[yul-phaser] ProgramCache: Add ability to gather cache stats

This commit is contained in:
Kamil Śliwak 2020-02-29 01:53:11 +01:00
parent d33ba54a38
commit 3e35decf2b
3 changed files with 131 additions and 0 deletions

View File

@ -71,6 +71,15 @@ protected:
BOOST_AUTO_TEST_SUITE(Phaser) BOOST_AUTO_TEST_SUITE(Phaser)
BOOST_AUTO_TEST_SUITE(ProgramCacheTest) BOOST_AUTO_TEST_SUITE(ProgramCacheTest)
BOOST_AUTO_TEST_CASE(CacheStats_operator_plus_should_add_stats_together)
{
CacheStats statsA{11, 12, 13, {{1, 14}, {2, 15}}};
CacheStats statsB{21, 22, 23, {{2, 24}, {3, 25}}};
CacheStats statsC{32, 34, 36, {{1, 14}, {2, 39}, {3, 25}}};
BOOST_CHECK(statsA + statsB == statsC);
}
BOOST_FIXTURE_TEST_CASE(optimiseProgram_should_apply_optimisation_steps_to_program, ProgramCacheFixture) BOOST_FIXTURE_TEST_CASE(optimiseProgram_should_apply_optimisation_steps_to_program, ProgramCacheFixture)
{ {
Program expectedProgram = optimisedProgram(m_program, "IuO"); Program expectedProgram = optimisedProgram(m_program, "IuO");
@ -201,6 +210,45 @@ BOOST_FIXTURE_TEST_CASE(startRound_should_remove_entries_older_than_two_rounds,
BOOST_TEST(m_programCache.size() == 0); BOOST_TEST(m_programCache.size() == 0);
} }
BOOST_FIXTURE_TEST_CASE(gatherStats_should_return_cache_statistics, ProgramCacheFixture)
{
size_t sizeI = optimisedProgram(m_program, "I").codeSize();
size_t sizeIu = optimisedProgram(m_program, "Iu").codeSize();
size_t sizeIuO = optimisedProgram(m_program, "IuO").codeSize();
size_t sizeL = optimisedProgram(m_program, "L").codeSize();
size_t sizeLT = optimisedProgram(m_program, "LT").codeSize();
m_programCache.optimiseProgram("L");
m_programCache.optimiseProgram("Iu");
BOOST_REQUIRE((cachedKeys(m_programCache) == set<string>{"L", "I", "Iu"}));
CacheStats expectedStats1{0, 3, sizeL + sizeI + sizeIu, {{0, 3}}};
BOOST_CHECK(m_programCache.gatherStats() == expectedStats1);
m_programCache.optimiseProgram("IuO");
BOOST_REQUIRE((cachedKeys(m_programCache) == set<string>{"L", "I", "Iu", "IuO"}));
CacheStats expectedStats2{2, 4, sizeL + sizeI + sizeIu + sizeIuO, {{0, 4}}};
BOOST_CHECK(m_programCache.gatherStats() == expectedStats2);
m_programCache.startRound(1);
BOOST_REQUIRE((cachedKeys(m_programCache) == set<string>{"L", "I", "Iu", "IuO"}));
BOOST_CHECK(m_programCache.gatherStats() == expectedStats2);
m_programCache.optimiseProgram("IuO");
BOOST_REQUIRE((cachedKeys(m_programCache) == set<string>{"L", "I", "Iu", "IuO"}));
CacheStats expectedStats3{5, 4, sizeL + sizeI + sizeIu + sizeIuO, {{0, 1}, {1, 3}}};
BOOST_CHECK(m_programCache.gatherStats() == expectedStats3);
m_programCache.startRound(2);
BOOST_REQUIRE((cachedKeys(m_programCache) == set<string>{"I", "Iu", "IuO"}));
CacheStats expectedStats4{5, 4, sizeI + sizeIu + sizeIuO, {{1, 3}}};
BOOST_CHECK(m_programCache.gatherStats() == expectedStats4);
m_programCache.optimiseProgram("LT");
BOOST_REQUIRE((cachedKeys(m_programCache) == set<string>{"L", "LT", "I", "Iu", "IuO"}));
CacheStats expectedStats5{5, 6, sizeL + sizeLT + sizeI + sizeIu + sizeIuO, {{1, 3}, {2, 2}}};
BOOST_CHECK(m_programCache.gatherStats() == expectedStats5);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View File

@ -23,6 +23,30 @@ using namespace std;
using namespace solidity::yul; using namespace solidity::yul;
using namespace solidity::phaser; using namespace solidity::phaser;
CacheStats& CacheStats::operator+=(CacheStats const& _other)
{
hits += _other.hits;
misses += _other.misses;
totalCodeSize += _other.totalCodeSize;
for (auto& [round, count]: _other.roundEntryCounts)
if (roundEntryCounts.find(round) != roundEntryCounts.end())
roundEntryCounts.at(round) += count;
else
roundEntryCounts.insert({round, count});
return *this;
}
bool CacheStats::operator==(CacheStats const& _other) const
{
return
hits == _other.hits &&
misses == _other.misses &&
totalCodeSize == _other.totalCodeSize &&
roundEntryCounts == _other.roundEntryCounts;
}
Program ProgramCache::optimiseProgram( Program ProgramCache::optimiseProgram(
string const& _abbreviatedOptimisationSteps, string const& _abbreviatedOptimisationSteps,
size_t _repetitionCount size_t _repetitionCount
@ -40,6 +64,7 @@ Program ProgramCache::optimiseProgram(
{ {
pair->second.roundNumber = m_currentRound; pair->second.roundNumber = m_currentRound;
++prefixSize; ++prefixSize;
++m_hits;
} }
else else
break; break;
@ -57,6 +82,7 @@ Program ProgramCache::optimiseProgram(
intermediateProgram.optimise({stepName}); intermediateProgram.optimise({stepName});
m_entries.insert({targetOptimisations.substr(0, i), {intermediateProgram, m_currentRound}}); m_entries.insert({targetOptimisations.substr(0, i), {intermediateProgram, m_currentRound}});
++m_misses;
} }
return intermediateProgram; return intermediateProgram;
@ -92,3 +118,34 @@ Program const* ProgramCache::find(string const& _abbreviatedOptimisationSteps) c
return &(pair->second.program); return &(pair->second.program);
} }
CacheStats ProgramCache::gatherStats() const
{
return {
/* hits = */ m_hits,
/* misses = */ m_misses,
/* totalCodeSize = */ calculateTotalCachedCodeSize(),
/* roundEntryCounts = */ countRoundEntries(),
};
}
size_t ProgramCache::calculateTotalCachedCodeSize() const
{
size_t size = 0;
for (auto const& pair: m_entries)
size += pair.second.program.codeSize();
return size;
}
map<size_t, size_t> ProgramCache::countRoundEntries() const
{
map<size_t, size_t> counts;
for (auto& pair: m_entries)
if (counts.find(pair.second.roundNumber) != counts.end())
++counts.at(pair.second.roundNumber);
else
counts.insert({pair.second.roundNumber, 1});
return counts;
}

View File

@ -39,6 +39,23 @@ struct CacheEntry
roundNumber(_roundNumber) {} roundNumber(_roundNumber) {}
}; };
/**
* Stores statistics about current cache usage.
*/
struct CacheStats
{
size_t hits;
size_t misses;
size_t totalCodeSize;
std::map<size_t, size_t> roundEntryCounts;
CacheStats& operator+=(CacheStats const& _other);
CacheStats operator+(CacheStats const& _other) const { return CacheStats(*this) += _other; }
bool operator==(CacheStats const& _other) const;
bool operator!=(CacheStats const& _other) const { return !(*this == _other); }
};
/** /**
* Class that optimises programs one step at a time which allows it to store and later reuse the * Class that optimises programs one step at a time which allows it to store and later reuse the
* results of the intermediate steps. * results of the intermediate steps.
@ -49,6 +66,8 @@ struct CacheEntry
* encountered in the current and the previous rounds. Entries older than that get removed to * encountered in the current and the previous rounds. Entries older than that get removed to
* conserve memory. * conserve memory.
* *
* @a gatherStats() allows getting statistics useful for determining cache effectiveness.
*
* The current strategy does speed things up (about 4:1 hit:miss ratio observed in my limited * The current strategy does speed things up (about 4:1 hit:miss ratio observed in my limited
* experiments) but there's room for improvement. We could fit more useful programs in * experiments) but there's room for improvement. We could fit more useful programs in
* the cache by being more picky about which ones we choose. * the cache by being more picky about which ones we choose.
@ -74,11 +93,16 @@ public:
Program const* find(std::string const& _abbreviatedOptimisationSteps) const; Program const* find(std::string const& _abbreviatedOptimisationSteps) const;
bool contains(std::string const& _abbreviatedOptimisationSteps) const { return find(_abbreviatedOptimisationSteps) != nullptr; } bool contains(std::string const& _abbreviatedOptimisationSteps) const { return find(_abbreviatedOptimisationSteps) != nullptr; }
CacheStats gatherStats() const;
std::map<std::string, CacheEntry> const& entries() const { return m_entries; }; std::map<std::string, CacheEntry> const& entries() const { return m_entries; };
Program const& program() const { return m_program; } Program const& program() const { return m_program; }
size_t currentRound() const { return m_currentRound; } size_t currentRound() const { return m_currentRound; }
private: private:
size_t calculateTotalCachedCodeSize() const;
std::map<size_t, size_t> countRoundEntries() const;
// The best matching data structure here would be a trie of chromosome prefixes but since // The best matching data structure here would be a trie of chromosome prefixes but since
// the programs are orders of magnitude larger than the prefixes, it does not really matter. // the programs are orders of magnitude larger than the prefixes, it does not really matter.
// A map should be good enough. // A map should be good enough.
@ -86,6 +110,8 @@ private:
Program m_program; Program m_program;
size_t m_currentRound = 0; size_t m_currentRound = 0;
size_t m_hits = 0;
size_t m_misses = 0;
}; };
} }