[Solidity] solc 컴파일러 의 가스비 절약을 위한 함수 분할

오픈 소스인 Solidity를 공부하며 오픈소스를 분석하던 중 solc 컴파일러가 가스비 절약을 위해서 Contract의 함수 개수에 따라 함수를 분할한다.

 

함수가 4개 이하이면 분할하지 않고, 5개 이상이면 분할한다.

분할하면 가스비가 줄어든다. 

 

함수(해시값)를 분할해서, 피벗을 기준으로 위,아래로 나눠 재귀적으로 함수를 분할한다. 

 

코드에 허점이 있는지 찾아보는 중이다. 

 

생각해볼 점

- 절반으로 나눈게 사실 해시값을 절반으로 정확히 나눈게 아니다.

5나누기 2하면 1 2 3 4 5 에서 3이 될테지만, 

1 2 3 4 5 6 7 8 이면 4일지 5일지 모른다.

즉 짝수일때는 상대적으로 비효율

 

- 벡터의 인덱스와 함수의 해시값은 직접적으로 연관되어 있지 않다.

즉 함수의 개수를 고려한 방법이라고 했지만, 완전히 그렇지는 않다.

정확하게 해시로 변환해서 해시의 중앙을 찾는게 좋다.

 

void ContractCompiler::appendInternalSelector(
	std::map<FixedHash<4>, evmasm::AssemblyItem const> const& _entryPoints,
	std::vector<FixedHash<4>> const& _ids,
	evmasm::AssemblyItem const& _notFoundTag,
	size_t _runs
)
{
	// Code for selecting from n functions without split:
	//  n times: dup1, push4 <id_i>, eq, push2/3 <tag_i>, jumpi
	//  push2/3 <notfound> jump
	// (called SELECT[n])
	// Code for selecting from n functions with split:
	//  dup1, push4 <pivot>, gt, push2/3<tag_less>, jumpi
	//   SELECT[n/2]
	//  tag_less:
	//   SELECT[n/2]
	//
	// This means each split adds 16-18 bytes of additional code (note the additional jump out!)
	// The average execution cost if we do not split at all are:
	//  (3 + 3 + 3 + 3 + 10) * n/2 = 24 * n/2 = 12 * n
	// If we split once:
	//  (3 + 3 + 3 + 3 + 10) + 24 * n/4 = 24 * (n/4 + 1) = 6 * n + 24;
	//
	// We should split if
	//   _runs * 12 * n > _runs * (6 * n + 24) + 17 * createDataGas
	// <=> _runs * 6 * (n - 4) > 17 * createDataGas
	//
	// Which also means that the execution itself is not profitable
	// unless we have at least 5 functions.

	// Start with some comparisons to avoid overflow, then do the actual comparison.
	bool split = false;
	if (_ids.size() <= 4)
		split = false;
	else if (_runs > (17 * evmasm::GasCosts::createDataGas) / 6)
		split = true;
	else
		split = (_runs * 6 * (_ids.size() - 4) > 17 * evmasm::GasCosts::createDataGas);

	if (split)
	{
		size_t pivotIndex = _ids.size() / 2;
		FixedHash<4> pivot{_ids.at(pivotIndex)};
		m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT;
		evmasm::AssemblyItem lessTag{m_context.appendConditionalJump()};
		// Here, we have funid >= pivot
		std::vector<FixedHash<4>> larger{_ids.begin() + static_cast<ptrdiff_t>(pivotIndex), _ids.end()};
		appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs);
		m_context << lessTag;
		// Here, we have funid < pivot
		std::vector<FixedHash<4>> smaller{_ids.begin(), _ids.begin() + static_cast<ptrdiff_t>(pivotIndex)};
		appendInternalSelector(_entryPoints, smaller, _notFoundTag, _runs);
	}
	else
	{
		for (auto const& id: _ids)
		{
			m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ;
			m_context.appendConditionalJumpTo(_entryPoints.at(id));
		}
		m_context.appendJumpTo(_notFoundTag);
	}
}

 

 

참고 정보

  • ids 벡터는 함수 ID를 저장합니다. 함수 ID는 함수 이름과 매개변수 타입을 기반으로 계산됩니다.