mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
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:
1 parent
5b5f4c3715
commit
7ec833336d
17 files changed
+1821
No files matched your search
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user