From 544e3068e23ef72aa10304e56ec129dd6505fc18 Mon Sep 17 00:00:00 2001 From: dasm Date: Mon, 10 Nov 2025 12:18:30 -0800 Subject: [PATCH 1/2] Add benchmarks for some of the basic operations on some of the basic types. --- benchmarks/simple_benchmarks.py | 126 ++++++++++++++++++++++++++++++++ benchmarks/utils.py | 28 +++++++ 2 files changed, 154 insertions(+) create mode 100644 benchmarks/simple_benchmarks.py create mode 100644 benchmarks/utils.py diff --git a/benchmarks/simple_benchmarks.py b/benchmarks/simple_benchmarks.py new file mode 100644 index 00000000..95dd57f3 --- /dev/null +++ b/benchmarks/simple_benchmarks.py @@ -0,0 +1,126 @@ +"""Benchmarks for basic operations of a number of flint types.""" + +import timeit +from flint import arb, fmpz, fmpq, acb +from utils import run_all_benchmarks, print_benchmark_results + +NUM_EXECUTIONS = 10000000 + + +def benchmark_arb_addition(num_executions = NUM_EXECUTIONS): + """Simple benchmark for adding two arbs.""" + a = arb(1, 2) + b = arb.pi() + + return timeit.timeit(lambda: a + b, number=num_executions) + + +def benchmark_arb_multiplication(num_executions = NUM_EXECUTIONS): + """Simple benchmark for multiplying two arbs.""" + a = arb(1, 2) + b = arb.pi() + + return timeit.timeit(lambda: a * b, number=num_executions) + + +def benchmark_arb_contains(num_executions = NUM_EXECUTIONS): + """Simple benchmark for comparing two arbs.""" + a = arb(1, 2) + b = arb.pi() + + def to_time(): + first = a in b + second = a in a + return first, second + + return timeit.timeit(to_time, number=num_executions) + + +def benchmark_fmpz_addition(num_executions = NUM_EXECUTIONS): + """Simple benchmark for adding two fmpzs.""" + a = fmpz(1) + b = fmpz(6) + + return timeit.timeit(lambda: a + b, number=num_executions) + + +def benchmark_fmpz_multiplication(num_executions = NUM_EXECUTIONS): + """Simple benchmark for multiplying two fmpzs.""" + a = fmpz(1) + b = fmpz(6) + + return timeit.timeit(lambda: a * b, number=num_executions) + + +def benchmark_fmpz_eq(num_executions = NUM_EXECUTIONS): + """Simple benchmark for equality on two fmpzs.""" + a = fmpz(1) + b = fmpz(6) + + def to_time(): + first = a == b + second = a == a + return first, second + + return timeit.timeit(to_time, number=num_executions) + + +def benchmark_fmpq_addition(num_executions = NUM_EXECUTIONS): + """Simple benchmark for adding two fmpqs.""" + a = fmpq(1, 2) + b = fmpq(15, 7) + + return timeit.timeit(lambda: a + b, number=num_executions) + + +def benchmark_fmpq_multiplication(num_executions = NUM_EXECUTIONS): + """Simple benchmark for multiplying two fmpqs.""" + a = fmpq(1, 2) + b = fmpq(15, 7) + + return timeit.timeit(lambda: a * b, number=num_executions) + + +def benchmark_fmpq_eq(num_executions = NUM_EXECUTIONS): + """Simple benchmark for equality on two fmpqs.""" + a = fmpq(1, 2) + b = fmpq(15, 7) + + def to_time(): + first = a == b + second = a == a + return first, second + + return timeit.timeit(to_time, number=num_executions) + + +def benchmark_acb_addition(num_executions = NUM_EXECUTIONS): + """Simple benchmark for adding two acbs.""" + a = acb(1 + 3j) + b = acb.pi() + + return timeit.timeit(lambda: a + b, number=num_executions) + + +def benchmark_acb_multiplication(num_executions = NUM_EXECUTIONS): + """Simple benchmark for multiplying two acbs.""" + a = acb(1 + 3j) + b = acb.pi() + + return timeit.timeit(lambda: a * b, number=num_executions) + + +def benchmark_acb_eq(num_executions = NUM_EXECUTIONS): + """Simple benchmark for equality on two acbs.""" + a = acb(1 + 3j) + b = acb.pi() + + def to_time(): + first = a in b + second = a in a + return first, second + + return timeit.timeit(to_time, number=num_executions) + +if __name__ == "__main__": + print_benchmark_results(run_all_benchmarks(__name__)) \ No newline at end of file diff --git a/benchmarks/utils.py b/benchmarks/utils.py new file mode 100644 index 00000000..80bb804f --- /dev/null +++ b/benchmarks/utils.py @@ -0,0 +1,28 @@ +"""Utility functions for benchmarking.""" + +import inspect +import sys + + +def get_all_benchmarks(modulename): + """Returns a list of all functions in the current module named 'benchmark_*'.""" + return [ + obj + for name, obj in inspect.getmembers(sys.modules[modulename]) + if inspect.isfunction(obj) and name.startswith("benchmark_") + ] + + +def run_all_benchmarks(modulename): + """Runs all benchmarks in the current file and returns their times.""" + return { + benchmark.__name__[len("benchmark_"):]: benchmark() + for benchmark in get_all_benchmarks(modulename) + } + +def print_benchmark_results(results): + """Pretty-prints a set of benchmark results.""" + print(results) + longest_name = max(len(name) for name in results.keys()) + for name, time in results.items(): + print(f"{name:<{longest_name+2}} | {time:>10.5f}") From f3b2c958c78a71eb50e4c8e54d535b1988d175d9 Mon Sep 17 00:00:00 2001 From: dasm Date: Tue, 25 Nov 2025 11:08:27 -0800 Subject: [PATCH 2/2] Use pyperf. --- benchmarks/simple_benchmarks.py | 271 +++++++++++++++++++------------- benchmarks/utils.py | 15 -- 2 files changed, 164 insertions(+), 122 deletions(-) diff --git a/benchmarks/simple_benchmarks.py b/benchmarks/simple_benchmarks.py index 95dd57f3..ec3e4744 100644 --- a/benchmarks/simple_benchmarks.py +++ b/benchmarks/simple_benchmarks.py @@ -1,126 +1,183 @@ -"""Benchmarks for basic operations of a number of flint types.""" +"""Benchmarks for basic operations of a number of flint types. -import timeit -from flint import arb, fmpz, fmpq, acb -from utils import run_all_benchmarks, print_benchmark_results +These benchmarks are written using pyperf, and can be run by -NUM_EXECUTIONS = 10000000 +python benchmarks/simple_benchmarks.py +Since each benchmark is very short ("microbenchmarks"), they may +require an increased number of samples to produce statistically +significant results (via the --values flag). +""" -def benchmark_arb_addition(num_executions = NUM_EXECUTIONS): - """Simple benchmark for adding two arbs.""" - a = arb(1, 2) - b = arb.pi() +import pyperf +from utils import get_all_benchmarks - return timeit.timeit(lambda: a + b, number=num_executions) +NUM_EXECUTIONS = 10000000 -def benchmark_arb_multiplication(num_executions = NUM_EXECUTIONS): +def benchmark_arb_addition(runner: pyperf.Runner): + """Simple benchmark for adding two arbs.""" + runner.timeit( + name="arb addition", + setup=[ + "from flint import arb", + "a = arb(1, 2)", + "b = arb.pi()", + ], + stmt="a + b" + ) + + +def benchmark_arb_multiplication(runner: pyperf.Runner): """Simple benchmark for multiplying two arbs.""" - a = arb(1, 2) - b = arb.pi() - - return timeit.timeit(lambda: a * b, number=num_executions) - - -def benchmark_arb_contains(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="arb multiplication", + setup=[ + "from flint import arb", + "a = arb(1, 2)", + "b = arb.pi()", + ], + stmt="a * b" + ) + + +def benchmark_arb_contains(runner: pyperf.Runner): """Simple benchmark for comparing two arbs.""" - a = arb(1, 2) - b = arb.pi() - - def to_time(): - first = a in b - second = a in a - return first, second - - return timeit.timeit(to_time, number=num_executions) - - -def benchmark_fmpz_addition(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="arb contains", + setup=[ + "from flint import arb", + "a = arb(1, 2)", + "b = arb.pi()", + ], + stmt=[ + "a in b", + "b in a", + ] + ) + + +def benchmark_fmpz_addition(runner: pyperf.Runner): """Simple benchmark for adding two fmpzs.""" - a = fmpz(1) - b = fmpz(6) - - return timeit.timeit(lambda: a + b, number=num_executions) - - -def benchmark_fmpz_multiplication(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="fmpz addition", + setup=[ + "from flint import fmpz", + "a = fmpz(1)", + "b = fmpz(6)", + ], + stmt="a + b" + ) + +def benchmark_fmpz_multiplication(runner: pyperf.Runner): """Simple benchmark for multiplying two fmpzs.""" - a = fmpz(1) - b = fmpz(6) - - return timeit.timeit(lambda: a * b, number=num_executions) - - -def benchmark_fmpz_eq(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="fmpz multiplication", + setup=[ + "from flint import fmpz", + "a = fmpz(1)", + "b = fmpz(6)", + ], + stmt="a * b" + ) + + +def benchmark_fmpz_eq(runner: pyperf.Runner): """Simple benchmark for equality on two fmpzs.""" - a = fmpz(1) - b = fmpz(6) - - def to_time(): - first = a == b - second = a == a - return first, second - - return timeit.timeit(to_time, number=num_executions) - - -def benchmark_fmpq_addition(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="fmpz equality", + setup=[ + "from flint import fmpz", + "a = fmpz(1)", + "b = fmpz(6)", + ], + stmt=["a == b", "b == a"] + ) + + +def benchmark_fmpq_addition(runner: pyperf.Runner): """Simple benchmark for adding two fmpqs.""" - a = fmpq(1, 2) - b = fmpq(15, 7) - - return timeit.timeit(lambda: a + b, number=num_executions) - - -def benchmark_fmpq_multiplication(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="fmpq addition", + setup=[ + "from flint import fmpq", + "a = fmpq(1, 2)", + "b = fmpq(15, 7)", + ], + stmt="a + b" + ) + + +def benchmark_fmpq_multiplication(runner: pyperf.Runner): """Simple benchmark for multiplying two fmpqs.""" - a = fmpq(1, 2) - b = fmpq(15, 7) - - return timeit.timeit(lambda: a * b, number=num_executions) - - -def benchmark_fmpq_eq(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="fmpq multiplication", + setup=[ + "from flint import fmpq", + "a = fmpq(1, 2)", + "b = fmpq(15, 7)", + ], + stmt="a * b" + ) + + +def benchmark_fmpq_eq(runner: pyperf.Runner): """Simple benchmark for equality on two fmpqs.""" - a = fmpq(1, 2) - b = fmpq(15, 7) - - def to_time(): - first = a == b - second = a == a - return first, second - - return timeit.timeit(to_time, number=num_executions) - - -def benchmark_acb_addition(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="fmpq equality", + setup=[ + "from flint import fmpq", + "a = fmpq(1, 2)", + "b = fmpq(15, 7)", + ], + stmt=["a == b", "b == a"] + ) + + +def benchmark_acb_addition(runner: pyperf.Runner): """Simple benchmark for adding two acbs.""" - a = acb(1 + 3j) - b = acb.pi() - - return timeit.timeit(lambda: a + b, number=num_executions) - - -def benchmark_acb_multiplication(num_executions = NUM_EXECUTIONS): + runner.timeit( + name="acb addition", + setup=[ + "from flint import acb", + "a = acb(1 + 3j)", + "b = acb.pi()", + ], + stmt="a + b" + ) + + +def benchmark_acb_multiplication(runner: pyperf.Runner): """Simple benchmark for multiplying two acbs.""" - a = acb(1 + 3j) - b = acb.pi() - - return timeit.timeit(lambda: a * b, number=num_executions) - - -def benchmark_acb_eq(num_executions = NUM_EXECUTIONS): - """Simple benchmark for equality on two acbs.""" - a = acb(1 + 3j) - b = acb.pi() - - def to_time(): - first = a in b - second = a in a - return first, second - - return timeit.timeit(to_time, number=num_executions) + runner.timeit( + name="acb multiplication", + setup=[ + "from flint import acb", + "a = acb(1 + 3j)", + "b = acb.pi()", + ], + stmt="a * b" + ) + + +def benchmark_acb_eq(runner: pyperf.Runner): + """Simple benchmark for containment on two acbs.""" + runner.timeit( + name="acb contains", + setup=[ + "from flint import acb", + "a = acb(1 + 3j)", + "b = acb.pi()", + ], + stmt=["a in b", "b in a"] + ) + +def main(): + """Run all the benchmarks.""" + runner = pyperf.Runner() + benchmarks = get_all_benchmarks(__name__) + for benchmark in benchmarks: + benchmark(runner) if __name__ == "__main__": - print_benchmark_results(run_all_benchmarks(__name__)) \ No newline at end of file + main() diff --git a/benchmarks/utils.py b/benchmarks/utils.py index 80bb804f..4d01282b 100644 --- a/benchmarks/utils.py +++ b/benchmarks/utils.py @@ -11,18 +11,3 @@ def get_all_benchmarks(modulename): for name, obj in inspect.getmembers(sys.modules[modulename]) if inspect.isfunction(obj) and name.startswith("benchmark_") ] - - -def run_all_benchmarks(modulename): - """Runs all benchmarks in the current file and returns their times.""" - return { - benchmark.__name__[len("benchmark_"):]: benchmark() - for benchmark in get_all_benchmarks(modulename) - } - -def print_benchmark_results(results): - """Pretty-prints a set of benchmark results.""" - print(results) - longest_name = max(len(name) for name in results.keys()) - for name, time in results.items(): - print(f"{name:<{longest_name+2}} | {time:>10.5f}")