From a97aeb0e6ee946ebf864534a29205bfd7fed15e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sun, 8 Mar 2020 16:55:24 +0100 Subject: [PATCH 01/13] [yul-phaser] AlgorithmRunner: A stronger test for run() output --- test/yulPhaser/AlgorithmRunner.cpp | 55 ++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 0382cc90d..ce6f43bd2 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -28,6 +28,9 @@ #include #include +#include +#include + using namespace std; using namespace boost::unit_test::framework; using namespace boost::test_tools; @@ -65,8 +68,29 @@ public: class AlgorithmRunnerFixture { protected: + // NOTE: Regexes here should not contain spaces because we strip them before matching + regex RoundSummaryRegex{R"(-+ROUND\d+-+)"}; + + string individualPattern(Individual const& individual) const + { + ostringstream output; + output << "Fitness:" << individual.fitness << ","; + output << "optimisations:" << individual.chromosome; + return output.str(); + 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 m_fitnessMetric = make_shared(); - output_test_stream m_output; + stringstream m_output; AlgorithmRunner::Options m_options; }; @@ -106,29 +130,24 @@ 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 + Population population(m_fitnessMetric, {Chromosome("fcCUnDve"), Chromosome("jsxIOo"), Chromosome("ighTLM")}); 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 - ); + AlgorithmRunner runner(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); - runner.run(algorithm); - runner.run(algorithm); - runner.run(algorithm); - BOOST_TEST(countSubstringOccurrences(m_output.str(), toString(runner.population().individuals()[0].chromosome)) == 4); + 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_save_initial_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture) From d6b96063f8ed816d220fa28537ccd277cff2363a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sun, 8 Mar 2020 16:59:14 +0100 Subject: [PATCH 02/13] [yul-phaser] AlgorithmRunner: Make all tests use population from AlgorithmRunnerFixture --- test/yulPhaser/AlgorithmRunner.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index ce6f43bd2..80a431fdc 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -90,6 +90,7 @@ protected: } shared_ptr m_fitnessMetric = make_shared(); + Population const m_population = Population::makeRandom(m_fitnessMetric, 5, 0, 20); stringstream m_output; AlgorithmRunner::Options m_options; }; @@ -109,7 +110,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; }; @@ -119,7 +119,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; @@ -132,10 +132,8 @@ BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRu BOOST_FIXTURE_TEST_CASE(run_should_print_round_summary_after_each_round, AlgorithmRunnerFixture) { - Population population(m_fitnessMetric, {Chromosome("fcCUnDve"), Chromosome("jsxIOo"), Chromosome("ighTLM")}); - m_options.maxRounds = 1; - AlgorithmRunner runner(population, {}, m_options, m_output); + AlgorithmRunner runner(m_population, {}, m_options, m_output); RandomisingAlgorithm algorithm; runner.run(algorithm); @@ -269,7 +267,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); From ec10a3d378f9bf07a1c9e4d99a17c627929911a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 27 Feb 2020 02:04:09 +0100 Subject: [PATCH 03/13] [yul-phaser] Add --show-initial-population option --- test/yulPhaser/AlgorithmRunner.cpp | 29 +++++++++++++++++++++++++++++ tools/yulPhaser/AlgorithmRunner.cpp | 10 ++++++++++ tools/yulPhaser/AlgorithmRunner.h | 2 ++ tools/yulPhaser/Phaser.cpp | 11 +++++++++++ 4 files changed, 52 insertions(+) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 80a431fdc..8cd4ec803 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -70,6 +70,7 @@ class AlgorithmRunnerFixture protected: // NOTE: Regexes here should not contain spaces because we strip them before matching regex RoundSummaryRegex{R"(-+ROUND\d+-+)"}; + regex InitialPopulationHeaderRegex{"-+INITIALPOPULATION-+"}; string individualPattern(Individual const& individual) const { @@ -133,6 +134,7 @@ BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRu BOOST_FIXTURE_TEST_CASE(run_should_print_round_summary_after_each_round, AlgorithmRunnerFixture) { m_options.maxRounds = 1; + m_options.showInitialPopulation = false; AlgorithmRunner runner(m_population, {}, m_options, m_output); RandomisingAlgorithm algorithm; @@ -148,6 +150,33 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_round_summary_after_each_round, Algorit 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; + 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; + 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_save_initial_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture) { m_options.maxRounds = 0; diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index e7ea5a072..dd1275daf 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -31,6 +31,7 @@ using namespace solidity::phaser; void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) { populationAutosave(); + printInitialPopulation(); cacheClear(); for (size_t round = 0; !m_options.maxRounds.has_value() || round < m_options.maxRounds.value(); ++round) @@ -47,6 +48,15 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) } } +void AlgorithmRunner::printInitialPopulation() const +{ + if (!m_options.showInitialPopulation) + return; + + m_outputStream << "---------- INITIAL POPULATION ----------" << endl; + m_outputStream << m_population; +} + void AlgorithmRunner::populationAutosave() const { if (!m_options.populationAutosaveFile.has_value()) diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index 8ee97ac48..978c6a7cb 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -47,6 +47,7 @@ public: bool randomiseDuplicates = false; std::optional minChromosomeLength = std::nullopt; std::optional maxChromosomeLength = std::nullopt; + bool showInitialPopulation = false; }; AlgorithmRunner( @@ -66,6 +67,7 @@ public: Population const& population() const { return m_population; } private: + void printInitialPopulation() const; void populationAutosave() const; void randomiseDuplicates(); void cacheClear(); diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 218b358a0..50cad1659 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -543,6 +543,16 @@ 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." + ) + ; + keywordDescription.add(outputDescription); + po::positional_options_description positionalDescription; positionalDescription.add("input-files", -1); @@ -592,6 +602,7 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c !_arguments["no-randomise-duplicates"].as(), _arguments["min-chromosome-length"].as(), _arguments["max-chromosome-length"].as(), + _arguments["show-initial-population"].as(), }; } From c875b3d94468ef9199a5ceb6428cd5b540099e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 27 Feb 2020 02:07:48 +0100 Subject: [PATCH 04/13] [yul-phaser] Add --show-only-top-chromosome and --hide-round options --- test/yulPhaser/AlgorithmRunner.cpp | 100 ++++++++++++++++++++++++++++ tools/yulPhaser/AlgorithmRunner.cpp | 29 +++++++- tools/yulPhaser/AlgorithmRunner.h | 6 ++ tools/yulPhaser/Phaser.cpp | 12 ++++ 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 8cd4ec803..b3379d661 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -78,6 +78,12 @@ protected: output << "Fitness:" << individual.fitness << ","; output << "optimisations:" << individual.chromosome; return output.str(); + } + + string topChromosomePattern(size_t roundNumber, Individual const& individual) const + { + ostringstream output; + output << roundNumber << "\\|" << individualPattern(individual); return output.str(); } @@ -135,6 +141,8 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_round_summary_after_each_round, Algorit { m_options.maxRounds = 1; m_options.showInitialPopulation = false; + m_options.showOnlyTopChromosome = false; + m_options.showRoundInfo = true; AlgorithmRunner runner(m_population, {}, m_options, m_output); RandomisingAlgorithm algorithm; @@ -150,10 +158,83 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_round_summary_after_each_round, Algorit 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(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); @@ -169,6 +250,8 @@ BOOST_FIXTURE_TEST_CASE(run_should_not_print_initial_population_if_not_requested { 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); @@ -177,6 +260,23 @@ BOOST_FIXTURE_TEST_CASE(run_should_not_print_initial_population_if_not_requested 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_save_initial_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture) { m_options.maxRounds = 0; diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index dd1275daf..90440e120 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -41,13 +41,36 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) m_population = _algorithm.runNextRound(m_population); randomiseDuplicates(); - m_outputStream << "---------- ROUND " << round + 1 << " ----------" << endl; - m_outputStream << m_population; - + printRoundSummary(round); populationAutosave(); } } +void AlgorithmRunner::printRoundSummary( + size_t _round +) const +{ + if (!m_options.showOnlyTopChromosome) + { + if (m_options.showRoundInfo) + { + m_outputStream << "---------- ROUND " << _round + 1; + 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 << m_population.individuals()[0] << endl; + } +} + void AlgorithmRunner::printInitialPopulation() const { if (!m_options.showInitialPopulation) diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index 978c6a7cb..abef48869 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -48,6 +49,8 @@ public: std::optional minChromosomeLength = std::nullopt; std::optional maxChromosomeLength = std::nullopt; bool showInitialPopulation = false; + bool showOnlyTopChromosome = false; + bool showRoundInfo = true; }; AlgorithmRunner( @@ -67,6 +70,9 @@ public: Population const& population() const { return m_population; } private: + void printRoundSummary( + size_t _round + ) const; void printInitialPopulation() const; void populationAutosave() const; void randomiseDuplicates(); diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 50cad1659..344b93208 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -550,6 +550,16 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() 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)." + ) ; keywordDescription.add(outputDescription); @@ -603,6 +613,8 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c _arguments["min-chromosome-length"].as(), _arguments["max-chromosome-length"].as(), _arguments["show-initial-population"].as(), + _arguments["show-only-top-chromosome"].as(), + !_arguments["hide-round"].as(), }; } From 47c3b558f2fc399f27b9e9c478b413e1f073645b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 27 Feb 2020 02:08:40 +0100 Subject: [PATCH 05/13] [yul-phaser] AlgorithmRunner: Print elapsed time after each round --- test/yulPhaser/AlgorithmRunner.cpp | 2 +- tools/yulPhaser/AlgorithmRunner.cpp | 14 ++++++++++++-- tools/yulPhaser/AlgorithmRunner.h | 4 +++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index b3379d661..831cb360a 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -69,7 +69,7 @@ class AlgorithmRunnerFixture { protected: // NOTE: Regexes here should not contain spaces because we strip them before matching - regex RoundSummaryRegex{R"(-+ROUND\d+-+)"}; + regex RoundSummaryRegex{R"(-+ROUND\d+\[round:[0-9.]+s,total:[0-9.]+s\]-+)"}; regex InitialPopulationHeaderRegex{"-+INITIALPOPULATION-+"}; string individualPattern(Individual const& individual) const diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index 90440e120..9ddce41bc 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -34,27 +34,37 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) printInitialPopulation(); cacheClear(); + chrono::steady_clock::time_point totalTimeStart = std::chrono::steady_clock::now(); for (size_t round = 0; !m_options.maxRounds.has_value() || round < m_options.maxRounds.value(); ++round) { + chrono::steady_clock::time_point roundTimeStart = std::chrono::steady_clock::now(); cacheStartRound(round + 1); m_population = _algorithm.runNextRound(m_population); randomiseDuplicates(); - printRoundSummary(round); + printRoundSummary(round, roundTimeStart, totalTimeStart); populationAutosave(); } } void AlgorithmRunner::printRoundSummary( - size_t _round + size_t _round, + chrono::steady_clock::time_point _roundTimeStart, + chrono::steady_clock::time_point _totalTimeStart ) const { if (!m_options.showOnlyTopChromosome) { if (m_options.showRoundInfo) { + chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + auto roundTime = chrono::duration_cast(now - _roundTimeStart).count(); + auto totalTime = chrono::duration_cast(now - _totalTimeStart).count(); + m_outputStream << "---------- ROUND " << _round + 1; + m_outputStream << " [round: " << fixed << setprecision(1) << roundTime / 1000.0 << " s,"; + m_outputStream << " total: " << fixed << setprecision(1) << totalTime / 1000.0 << " s]"; m_outputStream << " ----------" << endl; } else if (m_population.individuals().size() > 0) diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index abef48869..7c6946391 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -71,7 +71,9 @@ public: private: void printRoundSummary( - size_t _round + size_t _round, + std::chrono::steady_clock::time_point _roundTimeStart, + std::chrono::steady_clock::time_point _totalTimeStart ) const; void printInitialPopulation() const; void populationAutosave() const; From 1272a9335cf0b746d0c93e90071bdd7d572944c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 29 Feb 2020 00:56:47 +0100 Subject: [PATCH 06/13] [yul-phaser] Add --mode option --- tools/yulPhaser/Phaser.cpp | 35 ++++++++++++++++++++++++++++++----- tools/yulPhaser/Phaser.h | 14 +++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 344b93208..2a4ee4b86 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -46,6 +46,12 @@ namespace po = boost::program_options; namespace { +map const PhaserModeToStringMap = +{ + {PhaserMode::RunAlgorithm, "run-algorithm"}, +}; +map const StringToPhaserModeMap = invertMap(PhaserModeToStringMap); + map const AlgorithmToStringMap = { {Algorithm::Random, "random"}, @@ -71,6 +77,8 @@ map 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 +356,7 @@ void Phaser::main(int _argc, char** _argv) initialiseRNG(arguments.value()); - runAlgorithm(arguments.value()); + runPhaser(arguments.value()); } Phaser::CommandLineDescription Phaser::buildCommandLineDescription() @@ -392,6 +400,12 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::value()->value_name(""), "The number of rounds after which the algorithm should stop. (default=no limit)." ) + ( + "mode", + po::value()->value_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); @@ -618,13 +632,12 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c }; } -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 programs = ProgramFactory::build(programOptions); vector> programCaches = ProgramCacheFactory::build(cacheOptions, programs); @@ -632,11 +645,23 @@ void Phaser::runAlgorithm(po::variables_map const& _arguments) unique_ptr fitnessMetric = FitnessMetricFactory::build(metricOptions, move(programs), programCaches); Population population = PopulationFactory::build(populationOptions, move(fitnessMetric)); + if (_arguments["mode"].as() == PhaserMode::RunAlgorithm) + runAlgorithm(_arguments, move(population), move(programCaches)); +} + +void Phaser::runAlgorithm( + po::variables_map const& _arguments, + Population _population, + vector> _programCaches +) +{ + auto algorithmOptions = GeneticAlgorithmFactory::Options::fromCommandLine(_arguments); + unique_ptr 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); } diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index ba816044c..ef7c00f41 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -47,6 +47,11 @@ class Population; class Program; class ProgramCache; +enum class PhaserMode +{ + RunAlgorithm, +}; + enum class Algorithm { Random, @@ -67,6 +72,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 +230,12 @@ 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> _programCaches + ); }; } From d33ba54a38ebefe8861d947a620188fcb4fc9db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 29 Feb 2020 00:57:24 +0100 Subject: [PATCH 07/13] [yul-phaser] Add print-optimised-programs and print-optimised-asts modes --- tools/yulPhaser/Phaser.cpp | 41 +++++++++++++++++++++++++++++++++++++- tools/yulPhaser/Phaser.h | 8 ++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 2a4ee4b86..0bfd6a433 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -49,6 +49,8 @@ namespace map const PhaserModeToStringMap = { {PhaserMode::RunAlgorithm, "run-algorithm"}, + {PhaserMode::PrintOptimisedPrograms, "print-optimised-programs"}, + {PhaserMode::PrintOptimisedASTs, "print-optimised-asts"}, }; map const StringToPhaserModeMap = invertMap(PhaserModeToStringMap); @@ -642,11 +644,13 @@ void Phaser::runPhaser(po::variables_map const& _arguments) vector programs = ProgramFactory::build(programOptions); vector> programCaches = ProgramCacheFactory::build(cacheOptions, programs); - unique_ptr fitnessMetric = FitnessMetricFactory::build(metricOptions, move(programs), programCaches); + unique_ptr fitnessMetric = FitnessMetricFactory::build(metricOptions, programs, programCaches); Population population = PopulationFactory::build(populationOptions, move(fitnessMetric)); if (_arguments["mode"].as() == PhaserMode::RunAlgorithm) runAlgorithm(_arguments, move(population), move(programCaches)); + else + printOptimisedProgramsOrASTs(_arguments, population, move(programs), _arguments["mode"].as()); } void Phaser::runAlgorithm( @@ -665,3 +669,38 @@ void Phaser::runAlgorithm( AlgorithmRunner algorithmRunner(move(_population), move(_programCaches), buildAlgorithmRunnerOptions(_arguments), cout); algorithmRunner.run(*geneticAlgorithm); } + +void Phaser::printOptimisedProgramsOrASTs( + po::variables_map const& _arguments, + Population const& _population, + vector _programs, + PhaserMode phaserMode +) +{ + assert(phaserMode == PhaserMode::PrintOptimisedPrograms || phaserMode == PhaserMode::PrintOptimisedASTs); + assert(_programs.size() == _arguments["input-files"].as>().size()); + + if (_population.individuals().size() == 0) + { + cout << "" << endl; + return; + } + + vector const& paths = _arguments["input-files"].as>(); + 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(); ++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; + } + } +} diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index ef7c00f41..77e9e48c8 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -50,6 +50,8 @@ class ProgramCache; enum class PhaserMode { RunAlgorithm, + PrintOptimisedPrograms, + PrintOptimisedASTs, }; enum class Algorithm @@ -236,6 +238,12 @@ private: Population _population, std::vector> _programCaches ); + static void printOptimisedProgramsOrASTs( + boost::program_options::variables_map const& _arguments, + Population const& _population, + std::vector _programs, + PhaserMode phaserMode + ); }; } From 3e35decf2bade5d52018cfea98f611ad28a649b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 29 Feb 2020 01:53:11 +0100 Subject: [PATCH 08/13] [yul-phaser] ProgramCache: Add ability to gather cache stats --- test/yulPhaser/ProgramCache.cpp | 48 +++++++++++++++++++++++++++ tools/yulPhaser/ProgramCache.cpp | 57 ++++++++++++++++++++++++++++++++ tools/yulPhaser/ProgramCache.h | 26 +++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/test/yulPhaser/ProgramCache.cpp b/test/yulPhaser/ProgramCache.cpp index 598e7a16f..1ed6024bc 100644 --- a/test/yulPhaser/ProgramCache.cpp +++ b/test/yulPhaser/ProgramCache.cpp @@ -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{"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{"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{"L", "I", "Iu", "IuO"})); + BOOST_CHECK(m_programCache.gatherStats() == expectedStats2); + + m_programCache.optimiseProgram("IuO"); + BOOST_REQUIRE((cachedKeys(m_programCache) == set{"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{"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{"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() diff --git a/tools/yulPhaser/ProgramCache.cpp b/tools/yulPhaser/ProgramCache.cpp index bd3b05114..7acec16bd 100644 --- a/tools/yulPhaser/ProgramCache.cpp +++ b/tools/yulPhaser/ProgramCache.cpp @@ -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 ProgramCache::countRoundEntries() const +{ + map 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; +} diff --git a/tools/yulPhaser/ProgramCache.h b/tools/yulPhaser/ProgramCache.h index 433ba99c5..8cc830098 100644 --- a/tools/yulPhaser/ProgramCache.h +++ b/tools/yulPhaser/ProgramCache.h @@ -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 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 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 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; }; } From cd16a6e1780f9298170b96209cdb3429f72d15f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 29 Feb 2020 02:14:51 +0100 Subject: [PATCH 09/13] [yul-phaser] Add --show-cache-stats option --- test/yulPhaser/AlgorithmRunner.cpp | 106 ++++++++++++++++++++++++++++ tools/yulPhaser/AlgorithmRunner.cpp | 34 +++++++++ tools/yulPhaser/AlgorithmRunner.h | 2 + tools/yulPhaser/Phaser.cpp | 6 ++ 4 files changed, 148 insertions(+) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 831cb360a..fa87579dd 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -19,6 +19,7 @@ #include #include +#include #include @@ -277,6 +278,111 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_whole_initial_population_even_if_only_t 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 sourceStreams = { + CharStream("{mstore(10, 20)}", ""), + CharStream("{mstore(10, 20)\nsstore(10, 20)}", ""), + }; + vector programs = { + get(Program::load(sourceStreams[0])), + get(Program::load(sourceStreams[1])), + }; + vector> caches = { + make_shared(programs[0]), + make_shared(programs[1]), + }; + shared_ptr fitnessMetric = make_shared(vector>{ + make_shared(nullopt, caches[0]), + make_shared(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 cache = make_shared(get(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) { m_options.maxRounds = 0; diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index 9ddce41bc..5e03e2820 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -44,6 +44,7 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) randomiseDuplicates(); printRoundSummary(round, roundTimeStart, totalTimeStart); + printCacheStats(); populationAutosave(); } } @@ -90,6 +91,39 @@ void AlgorithmRunner::printInitialPopulation() const 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()) diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index 7c6946391..d1cffb30a 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -51,6 +51,7 @@ public: bool showInitialPopulation = false; bool showOnlyTopChromosome = false; bool showRoundInfo = true; + bool showCacheStats = false; }; AlgorithmRunner( @@ -76,6 +77,7 @@ private: std::chrono::steady_clock::time_point _totalTimeStart ) const; void printInitialPopulation() const; + void printCacheStats() const; void populationAutosave() const; void randomiseDuplicates(); void cacheClear(); diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 0bfd6a433..6e6ec7a3f 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -576,6 +576,11 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() 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." + ) ; keywordDescription.add(outputDescription); @@ -631,6 +636,7 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c _arguments["show-initial-population"].as(), _arguments["show-only-top-chromosome"].as(), !_arguments["hide-round"].as(), + _arguments["show-cache-stats"].as(), }; } From 58e3fca3de9245d170800964b26e61e050188657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 9 Mar 2020 19:19:42 +0100 Subject: [PATCH 10/13] [yul-phaser] AlgorithmRunner: Measure CPU time rather than wall-clock time --- tools/yulPhaser/AlgorithmRunner.cpp | 18 +++++++++--------- tools/yulPhaser/AlgorithmRunner.h | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index 5e03e2820..d3bda6960 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -34,10 +34,10 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) printInitialPopulation(); cacheClear(); - chrono::steady_clock::time_point totalTimeStart = std::chrono::steady_clock::now(); + clock_t totalTimeStart = clock(); for (size_t round = 0; !m_options.maxRounds.has_value() || round < m_options.maxRounds.value(); ++round) { - chrono::steady_clock::time_point roundTimeStart = std::chrono::steady_clock::now(); + clock_t roundTimeStart = clock(); cacheStartRound(round + 1); m_population = _algorithm.runNextRound(m_population); @@ -51,21 +51,21 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) void AlgorithmRunner::printRoundSummary( size_t _round, - chrono::steady_clock::time_point _roundTimeStart, - chrono::steady_clock::time_point _totalTimeStart + clock_t _roundTimeStart, + clock_t _totalTimeStart ) const { if (!m_options.showOnlyTopChromosome) { if (m_options.showRoundInfo) { - chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); - auto roundTime = chrono::duration_cast(now - _roundTimeStart).count(); - auto totalTime = chrono::duration_cast(now - _totalTimeStart).count(); + clock_t now = clock(); + double roundTime = static_cast(now - _roundTimeStart) / CLOCKS_PER_SEC; + double totalTime = static_cast(now - _totalTimeStart) / CLOCKS_PER_SEC; m_outputStream << "---------- ROUND " << _round + 1; - m_outputStream << " [round: " << fixed << setprecision(1) << roundTime / 1000.0 << " s,"; - m_outputStream << " total: " << fixed << setprecision(1) << totalTime / 1000.0 << " s]"; + 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) diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index d1cffb30a..4326ce227 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include @@ -73,8 +73,8 @@ public: private: void printRoundSummary( size_t _round, - std::chrono::steady_clock::time_point _roundTimeStart, - std::chrono::steady_clock::time_point _totalTimeStart + clock_t _roundTimeStart, + clock_t _totalTimeStart ) const; void printInitialPopulation() const; void printCacheStats() const; From 10e8d3616c0d24428f97b6e4d1ce62f03b51d642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 9 Mar 2020 19:23:25 +0100 Subject: [PATCH 11/13] [yul-phaser] AlgorithmRunner: Print total time when showing only the top chromosome --- test/yulPhaser/AlgorithmRunner.cpp | 2 +- tools/yulPhaser/AlgorithmRunner.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index fa87579dd..9d062d0ac 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -84,7 +84,7 @@ protected: string topChromosomePattern(size_t roundNumber, Individual const& individual) const { ostringstream output; - output << roundNumber << "\\|" << individualPattern(individual); + output << roundNumber << R"(\|[0-9.]+\|)" << individualPattern(individual); return output.str(); } diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index d3bda6960..c402e5d84 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -55,14 +55,14 @@ void AlgorithmRunner::printRoundSummary( clock_t _totalTimeStart ) const { + clock_t now = clock(); + double roundTime = static_cast(now - _roundTimeStart) / CLOCKS_PER_SEC; + double totalTime = static_cast(now - _totalTimeStart) / CLOCKS_PER_SEC; + if (!m_options.showOnlyTopChromosome) { if (m_options.showRoundInfo) { - clock_t now = clock(); - double roundTime = static_cast(now - _roundTimeStart) / CLOCKS_PER_SEC; - double totalTime = static_cast(now - _totalTimeStart) / CLOCKS_PER_SEC; - m_outputStream << "---------- ROUND " << _round + 1; m_outputStream << " [round: " << fixed << setprecision(1) << roundTime << " s,"; m_outputStream << " total: " << fixed << setprecision(1) << totalTime << " s]"; @@ -76,7 +76,10 @@ void AlgorithmRunner::printRoundSummary( 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; } From 3f524ccfe5d1c0bb7c5f18e814d4fd3a196a8b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 9 Mar 2020 19:24:37 +0100 Subject: [PATCH 12/13] [yul-phaser] Population: Print individuals in a more compact way --- test/yulPhaser/AlgorithmRunner.cpp | 3 +-- tools/yulPhaser/Population.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 9d062d0ac..8dd6e7564 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -76,8 +76,7 @@ protected: string individualPattern(Individual const& individual) const { ostringstream output; - output << "Fitness:" << individual.fitness << ","; - output << "optimisations:" << individual.chromosome; + output << individual.fitness << individual.chromosome; return output.str(); } diff --git a/tools/yulPhaser/Population.cpp b/tools/yulPhaser/Population.cpp index 87df4e59b..169cbfe30 100644 --- a/tools/yulPhaser/Population.cpp +++ b/tools/yulPhaser/Population.cpp @@ -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; } From e41ea6d25e6ff3269248e4110c99376ceacdc098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 9 Mar 2020 19:26:40 +0100 Subject: [PATCH 13/13] [yul-phaser] Add --show-seed option and don't print seed by default --- tools/yulPhaser/Phaser.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 6e6ec7a3f..2e38edbf0 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -581,6 +581,11 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() 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); @@ -622,7 +627,8 @@ void Phaser::initialiseRNG(po::variables_map const& _arguments) seed = SimulationRNG::generateSeed(); SimulationRNG::reset(seed); - cout << "Random seed: " << seed << endl; + if (_arguments["show-seed"].as()) + cout << "Random seed: " << seed << endl; } AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map const& _arguments)