diff --git a/python/conway_game_of_life/.gitignore b/python/conway_game_of_life/.gitignore new file mode 100644 index 0000000..1013463 --- /dev/null +++ b/python/conway_game_of_life/.gitignore @@ -0,0 +1,24 @@ +*.o +*.pyc +*.sw? +checks.json + +life +life_userparams +life_debug +life-codon + +conan.lock +conanbuildinfo.* +graph_info.json +conaninfo.txt + +# Python venv, pip, codon installation detritus: +bin +build +include +larger_tests +lib +lib64 +install.sh +pyvenv.cfg diff --git a/python/conway_game_of_life/Makefile b/python/conway_game_of_life/Makefile new file mode 100644 index 0000000..9ebb232 --- /dev/null +++ b/python/conway_game_of_life/Makefile @@ -0,0 +1,64 @@ + +ALL_TARGETS=life life_debug life_userparams life_noconstexpr life_userparams_noconstexpr + + +all: $(ALL_TARGETS) + +clean: + -rm $(ALL_TARGETS) + +distclean: clean conan-clean + +include conanbuildinfo.mak # Created by conan 'make' generator, defines CONAN_* variables used below +FMT_CXXFLAGS=$(CONAN_INCLUDE_DIRS_FMT:%=-I%) $(CONAN_DEFINES_FMT:%=-D%) $(CONAN_CXXFLAGS_FMT) +FMT_LFLAGS=$(CONAN_LIB_DIRS_FMT:%=-L%) $(CONAN_LIBS_FMT:%=-l%) $(CONAN_SYSTEM_LIBS_FMT:%=-l%) +BENCH_CXXFLAGS=$(CONAN_INCLUDE_DIRS_BENCHMARK:%=-I%) $(CONAN_DEFINES_BENCHMARK:%=-D%) $(CONAN_CXXFLAGS_BENCHMARK) +BENCH_LFLAGS=$(CONAN_LIB_DIRS_BENCHMARK:%=-L%) $(CONAN_LIBS_BENCHMARK:%=-l%) $(CONAN_SYSTEM_LIBS_BENCHMARK:%=-l%) +CATCH2_CXXFLAGS=$(CONAN_INCLUDE_DIRS_CATCH2:%=-I%) $(CONAN_DEFINES_CATCH2:%=-D%) $(CONAN_CXXFLAGS_CATCH2) +CATCH2_LFLAGS=$(CONAN_LIB_DIRS_CATCH2:%=-L%) $(CONAN_LIBS_CATCH2:%=-l%) $(CONAN_SYSTEM_LIBS_CATCH2:%=-l%) + +#read_all_conan_args=$(shell cat conanbuildinfo.args) # alternative, from conan compiler args generator + +CXX?=clang++ +CXX+=-m64 -DNDEBUG -D_GLIBCXX_USE_CXX11_ABI=0 # Toolchain flags not generated in conanbuildinfo.mak by conan 'make' generator. (Where is make_toolchain.mak? does it still exist?) + +info: + @echo CXX=$(CXX) + @echo FMT_CXXFLAGS=$(FMT_CXXFLAGS) + @echo FMT_LFLAGS=$(FMT_LFLAGS) + @echo BENCH_CXXFLAGS=$(BENCH_CXXFLAGS) + @echo BENCH_LFLAGS=$(BENCH_LFLAGS) + @echo CATCH2_CXXFLAGS=$(CATCH2_CXXFLAGS) + @echo CATCH2_LFLAGS=$(CATCH2_LFLAGS) + +help: + @echo Targets are: all info help conan clean distclean conan-clean $(ALL_TARGETS) + +conanbuildinfo.args conanbuildinfo.mak conanbuildinfo.txt &: conanfile.txt + conan install conanfile.txt && touch $@ # conan doesn't update file timestamp if no new contents were generated (even if conanfile.txt is newer) + +conan: conanbuildinfo.mak + +conan-clean: + -rm conanbuildinfo.args conanbuildinfo.mak conanbuildinfo.txt conaninfo.txt conan.lock graph_info.json + +%: %.cpp + $(CXX) -O3 -std=c++20 -Wall -Wextra $(CXXFLAGS) $(FMT_CXXFLAGS) -o $@ $< $(FMT_LFLAGS) + +life_userparams: life.cpp + $(CXX) -O3 -std=c++20 -Wall -Wextra -DUSER_PARAMS $(CXXFLAGS) $(FMT_CXXFLAGS) -o $@ $< $(FMT_LFLAGS) + +life_noconstexpr: life.cpp + $(CXX) -O3 -std=c++20 -Wall -Wextra -DNO_CONSTEXPR $(CXXFLAGS) $(FMT_CXXFLAGS) -o $@ $< $(FMT_LFLAGS) + +life_userparams_noconstexpr: life.cpp + $(CXX) -O3 -std=c++20 -Wall -Wextra -DUSER_PARAMS -DNO_CONSTEXPR $(CXXFLAGS) $(FMT_CXXFLAGS) -o $@ $< $(FMT_LFLAGS) + +%_debug: %.cpp + $(CXX) -g -Og -std=c++20 -Wall -Wextra $(CXXFLAGS) $(FMT_CXXFLAGS) -o $@ $< $(FMT_LFLAGS) + +%_benchmark: %.cpp + $(CXX) -g -O3 -std=c++20 -Wall -Wextra -DENABLE_BENCHMARK $(CXXFLAGS) $(FMT_CXXFLAGS) $(BENCH_CXXFLAGS) -o $@ $< $(FMT_LFLAGS) $(BENCH_LFLAGS) + +.PHONY: all conan clean distclean conan-clean help + diff --git a/python/conway_game_of_life/README.md b/python/conway_game_of_life/README.md new file mode 100644 index 0000000..e5dffe0 --- /dev/null +++ b/python/conway_game_of_life/README.md @@ -0,0 +1,71 @@ + +Game of Life Hacks to Play with C++, Python, Cppyy, Codon... +============================================================ + +C++ Version +----------- + +Clang is probably needed to allow constexpr std::vector. + +Makefile will run conan to install stuff if neccesary. + +To build life (and life_userparams) + + make -j3 CXX=clang++ + +Disable constexpr: + + make -j3 CXX=clang++ CXXFLAGS=-DNO_CONSTEXPR + +Disable constexpr and use g++: + + make -j3 CXX=g++ CXXFLAGS=-DNO_CONSTEXPR + +Python version with cPython or PyPy: +------------------------------------ + +To run with standard cPython: + + python3 life.py + +To install pypy3: + + sudo apt install pypy3 + +To run with pypy: + + pypy3 life.py + +cppyy versions: +--------------- + +To install cppyy: + + python3 -m venv conway_game_of_life + cd conway_game_of_life + . bin/activate + python3 -m pip install cppyy + +To run with cppyy, activate venv directory if not already (see above), then: + + bin/python3 life-cppyy.py + +There are additional variations life-cppyy-2.py and life-cppyy-3.py to try as well. + +Codon version: +-------------- + +To install codon: + + bash -c `curl -fsSL https://exaloop.io/install.sh` + +To build and run with codon: + + ~/.codon/bin/codon run -release life-codon.py + +Or: + + ~/.codon/bin/codon build -release -exe life-codon.py + ./life-codon + + diff --git a/python/conway_game_of_life/Results_RH.txt b/python/conway_game_of_life/Results_RH.txt new file mode 100644 index 0000000..6af27f3 --- /dev/null +++ b/python/conway_game_of_life/Results_RH.txt @@ -0,0 +1,132 @@ + +Not rigorous, just experimenting. + +Time values shown are just representative after running it a few times, +not an average or anything. + +clang 14.0.0, gcc 11.3.0, python 3.10.6, pypy 7.3.9 (with Python 3.8.13), codon 0.15.5, +cppyy 2.4.2 (cppyy-backend 1.14.10, cppyy-cling 6.27.1, cpycppyy 1.12.12). +Intel i7 3.6GHz CPUs (64 bit), Ubuntu 22.04.2, Linux 5.19.0. + + +life.cpp, Pure C++, clang++ -std=c++20 -O3: + + setup 0.00 ms + simulation 993.04 ms + output 0.12 ms + total 993.16 ms + + Size of ELF binary: 156.456 KB + + +life.cpp, Pure C++, clang++, no constexpr functions: + + setup 0.00 ms + simulation 996.84 ms + output 0.05 ms + total 996.89 ms + + Size of ELF binary: 156.584 KB + +life.py, Pure C++, g++ -std=c++20 -O3: + + Not tested (code needs to be hacked to avoid unsupported constexpr situations.) + +life.cpp, Pure C++, g++ -std=c++20 -O3, no constexpr functions: + + setup 0.00 ms + simulation 244.75 ms + output 0.04 ms + total 244.79 ms + + Size of ELF binary: 156.208 KB + + +life.cpp, Pure C++, clang++, game parameters as program arguments: + + setup 0.00 ms + simulation 1022.40 ms + output 0.07 ms + total 1022.47 ms + + Size of ELF binary: 156.568 KB + + +life.cpp, Pure C++, g++, no constexpr, game params as program arguments: + + setup 0.00 ms + simulation 2342.07 ms + output 0.07 ms + total 2342.13 ms + + + +life.py, Python3 (cpython): + + + setup 0.02 ms + simulation 53189.85 ms + output 0.78 ms + total 53190.66 ms + + +life.py, pypy3: + + setup 0.03 ms + simulation 4158.69 ms + output 6.38 ms + total 4165.13 ms + + +life-codon.py, using Codon (-release -exe): + + setup 0.01 ms + simulation 2435.31 ms + output 0.10 ms + total 2435.42 ms + + + +life-cppyy.py, uses cppyy, run with python3 3.10.6 installed in virtual env when installing cppyy: + + setup 271.24 ms + simulation 1174.23 ms + output 15.44 ms + total 1460.92 ms + + + +life-cppyy-2.py, uses cppyy, include all the Automata code (header only), but call a C++ function to run the game loop: + + setup 265.75 ms + simulation 1189.97 ms + output 13.86 ms + total 1469.58 ms + + +life-cppyy-3.py, uses cppyy, include all the Automata code (header only), but justcall a C++ function to do setup and run the game loop: + + setup 103.51 ms + simulation 1079.35 ms + output 20.26 ms + total 1203.13 ms + + + +life-cppyy-lib-run-demo-only.py, cppyy, but load Automata C++ code from a library, and call a C++ function to run the whole thing: +(i.e. a bit like the "Pure C++" examples, but we are invoking it from a Python program via cppyy. library built with g++ -O3) + + setup 34.39 ms + simulation 2766.24 ms + total 2800.66 ms + + Size of library: 20K + +life-cppyy-lib-run-demo-only.py, cppyy, but build Automata C++ code as a library (Automata class with visibility("hidden")), and call the one public C++ function to run the whole thing: +(even more like just invoking the optimized "Pure C++" program, but from python. library built with g++ -O3) + + setup 35.74 ms + simulation 1045.84 ms + total 1081.61 ms + + Size of library: 24K diff --git a/python/conway_game_of_life/conanfile.txt b/python/conway_game_of_life/conanfile.txt new file mode 100644 index 0000000..981955b --- /dev/null +++ b/python/conway_game_of_life/conanfile.txt @@ -0,0 +1,9 @@ +[requires] +fmt/[>=9.1.0] +benchmark/[>=1.7.1] +catch2/[>=3.3.0] + +[generators] +compiler_args +make +txt diff --git a/python/conway_game_of_life/life-codon.py b/python/conway_game_of_life/life-codon.py index ff481bc..bb26060 100644 --- a/python/conway_game_of_life/life-codon.py +++ b/python/conway_game_of_life/life-codon.py @@ -1,3 +1,5 @@ +import time + class Point: x: int y: int @@ -76,6 +78,7 @@ def add_glider(self, p: Point): self.set(p + Point(0, 2)) self.set(p + Point(1, 2)) +start = time.perf_counter() obj = Automata( 40, @@ -86,9 +89,15 @@ def add_glider(self, p: Point): obj.add_glider(Point(0, 18)) +setup_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 +start_simulation = time.perf_counter() + for i in range(0, 10000): obj = obj.next() +simulation_time_elapsed_ms = (time.perf_counter() - start_simulation) * 1000.0 +start_output = time.perf_counter() + for y in range(0, obj.height): for x in range(0, obj.width): if obj.get(Point(x, y)): @@ -96,3 +105,13 @@ def add_glider(self, p: Point): else: print(".", end="") print() + +output_time_elapsed_ms = (time.perf_counter() - start_output) * 1000.0 +total_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 + +print('') +print(f'setup {setup_time_elapsed_ms:>10.2f} ms') +print(f'simulation {simulation_time_elapsed_ms:>10.2f} ms') +print(f'output {output_time_elapsed_ms:>10.2f} ms') +print(f'total {total_time_elapsed_ms:>10.2f} ms') + diff --git a/python/conway_game_of_life/life-cppyy-2.py b/python/conway_game_of_life/life-cppyy-2.py new file mode 100644 index 0000000..b22e1eb --- /dev/null +++ b/python/conway_game_of_life/life-cppyy-2.py @@ -0,0 +1,48 @@ +import cppyy +from array import array +import time + + +# This is like life-cppyy.py, but the game loop is in a C++ function rather than here in Python. + +start = time.perf_counter() + +cppyy.include("life-cppyy.hpp") + + +born = cppyy.gbl.std.vector[bool]( + (False, False, False, True, False, False, False, False, False) +) +survives = cppyy.gbl.std.vector[bool]( + (False, False, True, True, False, False, False, False, False) +) + +obj = cppyy.gbl.Automata(40, 20, born, survives) + +obj.add_glider(cppyy.gbl.Automata.Point(0, 18)) + +setup_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 +start_simulation = time.perf_counter() + +obj = cppyy.gbl.run_demo_game(obj); + +simulation_time_elapsed_ms = (time.perf_counter() - start_simulation) * 1000.0 +start_output = time.perf_counter() + +for y in range(obj.height): + for x in range(obj.width): + if obj.get(cppyy.gbl.Automata.Point(x, y)): + print("X", end="") + else: + print(".", end="") + print() + +output_time_elapsed_ms = (time.perf_counter() - start_output) * 1000.0 +total_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 + +print('') +print(f'setup {setup_time_elapsed_ms:>10.2f} ms') +print(f'simulation {simulation_time_elapsed_ms:>10.2f} ms') +print(f'output {output_time_elapsed_ms:>10.2f} ms') +print(f'total {total_time_elapsed_ms:>10.2f} ms') + diff --git a/python/conway_game_of_life/life-cppyy-3.py b/python/conway_game_of_life/life-cppyy-3.py new file mode 100644 index 0000000..4beb3c5 --- /dev/null +++ b/python/conway_game_of_life/life-cppyy-3.py @@ -0,0 +1,37 @@ +import cppyy +from array import array +import time + +# This is like life-cppyy.py, except the creation of the initial Automata instance, +# the call to add_glider(), and the game run loop is all in one C++ function +# run_glider_demo_game(), rather than separate calls from Python. + +start = time.perf_counter() + +cppyy.include("life-cppyy.hpp") + +setup_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 +start_simulation = time.perf_counter() + +obj = cppyy.gbl.run_glider_demo_game(); + +simulation_time_elapsed_ms = (time.perf_counter() - start_simulation) * 1000.0 +start_output = time.perf_counter() + +for y in range(obj.height): + for x in range(obj.width): + if obj.get(cppyy.gbl.Automata.Point(x, y)): + print("X", end="") + else: + print(".", end="") + print() + +output_time_elapsed_ms = (time.perf_counter() - start_output) * 1000.0 +total_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 + +print('') +print(f'setup {setup_time_elapsed_ms:>10.2f} ms') +print(f'simulation {simulation_time_elapsed_ms:>10.2f} ms') +print(f'output {output_time_elapsed_ms:>10.2f} ms') +print(f'total {total_time_elapsed_ms:>10.2f} ms') + diff --git a/python/conway_game_of_life/life-cppyy-lib-run-demo-only.py b/python/conway_game_of_life/life-cppyy-lib-run-demo-only.py new file mode 100644 index 0000000..8df858c --- /dev/null +++ b/python/conway_game_of_life/life-cppyy-lib-run-demo-only.py @@ -0,0 +1,27 @@ +import cppyy +from array import array +import time + +# This is like life-cppyy.py, except we load a library (which may have been optimized differently) and only call the run_glider_demo_game() function. + +start = time.perf_counter() + +cppyy.include("lifelib/rundemos.hpp") +cppyy.load_library("lifelib/liblifelib") + +setup_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 +start_simulation = time.perf_counter() + +n = cppyy.gbl.run_glider_demo_game(); + +simulation_time_elapsed_ms = (time.perf_counter() - start_simulation) * 1000.0 + +print(f'{n} cells alive') + +total_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 + +print('') +print(f'setup {setup_time_elapsed_ms:>10.2f} ms') +print(f'simulation {simulation_time_elapsed_ms:>10.2f} ms') +print(f'total {total_time_elapsed_ms:>10.2f} ms') + diff --git a/python/conway_game_of_life/life-cppyy.hpp b/python/conway_game_of_life/life-cppyy.hpp index 7befcfc..716c1c9 100644 --- a/python/conway_game_of_life/life-cppyy.hpp +++ b/python/conway_game_of_life/life-cppyy.hpp @@ -101,3 +101,29 @@ struct Automata set(p + Point{ 1, 2 }); } }; + +Automata run_glider_demo_game() //size_t n, size_t w, size_t h) +{ + const size_t n = 10'000; + const size_t w = 40; + const size_t h = 20; + const Automata::index_t x = 0; + const Automata::index_t y = 18; + const std::array born{ false, false, false, true, false, false, false, false, false }; + const std::array surv{ false, false, true, true, false, false, false, false, false }; + auto a = Automata(w, h, born, surv); + a.add_glider(Automata::Point{x, y}); + for(size_t i = 0; i < n; ++i) + a = a.next(); + return a; +} + +Automata run_demo_game(Automata a) // size_t n +{ + const size_t n = 10000; + for(size_t i = 0; i < n; ++i) + a = a.next(); + return a; +} + + diff --git a/python/conway_game_of_life/life-cppyy.py b/python/conway_game_of_life/life-cppyy.py index 1032a48..87e464f 100644 --- a/python/conway_game_of_life/life-cppyy.py +++ b/python/conway_game_of_life/life-cppyy.py @@ -1,8 +1,12 @@ import cppyy from array import array +import time + +start = time.perf_counter() cppyy.include("life-cppyy.hpp") + born = cppyy.gbl.std.vector[bool]( (False, False, False, True, False, False, False, False, False) ) @@ -14,9 +18,14 @@ obj.add_glider(cppyy.gbl.Automata.Point(0, 18)) +setup_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 +start_simulation = time.perf_counter() + for i in range(10000): obj = obj.next() +simulation_time_elapsed_ms = (time.perf_counter() - start_simulation) * 1000.0 +start_output = time.perf_counter() for y in range(obj.height): for x in range(obj.width): @@ -25,3 +34,13 @@ else: print(".", end="") print() + +output_time_elapsed_ms = (time.perf_counter() - start_output) * 1000.0 +total_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 + +print('') +print(f'setup {setup_time_elapsed_ms:>10.2f} ms') +print(f'simulation {simulation_time_elapsed_ms:>10.2f} ms') +print(f'output {output_time_elapsed_ms:>10.2f} ms') +print(f'total {total_time_elapsed_ms:>10.2f} ms') + diff --git a/python/conway_game_of_life/life.cpp b/python/conway_game_of_life/life.cpp index 4f62a2d..bb50223 100644 --- a/python/conway_game_of_life/life.cpp +++ b/python/conway_game_of_life/life.cpp @@ -3,10 +3,22 @@ #include #include #include -#include +#include "fmt/format.h" +#include +#include // assert() + +#ifdef USER_PARAMS +#include // atol() +#endif + +#ifdef NO_CONSTEXPR +#define CONSTEXPR +#else +#define CONSTEXPR constexpr +#endif // necessary to keep the same algorithm as Python -constexpr auto floor_modulo(auto dividend, auto divisor) +CONSTEXPR auto floor_modulo(auto dividend, auto divisor) { return ((dividend % divisor) + divisor) % divisor; } @@ -24,7 +36,7 @@ struct Automata std::vector data = std::vector(width * height); - constexpr Automata(std::size_t width_, std::size_t height_, std::array born_, std::array survives_) + CONSTEXPR Automata(std::size_t width_, std::size_t height_, std::array born_, std::array survives_) : width(width_), height(height_), born(born_), survives(survives_) {} struct Point @@ -32,21 +44,24 @@ struct Automata index_t x; index_t y; - [[nodiscard]] constexpr Point operator+(Point rhs) const + [[nodiscard]] CONSTEXPR Point operator+(Point rhs) const { return Point{ x + rhs.x, y + rhs.y }; } }; - [[nodiscard]] constexpr std::size_t index(Point p) const + [[nodiscard]] CONSTEXPR std::size_t index(Point p) const { return floor_modulo(p.y, static_cast(height)) * width + floor_modulo(p.x, static_cast(width)); } - [[nodiscard]] constexpr bool get(Point p) const { return data[index(p)]; } + [[nodiscard]] CONSTEXPR bool get(Point p) const { return data[index(p)]; } - constexpr void set(Point p) { data[index(p)] = true; } + CONSTEXPR void set(Point p) { data[index(p)] = true; } +#ifdef NO_CONSTEXPR + static std::array neighbors; +#else constexpr static std::array neighbors{ Point{ -1, -1 }, Point{ 0, -1 }, @@ -57,14 +72,15 @@ struct Automata Point{ 0, 1 }, Point{ 1, 1 } }; +#endif - constexpr std::size_t count_neighbors(Point p) const + CONSTEXPR std::size_t count_neighbors(Point p) const { return static_cast(std::ranges::count_if( neighbors, [&](auto offset) { return get(p + offset); })); } - [[nodiscard]] constexpr Automata next() const + [[nodiscard]] CONSTEXPR Automata next() const { Automata result{ width, height, born, survives }; @@ -86,30 +102,84 @@ struct Automata return result; } - constexpr void add_glider(Point p) + CONSTEXPR void add_glider(Point p) { set(p); - set(p + Point(1, 1)); - set(p + Point(2, 1)); - set(p + Point(0, 2)); - set(p + Point(1, 2)); + set(p + Point{1, 1}); + set(p + Point{2, 1}); + set(p + Point{0, 2}); + set(p + Point{1, 2}); } }; -int main() -{ - auto obj = Automata(40, 20, { false, false, false, true, false, false, false, false, false }, { false, false, true, true, false, false, false, false, false }); +#ifdef NO_CONSTEXPR +std::array Automata::neighbors { + Automata::Point{ -1, -1 }, + Automata::Point{ 0, -1 }, + Automata::Point{ 1, -1 }, + Automata::Point{ -1, 0 }, + Automata::Point{ 1, 0 }, + Automata::Point{ -1, 1 }, + Automata::Point{ 0, 1 }, + Automata::Point{ 1, 1 } + }; +#endif + +// convert time to floating point milliseconds. (E.g. higher resolution (microseconds, nanoseconds, etc.) to ms.) +inline std::chrono::duration to_ms_float(auto t) { + //return std::chrono::duration_cast(t); + return std::chrono::duration(t); +} - obj.add_glider(Automata::Point(0, 18)); +int main([[maybe_unused]] int argc, [[maybe_unused]] char **argv) +{ +#ifdef USER_PARAMS + assert(argc > 4); + long warg = atol(argv[1]); + long harg = atol(argv[2]); + long xarg = atol(argv[3]); + long yarg = atol(argv[4]); + assert(warg > 0); + assert(harg > 0); + assert(xarg >= 0); + assert(yarg >= 0); + size_t w = (size_t)warg; + size_t h = (size_t)harg; + Automata::index_t x = (Automata::index_t)xarg; + Automata::index_t y = (Automata::index_t)yarg; + fmt::print("Using parameters from command line: game size ({} x {}), glider start position ({}, {}).\n", w, h, x, y); +#else + const size_t w = 40; + const size_t h = 20; + const Automata::index_t x = 0; + const Automata::index_t y = 18; + fmt::print("Note, compiled to use constant parameters: game size ({} x {}), glider start position ({}, {}).\n", w, h, x, y); +#endif + + using clock = std::chrono::steady_clock; + auto start = clock::now(); + + auto obj = Automata(w, h, { false, false, false, true, false, false, false, false, false }, { false, false, true, true, false, false, false, false, false }); + + obj.add_glider(Automata::Point{x, y}); + + auto setup_time_elapsed = clock::now() - start; + auto start_simulation = clock::now(); for (int i = 0; i < 10'000; ++i) { obj = obj.next(); } + auto simulation_time_elapsed = clock::now() - start_simulation; + auto start_output = clock::now(); + for (size_t y = 0; y < obj.height; ++y) { for (size_t x = 0; x < obj.width; ++x) { - if (obj.get(Automata::Point(static_cast(x), - static_cast(y)))) { + auto p = Automata::Point { + static_cast(x), + static_cast(y) + }; + if (obj.get(p)) { std::putchar('X'); } else { std::putchar('.'); @@ -117,4 +187,16 @@ int main() } std::puts(""); } + + auto output_time_elapsed = clock::now() - start_output; + auto total_time_elapsed = clock::now() - start; + + fmt::print("\n"); + fmt::print("setup {:>10.2f} ms\n", to_ms_float(setup_time_elapsed).count()); + fmt::print("simulation {:>10.2f} ms\n", to_ms_float(simulation_time_elapsed).count()); + fmt::print("output {:>10.2f} ms\n", to_ms_float(output_time_elapsed).count()); + fmt::print("total {:>10.2f} ms\n", to_ms_float(total_time_elapsed).count()); + + return 0; } + diff --git a/python/conway_game_of_life/life.py b/python/conway_game_of_life/life.py index c73c1d7..0309931 100644 --- a/python/conway_game_of_life/life.py +++ b/python/conway_game_of_life/life.py @@ -1,3 +1,5 @@ +import time + class Point: def __init__(self, x, y): self.x = x @@ -62,6 +64,8 @@ def add_glider(self, p: Point): self.set(p + Point(1, 2)) +start = time.perf_counter() + obj = Automata( 40, 20, @@ -71,9 +75,15 @@ def add_glider(self, p: Point): obj.add_glider(Point(0, 18)) +setup_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 +start_simulation = time.perf_counter() + for i in range(0, 10000): obj = obj.next() +simulation_time_elapsed_ms = (time.perf_counter() - start_simulation) * 1000.0 +start_output = time.perf_counter() + for y in range(0, obj.height): for x in range(0, obj.width): if obj.get(Point(x, y)): @@ -81,3 +91,13 @@ def add_glider(self, p: Point): else: print(".", end="") print() + +output_time_elapsed_ms = (time.perf_counter() - start_output) * 1000.0 +total_time_elapsed_ms = (time.perf_counter() - start) * 1000.0 + +print('') +print(f'setup {setup_time_elapsed_ms:>10.2f} ms') +print(f'simulation {simulation_time_elapsed_ms:>10.2f} ms') +print(f'output {output_time_elapsed_ms:>10.2f} ms') +print(f'total {total_time_elapsed_ms:>10.2f} ms') + diff --git a/python/conway_game_of_life/lifelib/Makefile b/python/conway_game_of_life/lifelib/Makefile new file mode 100644 index 0000000..2ae9270 --- /dev/null +++ b/python/conway_game_of_life/lifelib/Makefile @@ -0,0 +1,18 @@ + +all: liblifelib.so + +clean: + -rm liblifelib.so liblifelib.o + +CXX?=clang++ + +info: + @echo CXX=$(CXX) + +liblifelib.so: liblifelib.o + $(CXX) -shared -o $@ $^ + +liblifelib.o: lifelib.cpp automata.hpp rundemos.hpp + $(CXX) -O3 -std=c++20 -Wall -Wextra -fPIC $(CXXFLAGS) -c -o $@ $< -lm + + diff --git a/python/conway_game_of_life/lifelib/automata.hpp b/python/conway_game_of_life/lifelib/automata.hpp new file mode 100644 index 0000000..11d1636 --- /dev/null +++ b/python/conway_game_of_life/lifelib/automata.hpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +struct __attribute__ ((visibility ("hidden"))) +Automata +{ + using index_t = std::make_signed_t; + + std::size_t width; + std::size_t height; + std::array born; + std::array survives; + + std::vector data = std::vector(width * height); + + Automata(std::size_t width_, std::size_t height_, std::array born_, std::array survives_); + + Automata(std::size_t width_, std::size_t height_, const std::vector &born_, const std::vector &survives_); + + + struct Point + { + index_t x; + index_t y; + + [[nodiscard]] constexpr Point operator+(Point rhs) const + { + return Point{ x + rhs.x, y + rhs.y }; + } + }; + + [[nodiscard]] std::size_t index(Point p) const; + + [[nodiscard]] bool get(Point p) const { return data[index(p)]; } + + void set(Point p) { data[index(p)] = true; } + + constexpr static std::array neighbors{ + Point{ -1, -1 }, + Point{ 0, -1 }, + Point{ 1, -1 }, + Point{ -1, 0 }, + Point{ 1, 0 }, + Point{ -1, 1 }, + Point{ 0, 1 }, + Point{ 1, 1 } + }; + + std::size_t count_neighbors(Point p) const; + + [[nodiscard]] Automata next() const; + + void add_glider(Point p) + { + set(p); + set(p + Point{ 1, 1 }); + set(p + Point{ 2, 1 }); + set(p + Point{ 0, 2 }); + set(p + Point{ 1, 2 }); + } +}; + diff --git a/python/conway_game_of_life/lifelib/lifelib.cpp b/python/conway_game_of_life/lifelib/lifelib.cpp new file mode 100644 index 0000000..537fb35 --- /dev/null +++ b/python/conway_game_of_life/lifelib/lifelib.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include + +#include "automata.hpp" +#include "rundemos.hpp" + + + +template +constexpr auto floor_modulo(LHS dividend, RHS divisor) +{ + return ((dividend % divisor) + divisor) % divisor; +} + +Automata::Automata(std::size_t width_, std::size_t height_, std::array born_, std::array survives_) + : width(width_), height(height_), born(born_), survives(survives_) {} + +Automata::Automata(std::size_t width_, std::size_t height_, const std::vector &born_, const std::vector &survives_) + : width(width_), height(height_) +{ + std::copy_n(std::begin(born_), std::min(born_.size(), born.size()), std::begin(born)); + std::copy_n(std::begin(survives_), std::min(survives_.size(), survives.size()), std::begin(survives)); +} + +[[nodiscard]] Automata Automata::next() const +{ + Automata result{ width, height, born, survives }; + + for (std::size_t y = 0; y < height; ++y) { + for (std::size_t x = 0; x < width; ++x) { + Point p{ static_cast(x), static_cast(y) }; + const auto neighbors = count_neighbors(p); + if (get(p)) { + if (survives[neighbors]) { + result.set(p); + } + } else { + if (born[neighbors]) { + result.set(p); + } + } + } + } + + return result; +} + + +[[nodiscard]] std::size_t Automata::index(Automata::Point p) const +{ + return floor_modulo(p.y, static_cast(height)) * width + floor_modulo(p.x, static_cast(width)); +} + + +std::size_t Automata::count_neighbors(Point p) const +{ + return static_cast( + std::count_if(neighbors.begin(), neighbors.end(), [&](auto offset) { return get(p + offset); })); +} + +size_t run_glider_demo_game() //size_t n, size_t w, size_t h) +{ + const size_t n = 10'000; + const size_t w = 40; + const size_t h = 20; + const Automata::index_t x = 0; + const Automata::index_t y = 18; + const std::array born{ false, false, false, true, false, false, false, false, false }; + const std::array surv{ false, false, true, true, false, false, false, false, false }; + auto obj = Automata(w, h, born, surv); + obj.add_glider(Automata::Point{x, y}); + for(size_t i = 0; i < n; ++i) + obj = obj.next(); + + + size_t occupied_cells = 0; + + for (size_t y = 0; y < obj.height; ++y) { + for (size_t x = 0; x < obj.width; ++x) { + auto p = Automata::Point { + static_cast(x), + static_cast(y) + }; + if (obj.get(p)) { + std::putchar('X'); + ++occupied_cells; + } else { + std::putchar('.'); + } + } + std::puts(""); + } + + return occupied_cells; +} + + + diff --git a/python/conway_game_of_life/lifelib/rundemos.hpp b/python/conway_game_of_life/lifelib/rundemos.hpp new file mode 100644 index 0000000..814653f --- /dev/null +++ b/python/conway_game_of_life/lifelib/rundemos.hpp @@ -0,0 +1,5 @@ + +size_t run_glider_demo_game(); //size_t n, size_t w, size_t h, x, y. + + +