Merge pull request #4936 from ethereum/binSelect

Binary search for dispatch.
This commit is contained in:
chriseth 2018-12-07 00:55:09 +01:00 committed by GitHub
commit 6a9e8a6fe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 276 additions and 6 deletions

View File

@ -5,6 +5,7 @@ Language Features:
Compiler Features:
* Inline Assembly: Improve error messages around invalid function argument count.
* Code Generator: Use binary search for dispatch function if more efficient. The size/speed tradeoff can be tuned using ``--optimize-runs``.
Bugfixes:

View File

@ -34,13 +34,13 @@ void Compiler::compileContract(
bytes const& _metadata
)
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns);
runtimeCompiler.compileContract(_contract, _contracts);
m_runtimeContext.appendAuxiliaryData(_metadata);
// This might modify m_runtimeContext because it can access runtime functions at
// creation time.
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize);
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts);
m_context.optimise(m_optimize, m_optimizeRuns);

View File

@ -268,6 +268,70 @@ void ContractCompiler::appendDelegatecallCheck()
// "We have not been called via DELEGATECALL".
}
void ContractCompiler::appendInternalSelector(
map<FixedHash<4>, eth::AssemblyItem const> const& _entryPoints,
vector<FixedHash<4>> const& _ids,
eth::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 * eth::GasCosts::createDataGas) / 6)
split = true;
else
split = (_runs * 6 * (_ids.size() - 4) > 17 * eth::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;
eth::AssemblyItem lessTag{m_context.appendConditionalJump()};
// Here, we have funid >= pivot
vector<FixedHash<4>> larger{_ids.begin() + pivotIndex, _ids.end()};
appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs);
m_context << lessTag;
// Here, we have funid < pivot
vector<FixedHash<4>> smaller{_ids.begin(), _ids.begin() + 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);
}
}
void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract)
{
map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions();
@ -290,13 +354,14 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true);
// stack now is: <can-call-non-view-functions>? <funhash>
vector<FixedHash<4>> sortedIDs;
for (auto const& it: interfaceFunctions)
{
callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag()));
m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << Instruction::EQ;
m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first));
sortedIDs.emplace_back(it.first);
}
m_context.appendJumpTo(notFound);
std::sort(sortedIDs.begin(), sortedIDs.end());
appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimise_runs);
m_context << notFound;
if (fallback)

View File

@ -38,8 +38,9 @@ namespace solidity {
class ContractCompiler: private ASTConstVisitor
{
public:
explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise):
explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise, size_t _optimise_runs = 200):
m_optimise(_optimise),
m_optimise_runs(_optimise_runs),
m_runtimeCompiler(_runtimeCompiler),
m_context(_context)
{
@ -81,6 +82,14 @@ private:
/// This is done by inserting a specific push constant as the first instruction
/// whose data will be modified in memory at deploy time.
void appendDelegatecallCheck();
/// Appends the function selector. Is called recursively to create a binary search tree.
/// @a _runs the number of intended executions of the contract to tune the split point.
void appendInternalSelector(
std::map<FixedHash<4>, eth::AssemblyItem const> const& _entryPoints,
std::vector<FixedHash<4>> const& _ids,
eth::AssemblyItem const& _notFoundTag,
size_t _runs
);
void appendFunctionSelector(ContractDefinition const& _contract);
void appendCallValueCheck();
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
@ -122,6 +131,7 @@ private:
void storeStackHeight(ASTNode const* _node);
bool const m_optimise;
size_t const m_optimise_runs = 200;
/// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context;

View File

@ -0,0 +1,43 @@
pragma solidity >=0.0;
contract Large {
uint public a;
uint[] public b;
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
function f4(uint x) public returns (uint) { b[uint8(msg.data[3])] = x; }
function f5(uint x) public returns (uint) { b[uint8(msg.data[4])] = x; }
function f6(uint x) public returns (uint) { b[uint8(msg.data[5])] = x; }
function f7(uint x) public returns (uint) { b[uint8(msg.data[6])] = x; }
function f8(uint x) public returns (uint) { b[uint8(msg.data[7])] = x; }
function f9(uint x) public returns (uint) { b[uint8(msg.data[8])] = x; }
function f0(uint x) public pure returns (uint) { require(x > 10); }
function g1(uint x) public payable returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function g2(uint x) public payable returns (uint) { b[uint8(msg.data[1])] = x; }
function g3(uint x) public payable returns (uint) { b[uint8(msg.data[2])] = x; }
function g4(uint x) public payable returns (uint) { b[uint8(msg.data[3])] = x; }
function g5(uint x) public payable returns (uint) { b[uint8(msg.data[4])] = x; }
function g6(uint x) public payable returns (uint) { b[uint8(msg.data[5])] = x; }
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
function g0(uint x) public payable returns (uint) { require(x > 10); }
}
contract Medium {
uint public a;
uint[] public b;
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
function g0(uint x) public payable returns (uint) { require(x > 10); }
}
contract Small {
uint public a;
uint[] public b;
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function () external payable {}
}

View File

@ -0,0 +1 @@
--gas

View File

@ -0,0 +1,53 @@
======= gas_test_dispatch.sol:Large =======
Gas estimation:
construction:
1034 + 998400 = 999434
external:
a(): 456
b(uint256): 857
f0(uint256): 438
f1(uint256): 40781
f2(uint256): 20722
f3(uint256): 20810
f4(uint256): 20788
f5(uint256): 20766
f6(uint256): 20789
f7(uint256): 20701
f8(uint256): 20701
f9(uint256): 20723
g0(uint256): 324
g1(uint256): 40736
g2(uint256): 20699
g3(uint256): 20787
g4(uint256): 20765
g5(uint256): 20721
g6(uint256): 20744
g7(uint256): 20743
g8(uint256): 20721
g9(uint256): 20678
======= gas_test_dispatch.sol:Medium =======
Gas estimation:
construction:
411 + 376600 = 377011
external:
a(): 433
b(uint256): 857
f1(uint256): 40692
f2(uint256): 20722
f3(uint256): 20766
g0(uint256): 324
g7(uint256): 20721
g8(uint256): 20699
g9(uint256): 20655
======= gas_test_dispatch.sol:Small =======
Gas estimation:
construction:
153 + 107800 = 107953
external:
fallback: 123
a(): 388
b(uint256): 813
f1(uint256): 40692

View File

@ -0,0 +1,43 @@
pragma solidity >=0.0;
contract Large {
uint public a;
uint[] public b;
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
function f4(uint x) public returns (uint) { b[uint8(msg.data[3])] = x; }
function f5(uint x) public returns (uint) { b[uint8(msg.data[4])] = x; }
function f6(uint x) public returns (uint) { b[uint8(msg.data[5])] = x; }
function f7(uint x) public returns (uint) { b[uint8(msg.data[6])] = x; }
function f8(uint x) public returns (uint) { b[uint8(msg.data[7])] = x; }
function f9(uint x) public returns (uint) { b[uint8(msg.data[8])] = x; }
function f0(uint x) public pure returns (uint) { require(x > 10); }
function g1(uint x) public payable returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function g2(uint x) public payable returns (uint) { b[uint8(msg.data[1])] = x; }
function g3(uint x) public payable returns (uint) { b[uint8(msg.data[2])] = x; }
function g4(uint x) public payable returns (uint) { b[uint8(msg.data[3])] = x; }
function g5(uint x) public payable returns (uint) { b[uint8(msg.data[4])] = x; }
function g6(uint x) public payable returns (uint) { b[uint8(msg.data[5])] = x; }
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
function g0(uint x) public payable returns (uint) { require(x > 10); }
}
contract Medium {
uint public a;
uint[] public b;
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
function g0(uint x) public payable returns (uint) { require(x > 10); }
}
contract Small {
uint public a;
uint[] public b;
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
function () external payable {}
}

View File

@ -0,0 +1 @@
--optimize --optimize-runs 2 --gas

View File

@ -0,0 +1,53 @@
======= gas_test_dispatch_optimize.sol:Large =======
Gas estimation:
construction:
300 + 262000 = 262300
external:
a(): 463
b(uint256): 1173
f0(uint256): 399
f1(uint256): 41164
f2(uint256): 21224
f3(uint256): 21312
f4(uint256): 21290
f5(uint256): 21268
f6(uint256): 21180
f7(uint256): 20960
f8(uint256): 21092
f9(uint256): 21114
g0(uint256): 639
g1(uint256): 40876
g2(uint256): 20958
g3(uint256): 21046
g4(uint256): 21024
g5(uint256): 21112
g6(uint256): 20892
g7(uint256): 21002
g8(uint256): 20980
g9(uint256): 20826
======= gas_test_dispatch_optimize.sol:Medium =======
Gas estimation:
construction:
190 + 143000 = 143190
external:
a(): 463
b(uint256): 931
f1(uint256): 40944
f2(uint256): 20982
f3(uint256): 21026
g0(uint256): 397
g7(uint256): 20892
g8(uint256): 20870
g9(uint256): 20826
======= gas_test_dispatch_optimize.sol:Small =======
Gas estimation:
construction:
117 + 67400 = 67517
external:
fallback: 183
a(): 441
b(uint256): 821
f1(uint256): 40878