]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | #!/usr/local/bin/python -O\r |
2 | \r | |
3 | """ A Python Benchmark Suite\r | |
4 | \r | |
5 | """\r | |
6 | #\r | |
7 | # Note: Please keep this module compatible to Python 1.5.2.\r | |
8 | #\r | |
9 | # Tests may include features in later Python versions, but these\r | |
10 | # should then be embedded in try-except clauses in the configuration\r | |
11 | # module Setup.py.\r | |
12 | #\r | |
13 | \r | |
14 | # pybench Copyright\r | |
15 | __copyright__ = """\\r | |
16 | Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com)\r | |
17 | Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com)\r | |
18 | \r | |
19 | All Rights Reserved.\r | |
20 | \r | |
21 | Permission to use, copy, modify, and distribute this software and its\r | |
22 | documentation for any purpose and without fee or royalty is hereby\r | |
23 | granted, provided that the above copyright notice appear in all copies\r | |
24 | and that both that copyright notice and this permission notice appear\r | |
25 | in supporting documentation or portions thereof, including\r | |
26 | modifications, that you make.\r | |
27 | \r | |
28 | THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO\r | |
29 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\r | |
30 | FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,\r | |
31 | INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING\r | |
32 | FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\r | |
33 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION\r | |
34 | WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !\r | |
35 | """\r | |
36 | \r | |
37 | import sys, time, operator, string, platform\r | |
38 | from CommandLine import *\r | |
39 | \r | |
40 | try:\r | |
41 | import cPickle\r | |
42 | pickle = cPickle\r | |
43 | except ImportError:\r | |
44 | import pickle\r | |
45 | \r | |
46 | # Version number; version history: see README file !\r | |
47 | __version__ = '2.0'\r | |
48 | \r | |
49 | ### Constants\r | |
50 | \r | |
51 | # Second fractions\r | |
52 | MILLI_SECONDS = 1e3\r | |
53 | MICRO_SECONDS = 1e6\r | |
54 | \r | |
55 | # Percent unit\r | |
56 | PERCENT = 100\r | |
57 | \r | |
58 | # Horizontal line length\r | |
59 | LINE = 79\r | |
60 | \r | |
61 | # Minimum test run-time\r | |
62 | MIN_TEST_RUNTIME = 1e-3\r | |
63 | \r | |
64 | # Number of calibration runs to use for calibrating the tests\r | |
65 | CALIBRATION_RUNS = 20\r | |
66 | \r | |
67 | # Number of calibration loops to run for each calibration run\r | |
68 | CALIBRATION_LOOPS = 20\r | |
69 | \r | |
70 | # Allow skipping calibration ?\r | |
71 | ALLOW_SKIPPING_CALIBRATION = 1\r | |
72 | \r | |
73 | # Timer types\r | |
74 | TIMER_TIME_TIME = 'time.time'\r | |
75 | TIMER_TIME_CLOCK = 'time.clock'\r | |
76 | TIMER_SYSTIMES_PROCESSTIME = 'systimes.processtime'\r | |
77 | \r | |
78 | # Choose platform default timer\r | |
79 | if sys.platform[:3] == 'win':\r | |
80 | # On WinXP this has 2.5ms resolution\r | |
81 | TIMER_PLATFORM_DEFAULT = TIMER_TIME_CLOCK\r | |
82 | else:\r | |
83 | # On Linux this has 1ms resolution\r | |
84 | TIMER_PLATFORM_DEFAULT = TIMER_TIME_TIME\r | |
85 | \r | |
86 | # Print debug information ?\r | |
87 | _debug = 0\r | |
88 | \r | |
89 | ### Helpers\r | |
90 | \r | |
91 | def get_timer(timertype):\r | |
92 | \r | |
93 | if timertype == TIMER_TIME_TIME:\r | |
94 | return time.time\r | |
95 | elif timertype == TIMER_TIME_CLOCK:\r | |
96 | return time.clock\r | |
97 | elif timertype == TIMER_SYSTIMES_PROCESSTIME:\r | |
98 | import systimes\r | |
99 | return systimes.processtime\r | |
100 | else:\r | |
101 | raise TypeError('unknown timer type: %s' % timertype)\r | |
102 | \r | |
103 | def get_machine_details():\r | |
104 | \r | |
105 | if _debug:\r | |
106 | print 'Getting machine details...'\r | |
107 | buildno, builddate = platform.python_build()\r | |
108 | python = platform.python_version()\r | |
109 | try:\r | |
110 | unichr(100000)\r | |
111 | except ValueError:\r | |
112 | # UCS2 build (standard)\r | |
113 | unicode = 'UCS2'\r | |
114 | except NameError:\r | |
115 | unicode = None\r | |
116 | else:\r | |
117 | # UCS4 build (most recent Linux distros)\r | |
118 | unicode = 'UCS4'\r | |
119 | bits, linkage = platform.architecture()\r | |
120 | return {\r | |
121 | 'platform': platform.platform(),\r | |
122 | 'processor': platform.processor(),\r | |
123 | 'executable': sys.executable,\r | |
124 | 'implementation': getattr(platform, 'python_implementation',\r | |
125 | lambda:'n/a')(),\r | |
126 | 'python': platform.python_version(),\r | |
127 | 'compiler': platform.python_compiler(),\r | |
128 | 'buildno': buildno,\r | |
129 | 'builddate': builddate,\r | |
130 | 'unicode': unicode,\r | |
131 | 'bits': bits,\r | |
132 | }\r | |
133 | \r | |
134 | def print_machine_details(d, indent=''):\r | |
135 | \r | |
136 | l = ['Machine Details:',\r | |
137 | ' Platform ID: %s' % d.get('platform', 'n/a'),\r | |
138 | ' Processor: %s' % d.get('processor', 'n/a'),\r | |
139 | '',\r | |
140 | 'Python:',\r | |
141 | ' Implementation: %s' % d.get('implementation', 'n/a'),\r | |
142 | ' Executable: %s' % d.get('executable', 'n/a'),\r | |
143 | ' Version: %s' % d.get('python', 'n/a'),\r | |
144 | ' Compiler: %s' % d.get('compiler', 'n/a'),\r | |
145 | ' Bits: %s' % d.get('bits', 'n/a'),\r | |
146 | ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),\r | |
147 | d.get('buildno', 'n/a')),\r | |
148 | ' Unicode: %s' % d.get('unicode', 'n/a'),\r | |
149 | ]\r | |
150 | print indent + string.join(l, '\n' + indent) + '\n'\r | |
151 | \r | |
152 | ### Test baseclass\r | |
153 | \r | |
154 | class Test:\r | |
155 | \r | |
156 | """ All test must have this class as baseclass. It provides\r | |
157 | the necessary interface to the benchmark machinery.\r | |
158 | \r | |
159 | The tests must set .rounds to a value high enough to let the\r | |
160 | test run between 20-50 seconds. This is needed because\r | |
161 | clock()-timing only gives rather inaccurate values (on Linux,\r | |
162 | for example, it is accurate to a few hundreths of a\r | |
163 | second). If you don't want to wait that long, use a warp\r | |
164 | factor larger than 1.\r | |
165 | \r | |
166 | It is also important to set the .operations variable to a\r | |
167 | value representing the number of "virtual operations" done per\r | |
168 | call of .run().\r | |
169 | \r | |
170 | If you change a test in some way, don't forget to increase\r | |
171 | its version number.\r | |
172 | \r | |
173 | """\r | |
174 | \r | |
175 | ### Instance variables that each test should override\r | |
176 | \r | |
177 | # Version number of the test as float (x.yy); this is important\r | |
178 | # for comparisons of benchmark runs - tests with unequal version\r | |
179 | # number will not get compared.\r | |
180 | version = 2.0\r | |
181 | \r | |
182 | # The number of abstract operations done in each round of the\r | |
183 | # test. An operation is the basic unit of what you want to\r | |
184 | # measure. The benchmark will output the amount of run-time per\r | |
185 | # operation. Note that in order to raise the measured timings\r | |
186 | # significantly above noise level, it is often required to repeat\r | |
187 | # sets of operations more than once per test round. The measured\r | |
188 | # overhead per test round should be less than 1 second.\r | |
189 | operations = 1\r | |
190 | \r | |
191 | # Number of rounds to execute per test run. This should be\r | |
192 | # adjusted to a figure that results in a test run-time of between\r | |
193 | # 1-2 seconds.\r | |
194 | rounds = 100000\r | |
195 | \r | |
196 | ### Internal variables\r | |
197 | \r | |
198 | # Mark this class as implementing a test\r | |
199 | is_a_test = 1\r | |
200 | \r | |
201 | # Last timing: (real, run, overhead)\r | |
202 | last_timing = (0.0, 0.0, 0.0)\r | |
203 | \r | |
204 | # Warp factor to use for this test\r | |
205 | warp = 1\r | |
206 | \r | |
207 | # Number of calibration runs to use\r | |
208 | calibration_runs = CALIBRATION_RUNS\r | |
209 | \r | |
210 | # List of calibration timings\r | |
211 | overhead_times = None\r | |
212 | \r | |
213 | # List of test run timings\r | |
214 | times = []\r | |
215 | \r | |
216 | # Timer used for the benchmark\r | |
217 | timer = TIMER_PLATFORM_DEFAULT\r | |
218 | \r | |
219 | def __init__(self, warp=None, calibration_runs=None, timer=None):\r | |
220 | \r | |
221 | # Set parameters\r | |
222 | if warp is not None:\r | |
223 | self.rounds = int(self.rounds / warp)\r | |
224 | if self.rounds == 0:\r | |
225 | raise ValueError('warp factor set too high')\r | |
226 | self.warp = warp\r | |
227 | if calibration_runs is not None:\r | |
228 | if (not ALLOW_SKIPPING_CALIBRATION and\r | |
229 | calibration_runs < 1):\r | |
230 | raise ValueError('at least one calibration run is required')\r | |
231 | self.calibration_runs = calibration_runs\r | |
232 | if timer is not None:\r | |
233 | self.timer = timer\r | |
234 | \r | |
235 | # Init variables\r | |
236 | self.times = []\r | |
237 | self.overhead_times = []\r | |
238 | \r | |
239 | # We want these to be in the instance dict, so that pickle\r | |
240 | # saves them\r | |
241 | self.version = self.version\r | |
242 | self.operations = self.operations\r | |
243 | self.rounds = self.rounds\r | |
244 | \r | |
245 | def get_timer(self):\r | |
246 | \r | |
247 | """ Return the timer function to use for the test.\r | |
248 | \r | |
249 | """\r | |
250 | return get_timer(self.timer)\r | |
251 | \r | |
252 | def compatible(self, other):\r | |
253 | \r | |
254 | """ Return 1/0 depending on whether the test is compatible\r | |
255 | with the other Test instance or not.\r | |
256 | \r | |
257 | """\r | |
258 | if self.version != other.version:\r | |
259 | return 0\r | |
260 | if self.rounds != other.rounds:\r | |
261 | return 0\r | |
262 | return 1\r | |
263 | \r | |
264 | def calibrate_test(self):\r | |
265 | \r | |
266 | if self.calibration_runs == 0:\r | |
267 | self.overhead_times = [0.0]\r | |
268 | return\r | |
269 | \r | |
270 | calibrate = self.calibrate\r | |
271 | timer = self.get_timer()\r | |
272 | calibration_loops = range(CALIBRATION_LOOPS)\r | |
273 | \r | |
274 | # Time the calibration loop overhead\r | |
275 | prep_times = []\r | |
276 | for i in range(self.calibration_runs):\r | |
277 | t = timer()\r | |
278 | for i in calibration_loops:\r | |
279 | pass\r | |
280 | t = timer() - t\r | |
281 | prep_times.append(t / CALIBRATION_LOOPS)\r | |
282 | min_prep_time = min(prep_times)\r | |
283 | if _debug:\r | |
284 | print\r | |
285 | print 'Calib. prep time = %.6fms' % (\r | |
286 | min_prep_time * MILLI_SECONDS)\r | |
287 | \r | |
288 | # Time the calibration runs (doing CALIBRATION_LOOPS loops of\r | |
289 | # .calibrate() method calls each)\r | |
290 | for i in range(self.calibration_runs):\r | |
291 | t = timer()\r | |
292 | for i in calibration_loops:\r | |
293 | calibrate()\r | |
294 | t = timer() - t\r | |
295 | self.overhead_times.append(t / CALIBRATION_LOOPS\r | |
296 | - min_prep_time)\r | |
297 | \r | |
298 | # Check the measured times\r | |
299 | min_overhead = min(self.overhead_times)\r | |
300 | max_overhead = max(self.overhead_times)\r | |
301 | if _debug:\r | |
302 | print 'Calib. overhead time = %.6fms' % (\r | |
303 | min_overhead * MILLI_SECONDS)\r | |
304 | if min_overhead < 0.0:\r | |
305 | raise ValueError('calibration setup did not work')\r | |
306 | if max_overhead - min_overhead > 0.1:\r | |
307 | raise ValueError(\r | |
308 | 'overhead calibration timing range too inaccurate: '\r | |
309 | '%r - %r' % (min_overhead, max_overhead))\r | |
310 | \r | |
311 | def run(self):\r | |
312 | \r | |
313 | """ Run the test in two phases: first calibrate, then\r | |
314 | do the actual test. Be careful to keep the calibration\r | |
315 | timing low w/r to the test timing.\r | |
316 | \r | |
317 | """\r | |
318 | test = self.test\r | |
319 | timer = self.get_timer()\r | |
320 | \r | |
321 | # Get calibration\r | |
322 | min_overhead = min(self.overhead_times)\r | |
323 | \r | |
324 | # Test run\r | |
325 | t = timer()\r | |
326 | test()\r | |
327 | t = timer() - t\r | |
328 | if t < MIN_TEST_RUNTIME:\r | |
329 | raise ValueError('warp factor too high: '\r | |
330 | 'test times are < 10ms')\r | |
331 | eff_time = t - min_overhead\r | |
332 | if eff_time < 0:\r | |
333 | raise ValueError('wrong calibration')\r | |
334 | self.last_timing = (eff_time, t, min_overhead)\r | |
335 | self.times.append(eff_time)\r | |
336 | \r | |
337 | def calibrate(self):\r | |
338 | \r | |
339 | """ Calibrate the test.\r | |
340 | \r | |
341 | This method should execute everything that is needed to\r | |
342 | setup and run the test - except for the actual operations\r | |
343 | that you intend to measure. pybench uses this method to\r | |
344 | measure the test implementation overhead.\r | |
345 | \r | |
346 | """\r | |
347 | return\r | |
348 | \r | |
349 | def test(self):\r | |
350 | \r | |
351 | """ Run the test.\r | |
352 | \r | |
353 | The test needs to run self.rounds executing\r | |
354 | self.operations number of operations each.\r | |
355 | \r | |
356 | """\r | |
357 | return\r | |
358 | \r | |
359 | def stat(self):\r | |
360 | \r | |
361 | """ Return test run statistics as tuple:\r | |
362 | \r | |
363 | (minimum run time,\r | |
364 | average run time,\r | |
365 | total run time,\r | |
366 | average time per operation,\r | |
367 | minimum overhead time)\r | |
368 | \r | |
369 | """\r | |
370 | runs = len(self.times)\r | |
371 | if runs == 0:\r | |
372 | return 0.0, 0.0, 0.0, 0.0\r | |
373 | min_time = min(self.times)\r | |
374 | total_time = reduce(operator.add, self.times, 0.0)\r | |
375 | avg_time = total_time / float(runs)\r | |
376 | operation_avg = total_time / float(runs\r | |
377 | * self.rounds\r | |
378 | * self.operations)\r | |
379 | if self.overhead_times:\r | |
380 | min_overhead = min(self.overhead_times)\r | |
381 | else:\r | |
382 | min_overhead = self.last_timing[2]\r | |
383 | return min_time, avg_time, total_time, operation_avg, min_overhead\r | |
384 | \r | |
385 | ### Load Setup\r | |
386 | \r | |
387 | # This has to be done after the definition of the Test class, since\r | |
388 | # the Setup module will import subclasses using this class.\r | |
389 | \r | |
390 | import Setup\r | |
391 | \r | |
392 | ### Benchmark base class\r | |
393 | \r | |
394 | class Benchmark:\r | |
395 | \r | |
396 | # Name of the benchmark\r | |
397 | name = ''\r | |
398 | \r | |
399 | # Number of benchmark rounds to run\r | |
400 | rounds = 1\r | |
401 | \r | |
402 | # Warp factor use to run the tests\r | |
403 | warp = 1 # Warp factor\r | |
404 | \r | |
405 | # Average benchmark round time\r | |
406 | roundtime = 0\r | |
407 | \r | |
408 | # Benchmark version number as float x.yy\r | |
409 | version = 2.0\r | |
410 | \r | |
411 | # Produce verbose output ?\r | |
412 | verbose = 0\r | |
413 | \r | |
414 | # Dictionary with the machine details\r | |
415 | machine_details = None\r | |
416 | \r | |
417 | # Timer used for the benchmark\r | |
418 | timer = TIMER_PLATFORM_DEFAULT\r | |
419 | \r | |
420 | def __init__(self, name, verbose=None, timer=None, warp=None,\r | |
421 | calibration_runs=None):\r | |
422 | \r | |
423 | if name:\r | |
424 | self.name = name\r | |
425 | else:\r | |
426 | self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \\r | |
427 | (time.localtime(time.time())[:6])\r | |
428 | if verbose is not None:\r | |
429 | self.verbose = verbose\r | |
430 | if timer is not None:\r | |
431 | self.timer = timer\r | |
432 | if warp is not None:\r | |
433 | self.warp = warp\r | |
434 | if calibration_runs is not None:\r | |
435 | self.calibration_runs = calibration_runs\r | |
436 | \r | |
437 | # Init vars\r | |
438 | self.tests = {}\r | |
439 | if _debug:\r | |
440 | print 'Getting machine details...'\r | |
441 | self.machine_details = get_machine_details()\r | |
442 | \r | |
443 | # Make .version an instance attribute to have it saved in the\r | |
444 | # Benchmark pickle\r | |
445 | self.version = self.version\r | |
446 | \r | |
447 | def get_timer(self):\r | |
448 | \r | |
449 | """ Return the timer function to use for the test.\r | |
450 | \r | |
451 | """\r | |
452 | return get_timer(self.timer)\r | |
453 | \r | |
454 | def compatible(self, other):\r | |
455 | \r | |
456 | """ Return 1/0 depending on whether the benchmark is\r | |
457 | compatible with the other Benchmark instance or not.\r | |
458 | \r | |
459 | """\r | |
460 | if self.version != other.version:\r | |
461 | return 0\r | |
462 | if (self.machine_details == other.machine_details and\r | |
463 | self.timer != other.timer):\r | |
464 | return 0\r | |
465 | if (self.calibration_runs == 0 and\r | |
466 | other.calibration_runs != 0):\r | |
467 | return 0\r | |
468 | if (self.calibration_runs != 0 and\r | |
469 | other.calibration_runs == 0):\r | |
470 | return 0\r | |
471 | return 1\r | |
472 | \r | |
473 | def load_tests(self, setupmod, limitnames=None):\r | |
474 | \r | |
475 | # Add tests\r | |
476 | if self.verbose:\r | |
477 | print 'Searching for tests ...'\r | |
478 | print '--------------------------------------'\r | |
479 | for testclass in setupmod.__dict__.values():\r | |
480 | if not hasattr(testclass, 'is_a_test'):\r | |
481 | continue\r | |
482 | name = testclass.__name__\r | |
483 | if name == 'Test':\r | |
484 | continue\r | |
485 | if (limitnames is not None and\r | |
486 | limitnames.search(name) is None):\r | |
487 | continue\r | |
488 | self.tests[name] = testclass(\r | |
489 | warp=self.warp,\r | |
490 | calibration_runs=self.calibration_runs,\r | |
491 | timer=self.timer)\r | |
492 | l = self.tests.keys()\r | |
493 | l.sort()\r | |
494 | if self.verbose:\r | |
495 | for name in l:\r | |
496 | print ' %s' % name\r | |
497 | print '--------------------------------------'\r | |
498 | print ' %i tests found' % len(l)\r | |
499 | print\r | |
500 | \r | |
501 | def calibrate(self):\r | |
502 | \r | |
503 | print 'Calibrating tests. Please wait...',\r | |
504 | sys.stdout.flush()\r | |
505 | if self.verbose:\r | |
506 | print\r | |
507 | print\r | |
508 | print 'Test min max'\r | |
509 | print '-' * LINE\r | |
510 | tests = self.tests.items()\r | |
511 | tests.sort()\r | |
512 | for i in range(len(tests)):\r | |
513 | name, test = tests[i]\r | |
514 | test.calibrate_test()\r | |
515 | if self.verbose:\r | |
516 | print '%30s: %6.3fms %6.3fms' % \\r | |
517 | (name,\r | |
518 | min(test.overhead_times) * MILLI_SECONDS,\r | |
519 | max(test.overhead_times) * MILLI_SECONDS)\r | |
520 | if self.verbose:\r | |
521 | print\r | |
522 | print 'Done with the calibration.'\r | |
523 | else:\r | |
524 | print 'done.'\r | |
525 | print\r | |
526 | \r | |
527 | def run(self):\r | |
528 | \r | |
529 | tests = self.tests.items()\r | |
530 | tests.sort()\r | |
531 | timer = self.get_timer()\r | |
532 | print 'Running %i round(s) of the suite at warp factor %i:' % \\r | |
533 | (self.rounds, self.warp)\r | |
534 | print\r | |
535 | self.roundtimes = []\r | |
536 | for i in range(self.rounds):\r | |
537 | if self.verbose:\r | |
538 | print ' Round %-25i effective absolute overhead' % (i+1)\r | |
539 | total_eff_time = 0.0\r | |
540 | for j in range(len(tests)):\r | |
541 | name, test = tests[j]\r | |
542 | if self.verbose:\r | |
543 | print '%30s:' % name,\r | |
544 | test.run()\r | |
545 | (eff_time, abs_time, min_overhead) = test.last_timing\r | |
546 | total_eff_time = total_eff_time + eff_time\r | |
547 | if self.verbose:\r | |
548 | print ' %5.0fms %5.0fms %7.3fms' % \\r | |
549 | (eff_time * MILLI_SECONDS,\r | |
550 | abs_time * MILLI_SECONDS,\r | |
551 | min_overhead * MILLI_SECONDS)\r | |
552 | self.roundtimes.append(total_eff_time)\r | |
553 | if self.verbose:\r | |
554 | print (' '\r | |
555 | ' ------------------------------')\r | |
556 | print (' '\r | |
557 | ' Totals: %6.0fms' %\r | |
558 | (total_eff_time * MILLI_SECONDS))\r | |
559 | print\r | |
560 | else:\r | |
561 | print '* Round %i done in %.3f seconds.' % (i+1,\r | |
562 | total_eff_time)\r | |
563 | print\r | |
564 | \r | |
565 | def stat(self):\r | |
566 | \r | |
567 | """ Return benchmark run statistics as tuple:\r | |
568 | \r | |
569 | (minimum round time,\r | |
570 | average round time,\r | |
571 | maximum round time)\r | |
572 | \r | |
573 | XXX Currently not used, since the benchmark does test\r | |
574 | statistics across all rounds.\r | |
575 | \r | |
576 | """\r | |
577 | runs = len(self.roundtimes)\r | |
578 | if runs == 0:\r | |
579 | return 0.0, 0.0\r | |
580 | min_time = min(self.roundtimes)\r | |
581 | total_time = reduce(operator.add, self.roundtimes, 0.0)\r | |
582 | avg_time = total_time / float(runs)\r | |
583 | max_time = max(self.roundtimes)\r | |
584 | return (min_time, avg_time, max_time)\r | |
585 | \r | |
586 | def print_header(self, title='Benchmark'):\r | |
587 | \r | |
588 | print '-' * LINE\r | |
589 | print '%s: %s' % (title, self.name)\r | |
590 | print '-' * LINE\r | |
591 | print\r | |
592 | print ' Rounds: %s' % self.rounds\r | |
593 | print ' Warp: %s' % self.warp\r | |
594 | print ' Timer: %s' % self.timer\r | |
595 | print\r | |
596 | if self.machine_details:\r | |
597 | print_machine_details(self.machine_details, indent=' ')\r | |
598 | print\r | |
599 | \r | |
600 | def print_benchmark(self, hidenoise=0, limitnames=None):\r | |
601 | \r | |
602 | print ('Test '\r | |
603 | ' minimum average operation overhead')\r | |
604 | print '-' * LINE\r | |
605 | tests = self.tests.items()\r | |
606 | tests.sort()\r | |
607 | total_min_time = 0.0\r | |
608 | total_avg_time = 0.0\r | |
609 | for name, test in tests:\r | |
610 | if (limitnames is not None and\r | |
611 | limitnames.search(name) is None):\r | |
612 | continue\r | |
613 | (min_time,\r | |
614 | avg_time,\r | |
615 | total_time,\r | |
616 | op_avg,\r | |
617 | min_overhead) = test.stat()\r | |
618 | total_min_time = total_min_time + min_time\r | |
619 | total_avg_time = total_avg_time + avg_time\r | |
620 | print '%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \\r | |
621 | (name,\r | |
622 | min_time * MILLI_SECONDS,\r | |
623 | avg_time * MILLI_SECONDS,\r | |
624 | op_avg * MICRO_SECONDS,\r | |
625 | min_overhead *MILLI_SECONDS)\r | |
626 | print '-' * LINE\r | |
627 | print ('Totals: '\r | |
628 | ' %6.0fms %6.0fms' %\r | |
629 | (total_min_time * MILLI_SECONDS,\r | |
630 | total_avg_time * MILLI_SECONDS,\r | |
631 | ))\r | |
632 | print\r | |
633 | \r | |
634 | def print_comparison(self, compare_to, hidenoise=0, limitnames=None):\r | |
635 | \r | |
636 | # Check benchmark versions\r | |
637 | if compare_to.version != self.version:\r | |
638 | print ('* Benchmark versions differ: '\r | |
639 | 'cannot compare this benchmark to "%s" !' %\r | |
640 | compare_to.name)\r | |
641 | print\r | |
642 | self.print_benchmark(hidenoise=hidenoise,\r | |
643 | limitnames=limitnames)\r | |
644 | return\r | |
645 | \r | |
646 | # Print header\r | |
647 | compare_to.print_header('Comparing with')\r | |
648 | print ('Test '\r | |
649 | ' minimum run-time average run-time')\r | |
650 | print (' '\r | |
651 | ' this other diff this other diff')\r | |
652 | print '-' * LINE\r | |
653 | \r | |
654 | # Print test comparisons\r | |
655 | tests = self.tests.items()\r | |
656 | tests.sort()\r | |
657 | total_min_time = other_total_min_time = 0.0\r | |
658 | total_avg_time = other_total_avg_time = 0.0\r | |
659 | benchmarks_compatible = self.compatible(compare_to)\r | |
660 | tests_compatible = 1\r | |
661 | for name, test in tests:\r | |
662 | if (limitnames is not None and\r | |
663 | limitnames.search(name) is None):\r | |
664 | continue\r | |
665 | (min_time,\r | |
666 | avg_time,\r | |
667 | total_time,\r | |
668 | op_avg,\r | |
669 | min_overhead) = test.stat()\r | |
670 | total_min_time = total_min_time + min_time\r | |
671 | total_avg_time = total_avg_time + avg_time\r | |
672 | try:\r | |
673 | other = compare_to.tests[name]\r | |
674 | except KeyError:\r | |
675 | other = None\r | |
676 | if other is None:\r | |
677 | # Other benchmark doesn't include the given test\r | |
678 | min_diff, avg_diff = 'n/a', 'n/a'\r | |
679 | other_min_time = 0.0\r | |
680 | other_avg_time = 0.0\r | |
681 | tests_compatible = 0\r | |
682 | else:\r | |
683 | (other_min_time,\r | |
684 | other_avg_time,\r | |
685 | other_total_time,\r | |
686 | other_op_avg,\r | |
687 | other_min_overhead) = other.stat()\r | |
688 | other_total_min_time = other_total_min_time + other_min_time\r | |
689 | other_total_avg_time = other_total_avg_time + other_avg_time\r | |
690 | if (benchmarks_compatible and\r | |
691 | test.compatible(other)):\r | |
692 | # Both benchmark and tests are comparable\r | |
693 | min_diff = ((min_time * self.warp) /\r | |
694 | (other_min_time * other.warp) - 1.0)\r | |
695 | avg_diff = ((avg_time * self.warp) /\r | |
696 | (other_avg_time * other.warp) - 1.0)\r | |
697 | if hidenoise and abs(min_diff) < 10.0:\r | |
698 | min_diff = ''\r | |
699 | else:\r | |
700 | min_diff = '%+5.1f%%' % (min_diff * PERCENT)\r | |
701 | if hidenoise and abs(avg_diff) < 10.0:\r | |
702 | avg_diff = ''\r | |
703 | else:\r | |
704 | avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)\r | |
705 | else:\r | |
706 | # Benchmark or tests are not comparable\r | |
707 | min_diff, avg_diff = 'n/a', 'n/a'\r | |
708 | tests_compatible = 0\r | |
709 | print '%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \\r | |
710 | (name,\r | |
711 | min_time * MILLI_SECONDS,\r | |
712 | other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,\r | |
713 | min_diff,\r | |
714 | avg_time * MILLI_SECONDS,\r | |
715 | other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,\r | |
716 | avg_diff)\r | |
717 | print '-' * LINE\r | |
718 | \r | |
719 | # Summarise test results\r | |
720 | if not benchmarks_compatible or not tests_compatible:\r | |
721 | min_diff, avg_diff = 'n/a', 'n/a'\r | |
722 | else:\r | |
723 | if other_total_min_time != 0.0:\r | |
724 | min_diff = '%+5.1f%%' % (\r | |
725 | ((total_min_time * self.warp) /\r | |
726 | (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)\r | |
727 | else:\r | |
728 | min_diff = 'n/a'\r | |
729 | if other_total_avg_time != 0.0:\r | |
730 | avg_diff = '%+5.1f%%' % (\r | |
731 | ((total_avg_time * self.warp) /\r | |
732 | (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)\r | |
733 | else:\r | |
734 | avg_diff = 'n/a'\r | |
735 | print ('Totals: '\r | |
736 | ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %\r | |
737 | (total_min_time * MILLI_SECONDS,\r | |
738 | (other_total_min_time * compare_to.warp/self.warp\r | |
739 | * MILLI_SECONDS),\r | |
740 | min_diff,\r | |
741 | total_avg_time * MILLI_SECONDS,\r | |
742 | (other_total_avg_time * compare_to.warp/self.warp\r | |
743 | * MILLI_SECONDS),\r | |
744 | avg_diff\r | |
745 | ))\r | |
746 | print\r | |
747 | print '(this=%s, other=%s)' % (self.name,\r | |
748 | compare_to.name)\r | |
749 | print\r | |
750 | \r | |
751 | class PyBenchCmdline(Application):\r | |
752 | \r | |
753 | header = ("PYBENCH - a benchmark test suite for Python "\r | |
754 | "interpreters/compilers.")\r | |
755 | \r | |
756 | version = __version__\r | |
757 | \r | |
758 | debug = _debug\r | |
759 | \r | |
760 | options = [ArgumentOption('-n',\r | |
761 | 'number of rounds',\r | |
762 | Setup.Number_of_rounds),\r | |
763 | ArgumentOption('-f',\r | |
764 | 'save benchmark to file arg',\r | |
765 | ''),\r | |
766 | ArgumentOption('-c',\r | |
767 | 'compare benchmark with the one in file arg',\r | |
768 | ''),\r | |
769 | ArgumentOption('-s',\r | |
770 | 'show benchmark in file arg, then exit',\r | |
771 | ''),\r | |
772 | ArgumentOption('-w',\r | |
773 | 'set warp factor to arg',\r | |
774 | Setup.Warp_factor),\r | |
775 | ArgumentOption('-t',\r | |
776 | 'run only tests with names matching arg',\r | |
777 | ''),\r | |
778 | ArgumentOption('-C',\r | |
779 | 'set the number of calibration runs to arg',\r | |
780 | CALIBRATION_RUNS),\r | |
781 | SwitchOption('-d',\r | |
782 | 'hide noise in comparisons',\r | |
783 | 0),\r | |
784 | SwitchOption('-v',\r | |
785 | 'verbose output (not recommended)',\r | |
786 | 0),\r | |
787 | SwitchOption('--with-gc',\r | |
788 | 'enable garbage collection',\r | |
789 | 0),\r | |
790 | SwitchOption('--with-syscheck',\r | |
791 | 'use default sys check interval',\r | |
792 | 0),\r | |
793 | ArgumentOption('--timer',\r | |
794 | 'use given timer',\r | |
795 | TIMER_PLATFORM_DEFAULT),\r | |
796 | ]\r | |
797 | \r | |
798 | about = """\\r | |
799 | The normal operation is to run the suite and display the\r | |
800 | results. Use -f to save them for later reuse or comparisons.\r | |
801 | \r | |
802 | Available timers:\r | |
803 | \r | |
804 | time.time\r | |
805 | time.clock\r | |
806 | systimes.processtime\r | |
807 | \r | |
808 | Examples:\r | |
809 | \r | |
810 | python2.1 pybench.py -f p21.pybench\r | |
811 | python2.5 pybench.py -f p25.pybench\r | |
812 | python pybench.py -s p25.pybench -c p21.pybench\r | |
813 | """\r | |
814 | copyright = __copyright__\r | |
815 | \r | |
816 | def main(self):\r | |
817 | \r | |
818 | rounds = self.values['-n']\r | |
819 | reportfile = self.values['-f']\r | |
820 | show_bench = self.values['-s']\r | |
821 | compare_to = self.values['-c']\r | |
822 | hidenoise = self.values['-d']\r | |
823 | warp = int(self.values['-w'])\r | |
824 | withgc = self.values['--with-gc']\r | |
825 | limitnames = self.values['-t']\r | |
826 | if limitnames:\r | |
827 | if _debug:\r | |
828 | print '* limiting test names to one with substring "%s"' % \\r | |
829 | limitnames\r | |
830 | limitnames = re.compile(limitnames, re.I)\r | |
831 | else:\r | |
832 | limitnames = None\r | |
833 | verbose = self.verbose\r | |
834 | withsyscheck = self.values['--with-syscheck']\r | |
835 | calibration_runs = self.values['-C']\r | |
836 | timer = self.values['--timer']\r | |
837 | \r | |
838 | print '-' * LINE\r | |
839 | print 'PYBENCH %s' % __version__\r | |
840 | print '-' * LINE\r | |
841 | print '* using %s %s' % (\r | |
842 | getattr(platform, 'python_implementation', lambda:'Python')(),\r | |
843 | string.join(string.split(sys.version), ' '))\r | |
844 | \r | |
845 | # Switch off garbage collection\r | |
846 | if not withgc:\r | |
847 | try:\r | |
848 | import gc\r | |
849 | except ImportError:\r | |
850 | print '* Python version doesn\'t support garbage collection'\r | |
851 | else:\r | |
852 | try:\r | |
853 | gc.disable()\r | |
854 | except NotImplementedError:\r | |
855 | print '* Python version doesn\'t support gc.disable'\r | |
856 | else:\r | |
857 | print '* disabled garbage collection'\r | |
858 | \r | |
859 | # "Disable" sys check interval\r | |
860 | if not withsyscheck:\r | |
861 | # Too bad the check interval uses an int instead of a long...\r | |
862 | value = 2147483647\r | |
863 | try:\r | |
864 | sys.setcheckinterval(value)\r | |
865 | except (AttributeError, NotImplementedError):\r | |
866 | print '* Python version doesn\'t support sys.setcheckinterval'\r | |
867 | else:\r | |
868 | print '* system check interval set to maximum: %s' % value\r | |
869 | \r | |
870 | if timer == TIMER_SYSTIMES_PROCESSTIME:\r | |
871 | import systimes\r | |
872 | print '* using timer: systimes.processtime (%s)' % \\r | |
873 | systimes.SYSTIMES_IMPLEMENTATION\r | |
874 | else:\r | |
875 | print '* using timer: %s' % timer\r | |
876 | \r | |
877 | print\r | |
878 | \r | |
879 | if compare_to:\r | |
880 | try:\r | |
881 | f = open(compare_to,'rb')\r | |
882 | bench = pickle.load(f)\r | |
883 | bench.name = compare_to\r | |
884 | f.close()\r | |
885 | compare_to = bench\r | |
886 | except IOError, reason:\r | |
887 | print '* Error opening/reading file %s: %s' % (\r | |
888 | repr(compare_to),\r | |
889 | reason)\r | |
890 | compare_to = None\r | |
891 | \r | |
892 | if show_bench:\r | |
893 | try:\r | |
894 | f = open(show_bench,'rb')\r | |
895 | bench = pickle.load(f)\r | |
896 | bench.name = show_bench\r | |
897 | f.close()\r | |
898 | bench.print_header()\r | |
899 | if compare_to:\r | |
900 | bench.print_comparison(compare_to,\r | |
901 | hidenoise=hidenoise,\r | |
902 | limitnames=limitnames)\r | |
903 | else:\r | |
904 | bench.print_benchmark(hidenoise=hidenoise,\r | |
905 | limitnames=limitnames)\r | |
906 | except IOError, reason:\r | |
907 | print '* Error opening/reading file %s: %s' % (\r | |
908 | repr(show_bench),\r | |
909 | reason)\r | |
910 | print\r | |
911 | return\r | |
912 | \r | |
913 | if reportfile:\r | |
914 | print 'Creating benchmark: %s (rounds=%i, warp=%i)' % \\r | |
915 | (reportfile, rounds, warp)\r | |
916 | print\r | |
917 | \r | |
918 | # Create benchmark object\r | |
919 | bench = Benchmark(reportfile,\r | |
920 | verbose=verbose,\r | |
921 | timer=timer,\r | |
922 | warp=warp,\r | |
923 | calibration_runs=calibration_runs)\r | |
924 | bench.rounds = rounds\r | |
925 | bench.load_tests(Setup, limitnames=limitnames)\r | |
926 | try:\r | |
927 | bench.calibrate()\r | |
928 | bench.run()\r | |
929 | except KeyboardInterrupt:\r | |
930 | print\r | |
931 | print '*** KeyboardInterrupt -- Aborting'\r | |
932 | print\r | |
933 | return\r | |
934 | bench.print_header()\r | |
935 | if compare_to:\r | |
936 | bench.print_comparison(compare_to,\r | |
937 | hidenoise=hidenoise,\r | |
938 | limitnames=limitnames)\r | |
939 | else:\r | |
940 | bench.print_benchmark(hidenoise=hidenoise,\r | |
941 | limitnames=limitnames)\r | |
942 | \r | |
943 | # Ring bell\r | |
944 | sys.stderr.write('\007')\r | |
945 | \r | |
946 | if reportfile:\r | |
947 | try:\r | |
948 | f = open(reportfile,'wb')\r | |
949 | bench.name = reportfile\r | |
950 | pickle.dump(bench,f)\r | |
951 | f.close()\r | |
952 | except IOError, reason:\r | |
953 | print '* Error opening/writing reportfile'\r | |
954 | except IOError, reason:\r | |
955 | print '* Error opening/writing reportfile %s: %s' % (\r | |
956 | reportfile,\r | |
957 | reason)\r | |
958 | print\r | |
959 | \r | |
960 | if __name__ == '__main__':\r | |
961 | PyBenchCmdline()\r |