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