Merge pull request #8452 from imapp-pl/yul-phaser-more-output

[yul-phaser] More output
This commit is contained in:
chriseth 2020-03-25 16:43:05 +01:00 committed by GitHub
commit ff23f165f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 624 additions and 32 deletions

View File

@ -19,6 +19,7 @@
#include <tools/yulPhaser/AlgorithmRunner.h>
#include <tools/yulPhaser/Common.h>
#include <tools/yulPhaser/FitnessMetrics.h>
#include <liblangutil/CharStream.h>
@ -28,6 +29,9 @@
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/output_test_stream.hpp>
#include <regex>
#include <sstream>
using namespace std;
using namespace boost::unit_test::framework;
using namespace boost::test_tools;
@ -65,8 +69,36 @@ public:
class AlgorithmRunnerFixture
{
protected:
// NOTE: Regexes here should not contain spaces because we strip them before matching
regex RoundSummaryRegex{R"(-+ROUND\d+\[round:[0-9.]+s,total:[0-9.]+s\]-+)"};
regex InitialPopulationHeaderRegex{"-+INITIALPOPULATION-+"};
string individualPattern(Individual const& individual) const
{
ostringstream output;
output << individual.fitness << individual.chromosome;
return output.str();
}
string topChromosomePattern(size_t roundNumber, Individual const& individual) const
{
ostringstream output;
output << roundNumber << R"(\|[0-9.]+\|)" << individualPattern(individual);
return output.str();
}
bool nextLineMatches(stringstream& stream, regex const& pattern) const
{
string line;
if (getline(stream, line).fail())
return false;
return regex_match(stripWhitespace(line), pattern);
}
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
output_test_stream m_output;
Population const m_population = Population::makeRandom(m_fitnessMetric, 5, 0, 20);
stringstream m_output;
AlgorithmRunner::Options m_options;
};
@ -85,7 +117,6 @@ public:
protected:
TemporaryDirectory m_tempDir;
string const m_autosavePath = m_tempDir.memberPath("population-autosave.txt");
Population const m_population = Population::makeRandom(m_fitnessMetric, 5, 0, 20);
RandomisingAlgorithm m_algorithm;
};
@ -95,7 +126,7 @@ BOOST_AUTO_TEST_SUITE(AlgorithmRunnerTest)
BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRunnerFixture)
{
m_options.maxRounds = 5;
AlgorithmRunner runner(Population(m_fitnessMetric), {}, m_options, m_output);
AlgorithmRunner runner(m_population, {}, m_options, m_output);
CountingAlgorithm algorithm;
@ -106,29 +137,249 @@ BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRu
BOOST_TEST(algorithm.m_currentRound == 10);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_the_top_chromosome, AlgorithmRunnerFixture)
BOOST_FIXTURE_TEST_CASE(run_should_print_round_summary_after_each_round, AlgorithmRunnerFixture)
{
// run() is allowed to print more but should at least print the first one
m_options.maxRounds = 1;
AlgorithmRunner runner(
// NOTE: Chromosomes chosen so that they're not substrings of each other and are not
// words likely to appear in the output in normal circumstances.
Population(m_fitnessMetric, {Chromosome("fcCUnDve"), Chromosome("jsxIOo"), Chromosome("ighTLM")}),
{},
m_options,
m_output
);
m_options.showInitialPopulation = false;
m_options.showOnlyTopChromosome = false;
m_options.showRoundInfo = true;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
RandomisingAlgorithm algorithm;
CountingAlgorithm algorithm;
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, RoundSummaryRegex));
for (auto const& individual: runner.population().individuals())
BOOST_TEST(nextLineMatches(m_output, regex(individualPattern(individual))));
BOOST_TEST(m_output.is_empty());
runner.run(algorithm);
BOOST_TEST(countSubstringOccurrences(m_output.str(), toString(runner.population().individuals()[0].chromosome)) == 1);
BOOST_TEST(nextLineMatches(m_output, RoundSummaryRegex));
for (auto const& individual: runner.population().individuals())
BOOST_TEST(nextLineMatches(m_output, regex(individualPattern(individual))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_not_print_round_summary_if_not_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 1;
m_options.showInitialPopulation = false;
m_options.showOnlyTopChromosome = false;
m_options.showRoundInfo = false;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
RandomisingAlgorithm algorithm;
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, regex("")));
for (auto const& individual: runner.population().individuals())
BOOST_TEST(nextLineMatches(m_output, regex(individualPattern(individual))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_not_print_population_if_its_empty, AlgorithmRunnerFixture)
{
m_options.maxRounds = 1;
m_options.showInitialPopulation = false;
m_options.showOnlyTopChromosome = false;
m_options.showRoundInfo = true;
AlgorithmRunner runner(Population(m_fitnessMetric), {}, m_options, m_output);
RandomisingAlgorithm algorithm;
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, RoundSummaryRegex));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_only_top_chromosome_if_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 1;
m_options.showInitialPopulation = false;
m_options.showOnlyTopChromosome = true;
m_options.showRoundInfo = true;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
RandomisingAlgorithm algorithm;
runner.run(algorithm);
BOOST_TEST(countSubstringOccurrences(m_output.str(), toString(runner.population().individuals()[0].chromosome)) == 4);
BOOST_TEST(nextLineMatches(m_output, regex(topChromosomePattern(1, runner.population().individuals()[0]))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_not_print_round_number_for_top_chromosome_if_round_info_not_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 1;
m_options.showInitialPopulation = false;
m_options.showOnlyTopChromosome = true;
m_options.showRoundInfo = false;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
RandomisingAlgorithm algorithm;
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, regex(individualPattern(runner.population().individuals()[0]))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_not_print_population_if_its_empty_and_only_top_chromosome_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 3;
m_options.showRoundInfo = true;
m_options.showInitialPopulation = false;
m_options.showOnlyTopChromosome = true;
AlgorithmRunner runner(Population(m_fitnessMetric), {}, m_options, m_output);
RandomisingAlgorithm algorithm;
runner.run(algorithm);
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_initial_population_if_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 0;
m_options.showInitialPopulation = true;
m_options.showRoundInfo = false;
m_options.showOnlyTopChromosome = false;
RandomisingAlgorithm algorithm;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, InitialPopulationHeaderRegex));
for (auto const& individual: m_population.individuals())
BOOST_TEST(nextLineMatches(m_output, regex(individualPattern(individual))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_not_print_initial_population_if_not_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 0;
m_options.showInitialPopulation = false;
m_options.showRoundInfo = false;
m_options.showOnlyTopChromosome = false;
RandomisingAlgorithm algorithm;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
runner.run(algorithm);
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_whole_initial_population_even_if_only_top_chromosome_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 0;
m_options.showInitialPopulation = true;
m_options.showRoundInfo = false;
m_options.showOnlyTopChromosome = true;
RandomisingAlgorithm algorithm;
AlgorithmRunner runner(m_population, {}, m_options, m_output);
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, InitialPopulationHeaderRegex));
for (auto const& individual: m_population.individuals())
BOOST_TEST(nextLineMatches(m_output, regex(individualPattern(individual))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_cache_stats_if_requested, AlgorithmRunnerFixture)
{
m_options.maxRounds = 4;
m_options.showInitialPopulation = false;
m_options.showRoundInfo = false;
m_options.showOnlyTopChromosome = true;
m_options.showCacheStats = true;
RandomisingAlgorithm algorithm;
vector<CharStream> sourceStreams = {
CharStream("{mstore(10, 20)}", ""),
CharStream("{mstore(10, 20)\nsstore(10, 20)}", ""),
};
vector<Program> programs = {
get<Program>(Program::load(sourceStreams[0])),
get<Program>(Program::load(sourceStreams[1])),
};
vector<shared_ptr<ProgramCache>> caches = {
make_shared<ProgramCache>(programs[0]),
make_shared<ProgramCache>(programs[1]),
};
shared_ptr<FitnessMetric> fitnessMetric = make_shared<FitnessMetricAverage>(vector<shared_ptr<FitnessMetric>>{
make_shared<ProgramSize>(nullopt, caches[0]),
make_shared<ProgramSize>(nullopt, caches[1]),
});
Population population = Population::makeRandom(fitnessMetric, 2, 0, 5);
AlgorithmRunner runner(population, caches, m_options, m_output);
runner.run(algorithm);
BOOST_TEST(caches[0]->currentRound() == m_options.maxRounds.value());
BOOST_TEST(caches[1]->currentRound() == m_options.maxRounds.value());
CacheStats stats = caches[0]->gatherStats() + caches[1]->gatherStats();
for (size_t i = 0; i < m_options.maxRounds.value() - 1; ++i)
{
BOOST_TEST(nextLineMatches(m_output, regex(".*")));
BOOST_TEST(nextLineMatches(m_output, regex("-+CACHESTATS-+")));
if (i > 0)
BOOST_TEST(nextLineMatches(m_output, regex(R"(Round\d+:\d+entries)")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Round\d+:\d+entries)")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Totalhits:\d+)")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Totalmisses:\d+)")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Sizeofcachedcode:\d+)")));
}
BOOST_REQUIRE(stats.roundEntryCounts.size() == 2);
BOOST_REQUIRE(stats.roundEntryCounts.count(m_options.maxRounds.value() - 1) == 1);
BOOST_REQUIRE(stats.roundEntryCounts.count(m_options.maxRounds.value()) == 1);
size_t round = m_options.maxRounds.value();
BOOST_TEST(nextLineMatches(m_output, regex(".*")));
BOOST_TEST(nextLineMatches(m_output, regex("-+CACHESTATS-+")));
BOOST_TEST(nextLineMatches(m_output, regex("Round" + toString(round - 1) + ":" + toString(stats.roundEntryCounts[round - 1]) + "entries")));
BOOST_TEST(nextLineMatches(m_output, regex("Round" + toString(round) + ":" + toString(stats.roundEntryCounts[round]) + "entries")));
BOOST_TEST(nextLineMatches(m_output, regex("Totalhits:" + toString(stats.hits))));
BOOST_TEST(nextLineMatches(m_output, regex("Totalmisses:" + toString(stats.misses))));
BOOST_TEST(nextLineMatches(m_output, regex("Sizeofcachedcode:" + toString(stats.totalCodeSize))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_message_if_cache_stats_requested_but_cache_disabled, AlgorithmRunnerFixture)
{
m_options.maxRounds = 1;
m_options.showInitialPopulation = false;
m_options.showRoundInfo = false;
m_options.showOnlyTopChromosome = true;
m_options.showCacheStats = true;
RandomisingAlgorithm algorithm;
AlgorithmRunner runner(m_population, {nullptr}, m_options, m_output);
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, regex(".*")));
BOOST_TEST(nextLineMatches(m_output, regex("-+CACHESTATS-+")));
BOOST_TEST(nextLineMatches(m_output, regex(stripWhitespace("Program cache disabled"))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_print_partial_stats_and_message_if_some_caches_disabled, AlgorithmRunnerFixture)
{
m_options.maxRounds = 1;
m_options.showInitialPopulation = false;
m_options.showRoundInfo = false;
m_options.showOnlyTopChromosome = true;
m_options.showCacheStats = true;
RandomisingAlgorithm algorithm;
CharStream sourceStream = CharStream("{}", "");
shared_ptr<ProgramCache> cache = make_shared<ProgramCache>(get<Program>(Program::load(sourceStream)));
AlgorithmRunner runner(m_population, {cache, nullptr}, m_options, m_output);
BOOST_REQUIRE(cache->gatherStats().roundEntryCounts.size() == 0);
runner.run(algorithm);
BOOST_TEST(nextLineMatches(m_output, regex(".*")));
BOOST_TEST(nextLineMatches(m_output, regex("-+CACHESTATS-+")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Totalhits:\d+)")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Totalmisses:\d+)")));
BOOST_TEST(nextLineMatches(m_output, regex(R"(Sizeofcachedcode:\d+)")));
BOOST_TEST(nextLineMatches(m_output, regex(stripWhitespace("Program cache disabled for 1 out of 2 programs"))));
BOOST_TEST(m_output.peek() == EOF);
}
BOOST_FIXTURE_TEST_CASE(run_should_save_initial_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture)
@ -250,7 +501,7 @@ BOOST_FIXTURE_TEST_CASE(run_should_clear_cache_at_the_beginning_and_update_it_be
};
m_options.maxRounds = 10;
AlgorithmRunner runner(Population(m_fitnessMetric), caches, m_options, m_output);
AlgorithmRunner runner(m_population, caches, m_options, m_output);
CountingAlgorithm algorithm;
BOOST_TEST(algorithm.m_currentRound == 0);

View File

@ -71,6 +71,15 @@ protected:
BOOST_AUTO_TEST_SUITE(Phaser)
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)
{
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_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()

View File

@ -31,22 +31,102 @@ using namespace solidity::phaser;
void AlgorithmRunner::run(GeneticAlgorithm& _algorithm)
{
populationAutosave();
printInitialPopulation();
cacheClear();
clock_t totalTimeStart = clock();
for (size_t round = 0; !m_options.maxRounds.has_value() || round < m_options.maxRounds.value(); ++round)
{
clock_t roundTimeStart = clock();
cacheStartRound(round + 1);
m_population = _algorithm.runNextRound(m_population);
randomiseDuplicates();
m_outputStream << "---------- ROUND " << round + 1 << " ----------" << endl;
m_outputStream << m_population;
printRoundSummary(round, roundTimeStart, totalTimeStart);
printCacheStats();
populationAutosave();
}
}
void AlgorithmRunner::printRoundSummary(
size_t _round,
clock_t _roundTimeStart,
clock_t _totalTimeStart
) const
{
clock_t now = clock();
double roundTime = static_cast<double>(now - _roundTimeStart) / CLOCKS_PER_SEC;
double totalTime = static_cast<double>(now - _totalTimeStart) / CLOCKS_PER_SEC;
if (!m_options.showOnlyTopChromosome)
{
if (m_options.showRoundInfo)
{
m_outputStream << "---------- ROUND " << _round + 1;
m_outputStream << " [round: " << fixed << setprecision(1) << roundTime << " s,";
m_outputStream << " total: " << fixed << setprecision(1) << totalTime << " s]";
m_outputStream << " ----------" << endl;
}
else if (m_population.individuals().size() > 0)
m_outputStream << endl;
m_outputStream << m_population;
}
else if (m_population.individuals().size() > 0)
{
if (m_options.showRoundInfo)
{
m_outputStream << setw(5) << _round + 1 << " | ";
m_outputStream << setw(5) << fixed << setprecision(1) << totalTime << " | ";
}
m_outputStream << m_population.individuals()[0] << endl;
}
}
void AlgorithmRunner::printInitialPopulation() const
{
if (!m_options.showInitialPopulation)
return;
m_outputStream << "---------- INITIAL POPULATION ----------" << endl;
m_outputStream << m_population;
}
void AlgorithmRunner::printCacheStats() const
{
if (!m_options.showCacheStats)
return;
CacheStats totalStats{};
size_t disabledCacheCount = 0;
for (size_t i = 0; i < m_programCaches.size(); ++i)
if (m_programCaches[i] != nullptr)
totalStats += m_programCaches[i]->gatherStats();
else
++disabledCacheCount;
m_outputStream << "---------- CACHE STATS ----------" << endl;
if (disabledCacheCount < m_programCaches.size())
{
for (auto& [round, count]: totalStats.roundEntryCounts)
m_outputStream << "Round " << round << ": " << count << " entries" << endl;
m_outputStream << "Total hits: " << totalStats.hits << endl;
m_outputStream << "Total misses: " << totalStats.misses << endl;
m_outputStream << "Size of cached code: " << totalStats.totalCodeSize << endl;
}
if (disabledCacheCount == m_programCaches.size())
m_outputStream << "Program cache disabled" << endl;
else if (disabledCacheCount > 0)
{
m_outputStream << "Program cache disabled for " << disabledCacheCount << " out of ";
m_outputStream << m_programCaches.size() << " programs" << endl;
}
}
void AlgorithmRunner::populationAutosave() const
{
if (!m_options.populationAutosaveFile.has_value())

View File

@ -24,6 +24,7 @@
#include <tools/yulPhaser/Population.h>
#include <tools/yulPhaser/ProgramCache.h>
#include <ctime>
#include <optional>
#include <ostream>
@ -47,6 +48,10 @@ public:
bool randomiseDuplicates = false;
std::optional<size_t> minChromosomeLength = std::nullopt;
std::optional<size_t> maxChromosomeLength = std::nullopt;
bool showInitialPopulation = false;
bool showOnlyTopChromosome = false;
bool showRoundInfo = true;
bool showCacheStats = false;
};
AlgorithmRunner(
@ -66,6 +71,13 @@ public:
Population const& population() const { return m_population; }
private:
void printRoundSummary(
size_t _round,
clock_t _roundTimeStart,
clock_t _totalTimeStart
) const;
void printInitialPopulation() const;
void printCacheStats() const;
void populationAutosave() const;
void randomiseDuplicates();
void cacheClear();

View File

@ -46,6 +46,14 @@ namespace po = boost::program_options;
namespace
{
map<PhaserMode, string> const PhaserModeToStringMap =
{
{PhaserMode::RunAlgorithm, "run-algorithm"},
{PhaserMode::PrintOptimisedPrograms, "print-optimised-programs"},
{PhaserMode::PrintOptimisedASTs, "print-optimised-asts"},
};
map<string, PhaserMode> const StringToPhaserModeMap = invertMap(PhaserModeToStringMap);
map<Algorithm, string> const AlgorithmToStringMap =
{
{Algorithm::Random, "random"},
@ -71,6 +79,8 @@ map<string, MetricAggregatorChoice> const StringToMetricAggregatorChoiceMap = in
}
istream& phaser::operator>>(istream& _inputStream, PhaserMode& _phaserMode) { return deserializeChoice(_inputStream, _phaserMode, StringToPhaserModeMap); }
ostream& phaser::operator<<(ostream& _outputStream, PhaserMode _phaserMode) { return serializeChoice(_outputStream, _phaserMode, PhaserModeToStringMap); }
istream& phaser::operator>>(istream& _inputStream, Algorithm& _algorithm) { return deserializeChoice(_inputStream, _algorithm, StringToAlgorithmMap); }
ostream& phaser::operator<<(ostream& _outputStream, Algorithm _algorithm) { return serializeChoice(_outputStream, _algorithm, AlgorithmToStringMap); }
istream& phaser::operator>>(istream& _inputStream, MetricChoice& _metric) { return deserializeChoice(_inputStream, _metric, StringToMetricChoiceMap); }
@ -348,7 +358,7 @@ void Phaser::main(int _argc, char** _argv)
initialiseRNG(arguments.value());
runAlgorithm(arguments.value());
runPhaser(arguments.value());
}
Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
@ -392,6 +402,12 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
po::value<size_t>()->value_name("<NUM>"),
"The number of rounds after which the algorithm should stop. (default=no limit)."
)
(
"mode",
po::value<PhaserMode>()->value_name("<NAME>")->default_value(PhaserMode::RunAlgorithm),
"Mode of operation. The default is to run the algorithm but you can also tell phaser "
"to do something else with its parameters, e.g. just print the optimised programs and exit."
)
;
keywordDescription.add(generalDescription);
@ -543,6 +559,36 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
;
keywordDescription.add(cacheDescription);
po::options_description outputDescription("OUTPUT", lineLength, minDescriptionLength);
outputDescription.add_options()
(
"show-initial-population",
po::bool_switch(),
"Print the state of the population before the algorithm starts."
)
(
"show-only-top-chromosome",
po::bool_switch(),
"Print only the best chromosome found in each round rather than the whole population."
)
(
"hide-round",
po::bool_switch(),
"Hide information about the current round (round number and elapsed time)."
)
(
"show-cache-stats",
po::bool_switch(),
"Print information about cache size and effectiveness after each round."
)
(
"show-seed",
po::bool_switch(),
"Print the selected random seed."
)
;
keywordDescription.add(outputDescription);
po::positional_options_description positionalDescription;
positionalDescription.add("input-files", -1);
@ -581,6 +627,7 @@ void Phaser::initialiseRNG(po::variables_map const& _arguments)
seed = SimulationRNG::generateSeed();
SimulationRNG::reset(seed);
if (_arguments["show-seed"].as<bool>())
cout << "Random seed: " << seed << endl;
}
@ -592,28 +639,80 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c
!_arguments["no-randomise-duplicates"].as<bool>(),
_arguments["min-chromosome-length"].as<size_t>(),
_arguments["max-chromosome-length"].as<size_t>(),
_arguments["show-initial-population"].as<bool>(),
_arguments["show-only-top-chromosome"].as<bool>(),
!_arguments["hide-round"].as<bool>(),
_arguments["show-cache-stats"].as<bool>(),
};
}
void Phaser::runAlgorithm(po::variables_map const& _arguments)
void Phaser::runPhaser(po::variables_map const& _arguments)
{
auto programOptions = ProgramFactory::Options::fromCommandLine(_arguments);
auto cacheOptions = ProgramCacheFactory::Options::fromCommandLine(_arguments);
auto metricOptions = FitnessMetricFactory::Options::fromCommandLine(_arguments);
auto populationOptions = PopulationFactory::Options::fromCommandLine(_arguments);
auto algorithmOptions = GeneticAlgorithmFactory::Options::fromCommandLine(_arguments);
vector<Program> programs = ProgramFactory::build(programOptions);
vector<shared_ptr<ProgramCache>> programCaches = ProgramCacheFactory::build(cacheOptions, programs);
unique_ptr<FitnessMetric> fitnessMetric = FitnessMetricFactory::build(metricOptions, move(programs), programCaches);
unique_ptr<FitnessMetric> fitnessMetric = FitnessMetricFactory::build(metricOptions, programs, programCaches);
Population population = PopulationFactory::build(populationOptions, move(fitnessMetric));
if (_arguments["mode"].as<PhaserMode>() == PhaserMode::RunAlgorithm)
runAlgorithm(_arguments, move(population), move(programCaches));
else
printOptimisedProgramsOrASTs(_arguments, population, move(programs), _arguments["mode"].as<PhaserMode>());
}
void Phaser::runAlgorithm(
po::variables_map const& _arguments,
Population _population,
vector<shared_ptr<ProgramCache>> _programCaches
)
{
auto algorithmOptions = GeneticAlgorithmFactory::Options::fromCommandLine(_arguments);
unique_ptr<GeneticAlgorithm> geneticAlgorithm = GeneticAlgorithmFactory::build(
algorithmOptions,
population.individuals().size()
_population.individuals().size()
);
AlgorithmRunner algorithmRunner(population, move(programCaches), buildAlgorithmRunnerOptions(_arguments), cout);
AlgorithmRunner algorithmRunner(move(_population), move(_programCaches), buildAlgorithmRunnerOptions(_arguments), cout);
algorithmRunner.run(*geneticAlgorithm);
}
void Phaser::printOptimisedProgramsOrASTs(
po::variables_map const& _arguments,
Population const& _population,
vector<Program> _programs,
PhaserMode phaserMode
)
{
assert(phaserMode == PhaserMode::PrintOptimisedPrograms || phaserMode == PhaserMode::PrintOptimisedASTs);
assert(_programs.size() == _arguments["input-files"].as<vector<string>>().size());
if (_population.individuals().size() == 0)
{
cout << "<EMPTY POPULATION>" << endl;
return;
}
vector<string> const& paths = _arguments["input-files"].as<vector<string>>();
for (auto& individual: _population.individuals())
{
cout << "Chromosome: " << individual.chromosome << endl;
for (size_t i = 0; i < _programs.size(); ++i)
{
for (size_t j = 0; j < _arguments["chromosome-repetitions"].as<size_t>(); ++j)
_programs[i].optimise(individual.chromosome.optimisationSteps());
cout << "Program: " << paths[i] << endl;
if (phaserMode == PhaserMode::PrintOptimisedPrograms)
cout << _programs[i] << endl;
else
cout << _programs[i].toJson() << endl;
}
}
}

View File

@ -47,6 +47,13 @@ class Population;
class Program;
class ProgramCache;
enum class PhaserMode
{
RunAlgorithm,
PrintOptimisedPrograms,
PrintOptimisedASTs,
};
enum class Algorithm
{
Random,
@ -67,6 +74,8 @@ enum class MetricAggregatorChoice
Minimum,
};
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::PhaserMode& _phaserMode);
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::PhaserMode _phaserMode);
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::Algorithm& _algorithm);
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::Algorithm _algorithm);
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricChoice& _metric);
@ -223,7 +232,18 @@ private:
static void initialiseRNG(boost::program_options::variables_map const& _arguments);
static AlgorithmRunner::Options buildAlgorithmRunnerOptions(boost::program_options::variables_map const& _arguments);
static void runAlgorithm(boost::program_options::variables_map const& _arguments);
static void runPhaser(boost::program_options::variables_map const& _arguments);
static void runAlgorithm(
boost::program_options::variables_map const& _arguments,
Population _population,
std::vector<std::shared_ptr<ProgramCache>> _programCaches
);
static void printOptimisedProgramsOrASTs(
boost::program_options::variables_map const& _arguments,
Population const& _population,
std::vector<Program> _programs,
PhaserMode phaserMode
);
};
}

View File

@ -43,8 +43,7 @@ ostream& operator<<(ostream& _stream, Population const& _population);
ostream& phaser::operator<<(ostream& _stream, Individual const& _individual)
{
_stream << "Fitness: " << _individual.fitness;
_stream << ", optimisations: " << _individual.chromosome;
_stream << _individual.fitness << " " << _individual.chromosome;
return _stream;
}

View File

@ -23,6 +23,30 @@ using namespace std;
using namespace solidity::yul;
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(
string const& _abbreviatedOptimisationSteps,
size_t _repetitionCount
@ -40,6 +64,7 @@ Program ProgramCache::optimiseProgram(
{
pair->second.roundNumber = m_currentRound;
++prefixSize;
++m_hits;
}
else
break;
@ -57,6 +82,7 @@ Program ProgramCache::optimiseProgram(
intermediateProgram.optimise({stepName});
m_entries.insert({targetOptimisations.substr(0, i), {intermediateProgram, m_currentRound}});
++m_misses;
}
return intermediateProgram;
@ -92,3 +118,34 @@ Program const* ProgramCache::find(string const& _abbreviatedOptimisationSteps) c
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) {}
};
/**
* 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
* 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
* 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
* 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.
@ -74,11 +93,16 @@ public:
Program const* find(std::string const& _abbreviatedOptimisationSteps) const;
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; };
Program const& program() const { return m_program; }
size_t currentRound() const { return m_currentRound; }
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 programs are orders of magnitude larger than the prefixes, it does not really matter.
// A map should be good enough.
@ -86,6 +110,8 @@ private:
Program m_program;
size_t m_currentRound = 0;
size_t m_hits = 0;
size_t m_misses = 0;
};
}