benchmarks: add pyperformance workloads (#8633)

Add dependency-free CPU-bound workloads adapted from pyperformance 1.14.0 to cover interpreter, standard-library serialization, regex, numeric, and garbage-collection paths under the existing Criterion and CodSpeed harnesses. Keep inputs deterministic and scale expensive cases for simulation while checking that benchmark execution does not leak interpreter state.

Assisted-by: Codex:gpt-5.6-sol
This commit is contained in:
Lee Dogeon authored and GitHub committed 2026-09-01 10:03:29 +09:00
1 parent 5b5f4c3715
commit 7ec833336d
17 files changed
+1821

No files matched your search

+4
View File
@@ -97,3 +97,7 @@ rustflags = [
## Benchmark source
- <https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/nbody-python3-2.html>
- The workloads marked as adapted from pyperformance come from
[python/pyperformance 1.14.0](https://github.com/python/pyperformance/tree/1.14.0/pyperformance/data-files/benchmarks).
Their `pyperf` runners and internal timers are removed so Criterion is the only measurement
harness, and some input sizes are reduced for CodSpeed simulation.
+34
View File
@@ -0,0 +1,34 @@
# Adapted from python/pyperformance 1.14.0 (base64).
"""Exercise the base64 module's primary public APIs."""
import base64
import random
random_source = random.Random(12345)
data_tiny = random_source.randbytes(20)
data_small = random_source.randbytes(127)
data_medium = random_source.randbytes(3072)
data_large = random_source.randbytes(9000)
cases = (
(data_tiny, 45),
(data_small, 7),
(data_medium, 1),
(data_large, 1),
)
def exercise(encode, decode):
for data, count in cases:
encoded = encode(data)
for _ in range(count):
assert decode(encode(data)) == data
assert decode(encoded) == data
exercise(base64.b64encode, base64.b64decode)
exercise(base64.urlsafe_b64encode, base64.urlsafe_b64decode)
exercise(base64.b32encode, base64.b32decode)
exercise(base64.b16encode, base64.b16decode)
exercise(base64.a85encode, base64.a85decode)
exercise(base64.b85encode, base64.b85decode)
+42
View File
@@ -0,0 +1,42 @@
# Adapted from python/pyperformance 1.14.0 (decimal_factorial).
"""
Calculate `factorial` using the decimal module.
- 2024-06-14: Michael Droettboom copied this from
Modules/_decimal/tests/bench.py in the CPython source and adapted to use
pyperf.
"""
# Original copyright notice in CPython source:
#
# Copyright (C) 2001-2012 Python Software Foundation. All Rights Reserved.
# Modified and extended by Stefan Krah.
#
import decimal
def factorial(n, m):
if n > m:
return factorial(m, n)
elif m == 0:
return 1
elif n == m:
return n
else:
return factorial(n, (n + m) // 2) * factorial((n + m) // 2 + 1, m)
def bench_decimal_factorial():
# The upstream 10,000! and 100,000! inputs are reduced for simulation,
# while retaining enough precision to exercise large Decimal arithmetic.
with decimal.localcontext() as context:
context.prec = 3000
result = factorial(decimal.Decimal(1000), 0)
assert len(result.as_tuple().digits) == 2568
bench_decimal_factorial()
+71
View File
@@ -0,0 +1,71 @@
# Adapted from python/pyperformance 1.14.0 (deepcopy).
"""
Benchmark to measure performance of the python builtin method copy.deepcopy
Performance is tested on a nested dictionary and a dataclass
Author: Pieter Eendebak
"""
import copy
from dataclasses import dataclass
@dataclass
class A:
string: str
lst: list
boolean: bool
def benchmark_reduce(n):
""" Benchmark where the __reduce__ functionality is used """
class C(object):
def __init__(self):
self.a = 1
self.b = 2
def __reduce__(self):
return (C, (), self.__dict__)
def __setstate__(self, state):
self.__dict__.update(state)
c = C()
for _ in range(n):
_ = copy.deepcopy(c)
def benchmark_memo(n):
""" Benchmark where the memo functionality is used """
A = [1] * 100
data = {'a': (A, A, A), 'b': [A] * 100}
for _ in range(n):
_ = copy.deepcopy(data)
def benchmark(n):
""" Benchmark on some standard data types """
a = {
'list': [1, 2, 3, 43],
't': (1 ,2, 3),
'str': 'hello',
'subdict': {'a': True}
}
dc = A('hello', [1, 2, 3], True)
for _ in range(n):
for _ in range(30):
_ = copy.deepcopy(a)
for s in ['red', 'blue', 'green']:
dc.string = s
for value in range(5):
dc.lst[0] = value
for b in [True, False]:
dc.boolean = b
_ = copy.deepcopy(dc)
benchmark(1)
benchmark_reduce(5)
benchmark_memo(1)
+628
View File
@@ -0,0 +1,628 @@
# Adapted from python/pyperformance 1.14.0 (deltablue).
"""
deltablue.py
============
Ported for the PyPy project.
Contributed by Daniel Lindsley
This implementation of the DeltaBlue benchmark was directly ported
from the `V8's source code`_, which was in turn derived
from the Smalltalk implementation by John Maloney and Mario
Wolczko. The original Javascript implementation was licensed under the GPL.
It's been updated in places to be more idiomatic to Python (for loops over
collections, a couple magic methods, ``OrderedCollection`` being a list & things
altering those collections changed to the builtin methods) but largely retains
the layout & logic from the original. (Ugh.)
.. _`V8's source code`: (https://github.com/v8/v8/blob/master/benchmarks/deltablue.js)
"""
# The JS variant implements "OrderedCollection", which basically completely
# overlaps with ``list``. So we'll cheat. :D
class OrderedCollection(list):
pass
class Strength(object):
REQUIRED = None
STRONG_PREFERRED = None
PREFERRED = None
STRONG_DEFAULT = None
NORMAL = None
WEAK_DEFAULT = None
WEAKEST = None
def __init__(self, strength, name):
super(Strength, self).__init__()
self.strength = strength
self.name = name
@classmethod
def stronger(cls, s1, s2):
return s1.strength < s2.strength
@classmethod
def weaker(cls, s1, s2):
return s1.strength > s2.strength
@classmethod
def weakest_of(cls, s1, s2):
if cls.weaker(s1, s2):
return s1
return s2
@classmethod
def strongest(cls, s1, s2):
if cls.stronger(s1, s2):
return s1
return s2
def next_weaker(self):
strengths = {
0: self.__class__.WEAKEST,
1: self.__class__.WEAK_DEFAULT,
2: self.__class__.NORMAL,
3: self.__class__.STRONG_DEFAULT,
4: self.__class__.PREFERRED,
# TODO: This looks like a bug in the original code. Shouldn't this be
# ``STRONG_PREFERRED? Keeping for porting sake...
5: self.__class__.REQUIRED,
}
return strengths[self.strength]
# This is a terrible pattern IMO, but true to the original JS implementation.
Strength.REQUIRED = Strength(0, "required")
Strength.STRONG_PREFERRED = Strength(1, "strongPreferred")
Strength.PREFERRED = Strength(2, "preferred")
Strength.STRONG_DEFAULT = Strength(3, "strongDefault")
Strength.NORMAL = Strength(4, "normal")
Strength.WEAK_DEFAULT = Strength(5, "weakDefault")
Strength.WEAKEST = Strength(6, "weakest")
class Constraint(object):
def __init__(self, strength):
super(Constraint, self).__init__()
self.strength = strength
def add_constraint(self):
global planner
self.add_to_graph()
planner.incremental_add(self)
def satisfy(self, mark):
global planner
self.choose_method(mark)
if not self.is_satisfied():
if self.strength == Strength.REQUIRED:
print('Could not satisfy a required constraint!')
return None
self.mark_inputs(mark)
out = self.output()
overridden = out.determined_by
if overridden is not None:
overridden.mark_unsatisfied()
out.determined_by = self
if not planner.add_propagate(self, mark):
print('Cycle encountered')
out.mark = mark
return overridden
def destroy_constraint(self):
global planner
if self.is_satisfied():
planner.incremental_remove(self)
else:
self.remove_from_graph()
def is_input(self):
return False
class UrnaryConstraint(Constraint):
def __init__(self, v, strength):
super(UrnaryConstraint, self).__init__(strength)
self.my_output = v
self.satisfied = False
self.add_constraint()
def add_to_graph(self):
self.my_output.add_constraint(self)
self.satisfied = False
def choose_method(self, mark):
if self.my_output.mark != mark and \
Strength.stronger(self.strength, self.my_output.walk_strength):
self.satisfied = True
else:
self.satisfied = False
def is_satisfied(self):
return self.satisfied
def mark_inputs(self, mark):
# No-ops.
pass
def output(self):
# Ugh. Keeping it for consistency with the original. So much for
# "we're all adults here"...
return self.my_output
def recalculate(self):
self.my_output.walk_strength = self.strength
self.my_output.stay = not self.is_input()
if self.my_output.stay:
self.execute()
def mark_unsatisfied(self):
self.satisfied = False
def inputs_known(self, mark):
return True
def remove_from_graph(self):
if self.my_output is not None:
self.my_output.remove_constraint(self)
self.satisfied = False
class StayConstraint(UrnaryConstraint):
def __init__(self, v, string):
super(StayConstraint, self).__init__(v, string)
def execute(self):
# The methods, THEY DO NOTHING.
pass
class EditConstraint(UrnaryConstraint):
def __init__(self, v, string):
super(EditConstraint, self).__init__(v, string)
def is_input(self):
return True
def execute(self):
# This constraint also does nothing.
pass
class Direction(object):
# Hooray for things that ought to be structs!
NONE = 0
FORWARD = 1
BACKWARD = -1
class BinaryConstraint(Constraint):
def __init__(self, v1, v2, strength):
super(BinaryConstraint, self).__init__(strength)
self.v1 = v1
self.v2 = v2
self.direction = Direction.NONE
self.add_constraint()
def choose_method(self, mark):
if self.v1.mark == mark:
if self.v2.mark != mark and Strength.stronger(self.strength, self.v2.walk_strength):
self.direction = Direction.FORWARD
else:
self.direction = Direction.BACKWARD
if self.v2.mark == mark:
if self.v1.mark != mark and Strength.stronger(self.strength, self.v1.walk_strength):
self.direction = Direction.BACKWARD
else:
self.direction = Direction.NONE
if Strength.weaker(self.v1.walk_strength, self.v2.walk_strength):
if Strength.stronger(self.strength, self.v1.walk_strength):
self.direction = Direction.BACKWARD
else:
self.direction = Direction.NONE
else:
if Strength.stronger(self.strength, self.v2.walk_strength):
self.direction = Direction.FORWARD
else:
self.direction = Direction.BACKWARD
def add_to_graph(self):
self.v1.add_constraint(self)
self.v2.add_constraint(self)
self.direction = Direction.NONE
def is_satisfied(self):
return self.direction != Direction.NONE
def mark_inputs(self, mark):
self.input().mark = mark
def input(self):
if self.direction == Direction.FORWARD:
return self.v1
return self.v2
def output(self):
if self.direction == Direction.FORWARD:
return self.v2
return self.v1
def recalculate(self):
ihn = self.input()
out = self.output()
out.walk_strength = Strength.weakest_of(
self.strength, ihn.walk_strength)
out.stay = ihn.stay
if out.stay:
self.execute()
def mark_unsatisfied(self):
self.direction = Direction.NONE
def inputs_known(self, mark):
i = self.input()
return i.mark == mark or i.stay or i.determined_by is None
def remove_from_graph(self):
if self.v1 is not None:
self.v1.remove_constraint(self)
if self.v2 is not None:
self.v2.remove_constraint(self)
self.direction = Direction.NONE
class ScaleConstraint(BinaryConstraint):
def __init__(self, src, scale, offset, dest, strength):
self.direction = Direction.NONE
self.scale = scale
self.offset = offset
super(ScaleConstraint, self).__init__(src, dest, strength)
def add_to_graph(self):
super(ScaleConstraint, self).add_to_graph()
self.scale.add_constraint(self)
self.offset.add_constraint(self)
def remove_from_graph(self):
super(ScaleConstraint, self).remove_from_graph()
if self.scale is not None:
self.scale.remove_constraint(self)
if self.offset is not None:
self.offset.remove_constraint(self)
def mark_inputs(self, mark):
super(ScaleConstraint, self).mark_inputs(mark)
self.scale.mark = mark
self.offset.mark = mark
def execute(self):
if self.direction == Direction.FORWARD:
self.v2.value = self.v1.value * self.scale.value + self.offset.value
else:
self.v1.value = (
self.v2.value - self.offset.value) / self.scale.value
def recalculate(self):
ihn = self.input()
out = self.output()
out.walk_strength = Strength.weakest_of(
self.strength, ihn.walk_strength)
out.stay = ihn.stay and self.scale.stay and self.offset.stay
if out.stay:
self.execute()
class EqualityConstraint(BinaryConstraint):
def execute(self):
self.output().value = self.input().value
class Variable(object):
def __init__(self, name, initial_value=0):
super(Variable, self).__init__()
self.name = name
self.value = initial_value
self.constraints = OrderedCollection()
self.determined_by = None
self.mark = 0
self.walk_strength = Strength.WEAKEST
self.stay = True
def __repr__(self):
# To make debugging this beast from pdb easier...
return '<Variable: %s - %s>' % (
self.name,
self.value
)
def add_constraint(self, constraint):
self.constraints.append(constraint)
def remove_constraint(self, constraint):
self.constraints.remove(constraint)
if self.determined_by == constraint:
self.determined_by = None
class Planner(object):
def __init__(self):
super(Planner, self).__init__()
self.current_mark = 0
def incremental_add(self, constraint):
mark = self.new_mark()
overridden = constraint.satisfy(mark)
while overridden is not None:
overridden = overridden.satisfy(mark)
def incremental_remove(self, constraint):
out = constraint.output()
constraint.mark_unsatisfied()
constraint.remove_from_graph()
unsatisfied = self.remove_propagate_from(out)
strength = Strength.REQUIRED
# Do-while, the Python way.
repeat = True
while repeat:
for u in unsatisfied:
if u.strength == strength:
self.incremental_add(u)
strength = strength.next_weaker()
repeat = strength != Strength.WEAKEST
def new_mark(self):
self.current_mark += 1
return self.current_mark
def make_plan(self, sources):
mark = self.new_mark()
plan = Plan()
todo = sources
while len(todo):
c = todo.pop(0)
if c.output().mark != mark and c.inputs_known(mark):
plan.add_constraint(c)
c.output().mark = mark
self.add_constraints_consuming_to(c.output(), todo)
return plan
def extract_plan_from_constraints(self, constraints):
sources = OrderedCollection()
for c in constraints:
if c.is_input() and c.is_satisfied():
sources.append(c)
return self.make_plan(sources)
def add_propagate(self, c, mark):
todo = OrderedCollection()
todo.append(c)
while len(todo):
d = todo.pop(0)
if d.output().mark == mark:
self.incremental_remove(c)
return False
d.recalculate()
self.add_constraints_consuming_to(d.output(), todo)
return True
def remove_propagate_from(self, out):
out.determined_by = None
out.walk_strength = Strength.WEAKEST
out.stay = True
unsatisfied = OrderedCollection()
todo = OrderedCollection()
todo.append(out)
while len(todo):
v = todo.pop(0)
for c in v.constraints:
if not c.is_satisfied():
unsatisfied.append(c)
determining = v.determined_by
for c in v.constraints:
if c != determining and c.is_satisfied():
c.recalculate()
todo.append(c.output())
return unsatisfied
def add_constraints_consuming_to(self, v, coll):
determining = v.determined_by
cc = v.constraints
for c in cc:
if c != determining and c.is_satisfied():
# I guess we're just updating a reference (``coll``)? Seems
# inconsistent with the rest of the implementation, where they
# return the lists...
coll.append(c)
class Plan(object):
def __init__(self):
super(Plan, self).__init__()
self.v = OrderedCollection()
def add_constraint(self, c):
self.v.append(c)
def __len__(self):
return len(self.v)
def __getitem__(self, index):
return self.v[index]
def execute(self):
for c in self.v:
c.execute()
# Main
def chain_test(n):
"""
This is the standard DeltaBlue benchmark. A long chain of equality
constraints is constructed with a stay constraint on one end. An
edit constraint is then added to the opposite end and the time is
measured for adding and removing this constraint, and extracting
and executing a constraint satisfaction plan. There are two cases.
In case 1, the added constraint is stronger than the stay
constraint and values must propagate down the entire length of the
chain. In case 2, the added constraint is weaker than the stay
constraint so it cannot be accomodated. The cost in this case is,
of course, very low. Typical situations lie somewhere between these
two extremes.
"""
global planner
planner = Planner()
prev, first, last = None, None, None
# We need to go up to n inclusively.
for i in range(n + 1):
name = "v%s" % i
v = Variable(name)
if prev is not None:
EqualityConstraint(prev, v, Strength.REQUIRED)
if i == 0:
first = v
if i == n:
last = v
prev = v
StayConstraint(last, Strength.STRONG_DEFAULT)
edit = EditConstraint(first, Strength.PREFERRED)
edits = OrderedCollection()
edits.append(edit)
plan = planner.extract_plan_from_constraints(edits)
for i in range(100):
first.value = i
plan.execute()
if last.value != i:
print("Chain test failed.")
def projection_test(n):
"""
This test constructs a two sets of variables related to each
other by a simple linear transformation (scale and offset). The
time is measured to change a variable on either side of the
mapping and to change the scale and offset factors.
"""
global planner
planner = Planner()
scale = Variable("scale", 10)
offset = Variable("offset", 1000)
src = None
dests = OrderedCollection()
for i in range(n):
src = Variable("src%s" % i, i)
dst = Variable("dst%s" % i, i)
dests.append(dst)
StayConstraint(src, Strength.NORMAL)
ScaleConstraint(src, scale, offset, dst, Strength.REQUIRED)
change(src, 17)
if dst.value != 1170:
print("Projection 1 failed")
change(dst, 1050)
if src.value != 5:
print("Projection 2 failed")
change(scale, 5)
for i in range(n - 1):
if dests[i].value != (i * 5 + 1000):
print("Projection 3 failed")
change(offset, 2000)
for i in range(n - 1):
if dests[i].value != (i * 5 + 2000):
print("Projection 4 failed")
def change(v, new_value):
global planner
edit = EditConstraint(v, Strength.PREFERRED)
edits = OrderedCollection()
edits.append(edit)
plan = planner.extract_plan_from_constraints(edits)
for i in range(10):
v.value = new_value
plan.execute()
edit.destroy_constraint()
# HOORAY FOR GLOBALS... Oh wait.
# In spirit of the original, we'll keep it, but ugh.
planner = None
def delta_blue(n):
chain_test(n)
projection_test(n)
delta_blue(100)
+54
View File
@@ -0,0 +1,54 @@
# Adapted from python/pyperformance 1.14.0 (float).
"""
Artificial, floating point-heavy benchmark originally used by Factor.
"""
from math import sin, cos, sqrt
POINTS = 100000
class Point(object):
__slots__ = ('x', 'y', 'z')
def __init__(self, i):
self.x = x = sin(i)
self.y = cos(i) * 3
self.z = (x * x) / 2
def __repr__(self):
return "<Point: x=%s, y=%s, z=%s>" % (self.x, self.y, self.z)
def normalize(self):
x = self.x
y = self.y
z = self.z
norm = sqrt(x * x + y * y + z * z)
self.x /= norm
self.y /= norm
self.z /= norm
def maximize(self, other):
self.x = self.x if self.x > other.x else other.x
self.y = self.y if self.y > other.y else other.y
self.z = self.z if self.z > other.z else other.z
return self
def maximize(points):
next = points[0]
for p in points[1:]:
next = next.maximize(p)
return next
def benchmark(n):
points = [None] * n
for i in range(n):
points[i] = Point(i)
for p in points:
p.normalize()
return maximize(points)
result = benchmark(10000)
assert result.x <= 1.0 and result.y <= 1.0 and result.z <= 1.0
+55
View File
@@ -0,0 +1,55 @@
# Adapted from python/pyperformance 1.14.0 (gc_collect).
import gc
CYCLES = 100
LINKS = 20
class Node:
def __init__(self):
self.next = None
self.prev = None
def link_next(self, next):
self.next = next
self.next.prev = self
def create_cycle(node, n_links):
"""Create a cycle of n_links nodes, starting with node."""
if n_links == 0:
return
current = node
for i in range(n_links):
next_node = Node()
current.link_next(next_node)
current = next_node
current.link_next(node)
def create_gc_cycles(n_cycles, n_links):
"""Create n_cycles cycles n_links+1 nodes each."""
cycles = []
for _ in range(n_cycles):
node = Node()
cycles.append(node)
create_cycle(node, n_links)
return cycles
def benchamark_collection(loops, cycles, links):
for _ in range(loops):
gc.collect()
all_cycles = create_gc_cycles(cycles, links)
# Main loop to measure
del all_cycles
collected = gc.collect()
assert collected is None or collected >= cycles * (links + 1)
benchamark_collection(1, CYCLES, LINKS)
+28
View File
@@ -0,0 +1,28 @@
# Adapted from python/pyperformance 1.14.0 (gc_traversal).
import gc
N_LEVELS = 1000
def create_recursive_containers(n_levels):
current_list = []
for n in range(n_levels):
new_list = [None] * n
for index in range(n):
new_list[index] = current_list
current_list = new_list
return current_list
def benchamark_collection(loops, n_levels):
all_cycles = create_recursive_containers(n_levels)
for _ in range(loops):
gc.collect()
# Main loop to measure
collected = gc.collect()
assert collected is None or collected == 0
benchamark_collection(1, N_LEVELS)
+42
View File
@@ -0,0 +1,42 @@
# Adapted from python/pyperformance 1.14.0 (generators).
"""
Benchmark recursive generators implemented in python
by traversing a binary tree.
Author: Kumar Aditya
"""
from __future__ import annotations
from collections.abc import Iterator
class Tree:
def __init__(self, left: Tree | None, value: int, right: Tree | None) -> None:
self.left = left
self.value = value
self.right = right
def __iter__(self) -> Iterator[int]:
if self.left:
yield from self.left
yield self.value
if self.right:
yield from self.right
def tree(input: range) -> Tree | None:
n = len(input)
if n == 0:
return None
i = n // 2
return Tree(tree(input[:i]), input[i], tree(input[i + 1:]))
def bench_generators(loops: int) -> None:
assert list(tree(range(10))) == list(range(10))
range_it = range(loops)
iterable = tree(range(100000))
for _ in range_it:
for _ in iterable:
pass
bench_generators(1)
+28
View File
@@ -0,0 +1,28 @@
# Adapted from python/pyperformance 1.14.0 (json_dumps).
import json
empty = ({}, 2000)
simple_data = {
"key1": 0,
"key2": True,
"key3": "value",
"key4": "foo",
"key5": "string",
}
simple = (simple_data, 1000)
nested_data = {
"key1": 0,
"key2": simple_data,
"key3": "value",
"key4": simple_data,
"key5": simple_data,
"key": "ąćż",
}
nested = (nested_data, 1000)
huge = ([nested_data] * 1000, 1)
for obj, count in (empty, simple, nested, huge):
for _ in range(count):
json.dumps(obj)
+55
View File
@@ -0,0 +1,55 @@
# Adapted from python/pyperformance 1.14.0 (nqueens).
"""Simple, brute-force N-Queens solver."""
__author__ = "collinwinter@google.com (Collin Winter)"
# Pure-Python implementation of itertools.permutations().
def permutations(iterable, r=None):
"""permutations(range(3), 2) --> (0,1) (0,2) (1,0) (1,2) (2,0) (2,1)"""
pool = tuple(iterable)
n = len(pool)
if r is None:
r = n
indices = list(range(n))
cycles = list(range(n - r + 1, n + 1))[::-1]
yield tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i + 1:] + indices[i:i + 1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield tuple(pool[i] for i in indices[:r])
break
else:
return
# From http://code.activestate.com/recipes/576647/
def n_queens(queen_count):
"""N-Queens solver.
Args:
queen_count: the number of queens to solve for. This is also the
board size.
Yields:
Solutions to the problem. Each yielded value is looks like
(3, 8, 2, 1, 4, ..., 6) where each number is the column position for the
queen, and the index into the tuple indicates the row.
"""
cols = range(queen_count)
for vec in permutations(cols):
if (queen_count == len(set(vec[i] + i for i in cols))
== len(set(vec[i] - i for i in cols))):
yield vec
def bench_n_queens(queen_count):
list(n_queens(queen_count))
bench_n_queens(8)
+33
View File
@@ -0,0 +1,33 @@
# Adapted from python/pyperformance 1.14.0 (pickle_pure_python).
import datetime
import pickle
profile = {
"age": 18,
"birthday": datetime.date(1980, 5, 7),
"country": "BR",
"encrypted_id": "G9urXXAJwjE",
"flags": 412317970704,
"friend_count": 0,
"locale_preference": "pt_BR",
"tags": ["a", "b", "c", "d", "e", "f", "g"],
"username": "collinwinter",
}
record = ([*range(20)], 60)
group = [dict(profile, id=index) for index in range(3)]
list_data = [[list(range(10)), list(range(10))] for _ in range(10)]
dict_data = {key: dict.fromkeys(range(10)) for key in range(100)}
protocol = pickle.HIGHEST_PROTOCOL
dumps = pickle._dumps
for obj, count in (
(profile, 20),
(record, 20),
(group, 20),
(list_data, 10),
(dict_data, 5),
):
for _ in range(count):
dumps(obj, protocol)
+57
View File
@@ -0,0 +1,57 @@
# Adapted from python/pyperformance 1.14.0 (pidigits).
# coding: utf-8
"""
Calculating some of the digits of π.
This benchmark stresses big integer arithmetic.
Adapted from code on:
http://benchmarksgame.alioth.debian.org/
"""
import itertools
DEFAULT_DIGITS = 2000
icount = itertools.count
islice = itertools.islice
def gen_x():
return map(lambda k: (k, 4 * k + 2, 0, 2 * k + 1), icount(1))
def compose(a, b):
aq, ar, as_, at = a
bq, br, bs, bt = b
return (aq * bq,
aq * br + ar * bt,
as_ * bq + at * bs,
as_ * br + at * bt)
def extract(z, j):
q, r, s, t = z
return (q * j + r) // (s * j + t)
def gen_pi_digits():
z = (1, 0, 0, 1)
x = gen_x()
while 1:
y = extract(z, 3)
while y != extract(z, 4):
z = compose(z, next(x))
y = extract(z, 3)
z = compose((10, -10 * y, 0, 1), z)
yield y
def calc_ndigits(n):
return list(islice(gen_pi_digits(), n))
def add_cmdline_args(cmd, args):
cmd.extend(("--digits", str(args.digits)))
digits = calc_ndigits(1000)
assert digits[:10] == [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
+158
View File
@@ -0,0 +1,158 @@
# Adapted from python/pyperformance 1.14.0 (regex_effbot).
"""Benchmarks for Python's regex engine.
These are some of the original benchmarks used to tune Python's regex engine
in 2000 written by Fredrik Lundh. Retreived from
http://mail.python.org/pipermail/python-dev/2000-August/007797.html and
integrated into Unladen Swallow's pyperf.py in 2009 by David Laing.
These benchmarks are of interest since they helped to guide the original
optimization of the sre engine, and we shouldn't necessarily ignore them just
because they're "old".
"""
# Python imports
import re
# Local imports
USE_BYTES = False
def re_compile(s):
if USE_BYTES:
return re.compile(s.encode('latin1'))
else:
return re.compile(s)
# These are the regular expressions to be tested. These sync up,
# index-for-index with the list of strings generated by gen_string_table()
# below.
def gen_regex_table():
return [
re_compile('Python|Perl'),
re_compile('Python|Perl'),
re_compile('(Python|Perl)'),
re_compile('(?:Python|Perl)'),
re_compile('Python'),
re_compile('Python'),
re_compile('.*Python'),
re_compile('.*Python.*'),
re_compile('.*(Python)'),
re_compile('.*(?:Python)'),
re_compile('Python|Perl|Tcl'),
re_compile('Python|Perl|Tcl'),
re_compile('(Python|Perl|Tcl)'),
re_compile('(?:Python|Perl|Tcl)'),
re_compile('(Python)\\1'),
re_compile('(Python)\\1'),
re_compile('([0a-z][a-z0-9]*,)+'),
re_compile('(?:[0a-z][a-z0-9]*,)+'),
re_compile('([a-z][a-z0-9]*,)+'),
re_compile('(?:[a-z][a-z0-9]*,)+'),
re_compile('.*P.*y.*t.*h.*o.*n.*')]
def gen_string_table(n):
"""Generates the list of strings that will be used in the benchmarks.
All strings have repeated prefixes and suffices, and n specifies the
number of repetitions.
"""
strings = []
def append(s):
if USE_BYTES:
strings.append(s.encode('latin1'))
else:
strings.append(s)
append('-' * n + 'Perl' + '-' * n)
append('P' * n + 'Perl' + 'P' * n)
append('-' * n + 'Perl' + '-' * n)
append('-' * n + 'Perl' + '-' * n)
append('-' * n + 'Python' + '-' * n)
append('P' * n + 'Python' + 'P' * n)
append('-' * n + 'Python' + '-' * n)
append('-' * n + 'Python' + '-' * n)
append('-' * n + 'Python' + '-' * n)
append('-' * n + 'Python' + '-' * n)
append('-' * n + 'Perl' + '-' * n)
append('P' * n + 'Perl' + 'P' * n)
append('-' * n + 'Perl' + '-' * n)
append('-' * n + 'Perl' + '-' * n)
append('-' * n + 'PythonPython' + '-' * n)
append('P' * n + 'PythonPython' + 'P' * n)
append('-' * n + 'a5,b7,c9,' + '-' * n)
append('-' * n + 'a5,b7,c9,' + '-' * n)
append('-' * n + 'a5,b7,c9,' + '-' * n)
append('-' * n + 'a5,b7,c9,' + '-' * n)
append('-' * n + 'Python' + '-' * n)
return strings
def init_benchmarks(n_values=None):
"""Initialize the strings we'll run the regexes against.
The strings used in the benchmark are prefixed and suffixed by
strings that are repeated n times.
The sequence n_values contains the values for n.
If n_values is None the values of n from the original benchmark
are used.
The generated list of strings is cached in the string_tables
variable, which is indexed by n.
Returns:
A list of string prefix/suffix lengths.
"""
if n_values is None:
n_values = (0, 5, 50, 250, 1000, 5000, 10000)
string_tables = {n: gen_string_table(n) for n in n_values}
regexs = gen_regex_table()
data = []
for n in n_values:
for id in range(len(regexs)):
regex = regexs[id]
string = string_tables[n][id]
data.append((regex, string))
return data
def bench_regex_effbot(loops):
if bench_regex_effbot.data is None:
bench_regex_effbot.data = init_benchmarks()
data = bench_regex_effbot.data
range_it = range(loops)
search = re.search
for _ in range_it:
# Runs all of the benchmarks for a given value of n.
for regex, string in data:
# search 10 times
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
search(regex, string)
# cached data, generated at the first call
bench_regex_effbot.data = None
def add_cmdline_args(cmd, args):
if args.force_bytes:
cmd.append("--force_bytes")
bench_regex_effbot(1)
+415
View File
@@ -0,0 +1,415 @@
# Adapted from python/pyperformance 1.14.0 (richards).
"""
based on a Java version:
Based on original version written in BCPL by Dr Martin Richards
in 1981 at Cambridge University Computer Laboratory, England
and a C++ version derived from a Smalltalk version written by
L Peter Deutsch.
Java version: Copyright (C) 1995 Sun Microsystems, Inc.
Translation from C++, Mario Wolczko
Outer loop added by Alex Jacoby
"""
# Task IDs
I_IDLE = 1
I_WORK = 2
I_HANDLERA = 3
I_HANDLERB = 4
I_DEVA = 5
I_DEVB = 6
# Packet types
K_DEV = 1000
K_WORK = 1001
# Packet
BUFSIZE = 4
BUFSIZE_RANGE = range(BUFSIZE)
class Packet(object):
def __init__(self, l, i, k):
self.link = l
self.ident = i
self.kind = k
self.datum = 0
self.data = [0] * BUFSIZE
def append_to(self, lst):
self.link = None
if lst is None:
return self
else:
p = lst
next = p.link
while next is not None:
p = next
next = p.link
p.link = self
return lst
# Task Records
class TaskRec(object):
pass
class DeviceTaskRec(TaskRec):
def __init__(self):
self.pending = None
class IdleTaskRec(TaskRec):
def __init__(self):
self.control = 1
self.count = 10000
class HandlerTaskRec(TaskRec):
def __init__(self):
self.work_in = None
self.device_in = None
def workInAdd(self, p):
self.work_in = p.append_to(self.work_in)
return self.work_in
def deviceInAdd(self, p):
self.device_in = p.append_to(self.device_in)
return self.device_in
class WorkerTaskRec(TaskRec):
def __init__(self):
self.destination = I_HANDLERA
self.count = 0
# Task
class TaskState(object):
def __init__(self):
self.packet_pending = True
self.task_waiting = False
self.task_holding = False
def packetPending(self):
self.packet_pending = True
self.task_waiting = False
self.task_holding = False
return self
def waiting(self):
self.packet_pending = False
self.task_waiting = True
self.task_holding = False
return self
def running(self):
self.packet_pending = False
self.task_waiting = False
self.task_holding = False
return self
def waitingWithPacket(self):
self.packet_pending = True
self.task_waiting = True
self.task_holding = False
return self
def isPacketPending(self):
return self.packet_pending
def isTaskWaiting(self):
return self.task_waiting
def isTaskHolding(self):
return self.task_holding
def isTaskHoldingOrWaiting(self):
return self.task_holding or (not self.packet_pending and self.task_waiting)
def isWaitingWithPacket(self):
return self.packet_pending and self.task_waiting and not self.task_holding
tracing = False
layout = 0
def trace(a):
global layout
layout -= 1
if layout <= 0:
print()
layout = 50
print(a, end='')
TASKTABSIZE = 10
class TaskWorkArea(object):
def __init__(self):
self.taskTab = [None] * TASKTABSIZE
self.taskList = None
self.holdCount = 0
self.qpktCount = 0
taskWorkArea = TaskWorkArea()
class Task(TaskState):
def __init__(self, i, p, w, initialState, r):
self.link = taskWorkArea.taskList
self.ident = i
self.priority = p
self.input = w
self.packet_pending = initialState.isPacketPending()
self.task_waiting = initialState.isTaskWaiting()
self.task_holding = initialState.isTaskHolding()
self.handle = r
taskWorkArea.taskList = self
taskWorkArea.taskTab[i] = self
def fn(self, pkt, r):
raise NotImplementedError
def addPacket(self, p, old):
if self.input is None:
self.input = p
self.packet_pending = True
if self.priority > old.priority:
return self
else:
p.append_to(self.input)
return old
def runTask(self):
if self.isWaitingWithPacket():
msg = self.input
self.input = msg.link
if self.input is None:
self.running()
else:
self.packetPending()
else:
msg = None
return self.fn(msg, self.handle)
def waitTask(self):
self.task_waiting = True
return self
def hold(self):
taskWorkArea.holdCount += 1
self.task_holding = True
return self.link
def release(self, i):
t = self.findtcb(i)
t.task_holding = False
if t.priority > self.priority:
return t
else:
return self
def qpkt(self, pkt):
t = self.findtcb(pkt.ident)
taskWorkArea.qpktCount += 1
pkt.link = None
pkt.ident = self.ident
return t.addPacket(pkt, self)
def findtcb(self, id):
t = taskWorkArea.taskTab[id]
if t is None:
raise Exception("Bad task id %d" % id)
return t
# DeviceTask
class DeviceTask(Task):
def __init__(self, i, p, w, s, r):
Task.__init__(self, i, p, w, s, r)
def fn(self, pkt, r):
d = r
assert isinstance(d, DeviceTaskRec)
if pkt is None:
pkt = d.pending
if pkt is None:
return self.waitTask()
else:
d.pending = None
return self.qpkt(pkt)
else:
d.pending = pkt
if tracing:
trace(pkt.datum)
return self.hold()
class HandlerTask(Task):
def __init__(self, i, p, w, s, r):
Task.__init__(self, i, p, w, s, r)
def fn(self, pkt, r):
h = r
assert isinstance(h, HandlerTaskRec)
if pkt is not None:
if pkt.kind == K_WORK:
h.workInAdd(pkt)
else:
h.deviceInAdd(pkt)
work = h.work_in
if work is None:
return self.waitTask()
count = work.datum
if count >= BUFSIZE:
h.work_in = work.link
return self.qpkt(work)
dev = h.device_in
if dev is None:
return self.waitTask()
h.device_in = dev.link
dev.datum = work.data[count]
work.datum = count + 1
return self.qpkt(dev)
# IdleTask
class IdleTask(Task):
def __init__(self, i, p, w, s, r):
Task.__init__(self, i, 0, None, s, r)
def fn(self, pkt, r):
i = r
assert isinstance(i, IdleTaskRec)
i.count -= 1
if i.count == 0:
return self.hold()
elif i.control & 1 == 0:
i.control //= 2
return self.release(I_DEVA)
else:
i.control = i.control // 2 ^ 0xd008
return self.release(I_DEVB)
# WorkTask
A = ord('A')
class WorkTask(Task):
def __init__(self, i, p, w, s, r):
Task.__init__(self, i, p, w, s, r)
def fn(self, pkt, r):
w = r
assert isinstance(w, WorkerTaskRec)
if pkt is None:
return self.waitTask()
if w.destination == I_HANDLERA:
dest = I_HANDLERB
else:
dest = I_HANDLERA
w.destination = dest
pkt.ident = dest
pkt.datum = 0
for i in BUFSIZE_RANGE: # range(BUFSIZE)
w.count += 1
if w.count > 26:
w.count = 1
pkt.data[i] = A + w.count - 1
return self.qpkt(pkt)
def schedule():
t = taskWorkArea.taskList
while t is not None:
if tracing:
print("tcb =", t.ident)
if t.isTaskHoldingOrWaiting():
t = t.link
else:
if tracing:
trace(chr(ord("0") + t.ident))
t = t.runTask()
class Richards(object):
def run(self, iterations):
for i in range(iterations):
taskWorkArea.holdCount = 0
taskWorkArea.qpktCount = 0
IdleTask(I_IDLE, 1, 10000, TaskState().running(), IdleTaskRec())
wkq = Packet(None, 0, K_WORK)
wkq = Packet(wkq, 0, K_WORK)
WorkTask(I_WORK, 1000, wkq, TaskState(
).waitingWithPacket(), WorkerTaskRec())
wkq = Packet(None, I_DEVA, K_DEV)
wkq = Packet(wkq, I_DEVA, K_DEV)
wkq = Packet(wkq, I_DEVA, K_DEV)
HandlerTask(I_HANDLERA, 2000, wkq, TaskState(
).waitingWithPacket(), HandlerTaskRec())
wkq = Packet(None, I_DEVB, K_DEV)
wkq = Packet(wkq, I_DEVB, K_DEV)
wkq = Packet(wkq, I_DEVB, K_DEV)
HandlerTask(I_HANDLERB, 3000, wkq, TaskState(
).waitingWithPacket(), HandlerTaskRec())
wkq = None
DeviceTask(I_DEVA, 4000, wkq,
TaskState().waiting(), DeviceTaskRec())
DeviceTask(I_DEVB, 5000, wkq,
TaskState().waiting(), DeviceTaskRec())
schedule()
if taskWorkArea.holdCount == 9297 and taskWorkArea.qpktCount == 23246:
pass
else:
return False
return True
assert Richards().run(1)
+33
View File
@@ -0,0 +1,33 @@
# Adapted from python/pyperformance 1.14.0 (unpickle_pure_python).
import datetime
import pickle
profile = {
"age": 18,
"birthday": datetime.date(1980, 5, 7),
"country": "BR",
"encrypted_id": "G9urXXAJwjE",
"flags": 412317970704,
"friend_count": 0,
"locale_preference": "pt_BR",
"tags": ["a", "b", "c", "d", "e", "f", "g"],
"username": "collinwinter",
}
record = ([*range(20)], 60)
group = [dict(profile, id=index) for index in range(3)]
list_data = [[list(range(10)), list(range(10))] for _ in range(10)]
protocol = pickle.HIGHEST_PROTOCOL
dumps = pickle._dumps
loads = pickle._loads
for obj, count in (
(profile, 20),
(record, 20),
(group, 20),
(list_data, 10),
):
encoded = dumps(obj, protocol)
for _ in range(count):
assert loads(encoded) == obj
+84
View File
@@ -0,0 +1,84 @@
// cspell:ignore pyperformance nqueens pidigits
use rustpython::{InterpreterBuilder, InterpreterBuilderExt};
use std::{fs, path::Path};
const PYPERFORMANCE_BENCHMARKS: &[&str] = &[
"base64.py",
"decimal_factorial.py",
"deepcopy.py",
"deltablue.py",
"float.py",
"gc_collect.py",
"gc_traversal.py",
"generators.py",
"json_dumps.py",
"nqueens.py",
"pickle.py",
"pidigits.py",
"regex_effbot.py",
"richards.py",
"unpickle.py",
];
#[test]
fn pyperformance_benchmarks_run_in_rustpython() {
let benchmark_dir = Path::new("benches/benchmarks");
for name in PYPERFORMANCE_BENCHMARKS {
let path = benchmark_dir.join(name);
let source = fs::read_to_string(&path)
.unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display()));
let source_path = path.to_string_lossy();
InterpreterBuilder::new()
.init_stdlib()
.interpreter()
.enter(|vm| {
let code = vm
.compile(&source, rustpython::vm::compiler::Mode::Exec, &*source_path)
.unwrap_or_else(|err| panic!("failed to compile {}: {err}", path.display()));
let scope = vm.new_scope_with_builtins();
if let Err(err) = vm.run_code_obj(code, scope) {
vm.print_exception(err);
panic!("failed to execute {}", path.display());
}
});
}
}
#[test]
fn pyperformance_benchmarks_restore_interpreter_state() {
let source = r#"
import decimal
import pickle
import sys
missing = object()
pickle_module = sys.modules.get("pickle", missing)
pickle_accelerator = sys.modules.get("_pickle", missing)
decimal_precision = decimal.getcontext().prec
for path in (
"benches/benchmarks/pickle.py",
"benches/benchmarks/unpickle.py",
"benches/benchmarks/decimal_factorial.py",
):
with open(path) as benchmark_file:
benchmark_source = benchmark_file.read()
exec(compile(benchmark_source, path, "exec"), {})
assert sys.modules.get("pickle", missing) is pickle_module
assert sys.modules.get("_pickle", missing) is pickle_accelerator
assert decimal.getcontext().prec == decimal_precision
"#;
InterpreterBuilder::new()
.init_stdlib()
.interpreter()
.enter(|vm| {
if let Err(err) = vm.run_simple_string(source) {
vm.print_exception(err);
panic!("pyperformance benchmarks leaked interpreter state");
}
});
}