Commit 601e093d authored by Chris Toshok's avatar Chris Toshok

add richards.py and deltablue.py minibenchmarks

parent c250a77e
"""
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`: (http://code.google.com/p/v8/source/browse/branches/bleeding_edge/benchmarks/deltablue.js)
"""
from __future__ import print_function
# 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.STONG_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 == 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, dest = None, 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():
chain_test(100)
projection_test(100)
# Specific to the PyPy implementation, to run within the main harnass.
def main(n):
import time
times = []
for i in range(n):
t1 = time.time()
delta_blue()
t2 = time.time()
times.append(t2 - t1)
return times
main(100)
# 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,
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: # xrange(BUFSIZE)
w.count += 1
if w.count > 26:
w.count = 1
pkt.data[i] = A + w.count - 1
return self.qpkt(pkt)
import time
def schedule():
t = taskWorkArea.taskList
while t is not None:
pkt = 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 xrange(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
def entry_point(iterations):
r = Richards()
startTime = time.time()
result = r.run(iterations)
endTime = time.time()
return result, startTime, endTime
def main(entry_point = entry_point, iterations = 10):
print "Richards benchmark (Python) starting... [%r]" % entry_point
result, startTime, endTime = entry_point(iterations)
if not result:
print "Incorrect results!"
return -1
print "finished."
total_s = endTime - startTime
print "Total time for %d iterations: %.2f secs" %(iterations,total_s)
print "Average time per iteration: %.2f ms" %(total_s*1000/iterations)
return 42
if __name__ == '__main__':
import sys
if len(sys.argv) >= 2:
main(iterations = int(sys.argv[1]))
else:
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment