1+ # -*- coding: utf-8 -*-
2+ from timeit import default_timer as timer
3+
4+ ###
5+ # This file will usually not need any alteration.
6+ ###
7+
8+ #
9+
10+ # Class to wrap a benchmark (helper functions)
11+ class Benchmark (object ):
12+ def __init__ (self ):
13+ self .totalTime = {}
14+ self .iterations = {}
15+ self .startTime = {}
16+ self .depth = 0
17+ self .events = []
18+ # True will make all benchmark instances log their actions in an easy to follow manner
19+ # Might however use up more memory as each action is logged - defaults to False. Use with benchmark.printEvents()
20+ self .verbose = False
21+
22+ def __str__ (self ):
23+ totalTimePrint = ', ' .join ('"%s": %.6f' % (key , value ) for key , value in self .totalTime .items ())
24+ iterationsPrint = ', ' .join ('"%s": %d' % (key , value ) for key , value in self .iterations .items ())
25+ return "Benchmark <Total time: {%s}, iterations: {%s}>" % (totalTimePrint , iterationsPrint )
26+ def __repr__ (self ):
27+ totalTimePrint = ', ' .join ('"%s": %.6f' % (key , value ) for key , value in self .totalTime .items ())
28+ iterationsPrint = ', ' .join ('"%s": %d' % (key , value ) for key , value in self .iterations .items ())
29+ return "Benchmark <Total time: {%s}, iterations: {%s}>" % (totalTimePrint , iterationsPrint )
30+
31+ def __add__ (self , other ):
32+ self .mergeDictionaries (self .totalTime , other .totalTime )
33+ self .mergeDictionaries (self .iterations , other .iterations )
34+ self .mergeDictionaries (self .startTime , other .startTime )
35+ return self
36+ def __radd__ (self , other ):
37+ return self .__add__ (other )
38+
39+ def asTables (self ):
40+ timeLatex = """$\\ begin{array}{l | l}
41+ \\ text{name} & \\ text{time }s\\ \\
42+ \\ hline\n """
43+ iterationsLatex = """$\\ begin{array}{l | l}
44+ \\ text{name} & \\ text{iterations}\\ \\
45+ \\ hline\n """
46+
47+ for key , value in self .totalTime .items ():
48+ timeLatex += "\\ text{%s}&%f\\ \\ \n " % (key , value )
49+ for key , value in self .iterations .items ():
50+ iterationsLatex += "\\ text{%s}&%d\\ \\ \n " % (key , value )
51+
52+ timeLatex += "\\ end{array}$"
53+ iterationsLatex += "\\ end{array}$"
54+
55+ return (timeLatex , iterationsLatex )
56+
57+ def mergeDictionaries (self , a , b ):
58+ for key in b .keys ():
59+ if key not in a :
60+ a [key ] = b [key ]
61+ else :
62+ a [key ] += b [key ]
63+
64+ def start (self , name = "default" ):
65+ t = timer ()
66+
67+ if ":" in name or "," in name or "-" in name :
68+ raise ValueError ("A benchmark name may not contain characters ':', '-' or ','" )
69+
70+ if name in self .startTime :
71+ raise ValueError ("Benchmark for %s is already in progress, please stop benchmark first" % name )
72+
73+ self .startTime [name ] = t
74+
75+ if self .verbose :
76+ self .events .append (("start" , name , self .depth , None ))
77+ self .depth += 1
78+
79+ def stop (self , name = "default" ):
80+ t = timer ()
81+
82+ if ":" in name or "," in name or "-" in name :
83+ raise ValueError ("A benchmark name may not contain characters ':', '-' or ','" )
84+
85+ if name not in self .startTime :
86+ raise ValueError ("Benchmark for %s cannot be stopped before it has been started" % name )
87+
88+ if name not in self .totalTime :
89+ self .totalTime [name ] = 0
90+
91+ timeTaken = t - self .startTime [name ]
92+ self .totalTime [name ] += timeTaken
93+ del self .startTime [name ]
94+
95+ if self .verbose :
96+ self .depth -= 1
97+ self .events .append (("stop" , name , self .depth , timeTaken ))
98+
99+ def iterate (self , name = "default" ):
100+ if ":" in name or "," in name or "-" in name :
101+ raise ValueError ("A benchmark name may not contain characters ':', '-' or ','" )
102+
103+ if name not in self .iterations :
104+ self .iterations [name ] = 0
105+
106+ self .iterations [name ] += 1
107+
108+ if self .verbose :
109+ self .events .append (("iterate" , name , self .depth , None ))
110+
111+ def printEvents (self ):
112+ if self .verbose == False or len (self .events ) == 0 :
113+ print "It seems like no events were captured. Try setting verbose = True on the benchmark instance"
114+
115+ i = 0
116+ while i < len (self .events ):
117+ type , name , depth , timeTaken = self .events [i ]
118+ if type == "iterate" :
119+ start = i
120+ # Group identical iterations to preserve output length
121+ for j in range (i + 1 , len (self .events )):
122+ otherType , otherName , otherDepth , otherTime = self .events [j ]
123+ if otherType == type and otherName == name :
124+ i += 1
125+ else :
126+ break
127+ # Print number of occurances if multiple iterations followed each other
128+ if start == i :
129+ print "\t " * depth + "🔂 %s" % name
130+ else :
131+ print "\t " * depth + "🔂 %s x %d" % (name , i - start + 1 )
132+ elif type == "start" :
133+ print "\t " * depth + "▶️ %s" % name
134+ elif type == "stop" :
135+ print "\t " * depth + "⏹ %s (%f s)" % (name , timeTaken )
136+ i += 1
137+
138+ @classmethod
139+ def max (self , benchmarks ):
140+ benchmark = Benchmark ()
141+
142+ totalTime = {}
143+ iterations = {}
144+
145+ for b in benchmarks :
146+ for key , value in b .totalTime .items ():
147+ if key not in totalTime :
148+ totalTime [key ] = []
149+ totalTime [key ].append (value )
150+ for key , value in b .iterations .items ():
151+ if key not in iterations :
152+ iterations [key ] = []
153+ iterations [key ].append (value )
154+
155+ for key , values in totalTime .items ():
156+ benchmark .totalTime [key ] = max (values )
157+ for key , values in iterations .items ():
158+ benchmark .iterations [key ] = max (values )
159+
160+ return benchmark
161+
162+ @classmethod
163+ def min (self , benchmarks ):
164+ benchmark = Benchmark ()
165+
166+ totalTime = {}
167+ iterations = {}
168+
169+ for b in benchmarks :
170+ for key , value in b .totalTime .items ():
171+ if key not in totalTime :
172+ totalTime [key ] = []
173+ totalTime [key ].append (value )
174+ for key , value in b .iterations .items ():
175+ if key not in iterations :
176+ iterations [key ] = []
177+ iterations [key ].append (value )
178+
179+ for key , values in totalTime .items ():
180+ benchmark .totalTime [key ] = min (values )
181+ for key , values in iterations .items ():
182+ benchmark .iterations [key ] = min (values )
183+
184+ return benchmark
185+
186+ @classmethod
187+ def average (self , benchmarks ):
188+ benchmark = Benchmark ()
189+
190+ totalTime = {}
191+ iterations = {}
192+
193+ average = lambda x : sum (x ) / len (x )
194+
195+ for b in benchmarks :
196+ for key , value in b .totalTime .items ():
197+ if key not in totalTime :
198+ totalTime [key ] = []
199+ totalTime [key ].append (value )
200+ for key , value in b .iterations .items ():
201+ if key not in iterations :
202+ iterations [key ] = []
203+ iterations [key ].append (value )
204+
205+ for key , values in totalTime .items ():
206+ benchmark .totalTime [key ] = average (values )
207+ for key , values in iterations .items ():
208+ benchmark .iterations [key ] = average (values )
209+
210+ return benchmark
0 commit comments