| 1 | #!/usr/bin/env python |
|---|
| 2 | """ |
|---|
| 3 | PySpaceWar benchmarks for optimisation work |
|---|
| 4 | |
|---|
| 5 | $Id$ |
|---|
| 6 | """ |
|---|
| 7 | |
|---|
| 8 | import os |
|---|
| 9 | import sys |
|---|
| 10 | import time |
|---|
| 11 | import random |
|---|
| 12 | import optparse |
|---|
| 13 | |
|---|
| 14 | import pygame |
|---|
| 15 | from pygame.locals import * |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | def setup_path(): |
|---|
| 19 | """Set up python path if running from a source tree.""" |
|---|
| 20 | pkgdir = os.path.join(os.path.dirname(__file__), 'src') |
|---|
| 21 | print pkgdir |
|---|
| 22 | if os.path.isdir(pkgdir): |
|---|
| 23 | sys.path.insert(0, pkgdir) |
|---|
| 24 | |
|---|
| 25 | setup_path() |
|---|
| 26 | |
|---|
| 27 | |
|---|
| 28 | from pyspacewar.game import Game |
|---|
| 29 | from pyspacewar.world import Ship |
|---|
| 30 | from pyspacewar.ai import AIController |
|---|
| 31 | from pyspacewar.ui import GameUI |
|---|
| 32 | |
|---|
| 33 | |
|---|
| 34 | def get_cpu_speed(): |
|---|
| 35 | try: |
|---|
| 36 | rows = [row for row in file('/proc/cpuinfo') |
|---|
| 37 | if row.startswith('cpu MHz')] |
|---|
| 38 | except IOError: |
|---|
| 39 | return 0 |
|---|
| 40 | try: |
|---|
| 41 | return float(rows[0].split(':')[1]) |
|---|
| 42 | except IndexError: |
|---|
| 43 | return 0 |
|---|
| 44 | |
|---|
| 45 | |
|---|
| 46 | class DummyAIController(object): |
|---|
| 47 | |
|---|
| 48 | def __init__(self, ship): |
|---|
| 49 | self.ship = ship |
|---|
| 50 | |
|---|
| 51 | def control(self): |
|---|
| 52 | if self.ship.appearance % 2: |
|---|
| 53 | self.ship.turn_left() |
|---|
| 54 | else: |
|---|
| 55 | self.ship.turn_right() |
|---|
| 56 | if len(self.ship.world.objects) < 90: |
|---|
| 57 | self.ship.launch() |
|---|
| 58 | |
|---|
| 59 | class DummyTimeSource(object): |
|---|
| 60 | |
|---|
| 61 | delta = 0 |
|---|
| 62 | |
|---|
| 63 | def now(self): |
|---|
| 64 | return 0 |
|---|
| 65 | |
|---|
| 66 | def wait(self, time): |
|---|
| 67 | return True |
|---|
| 68 | |
|---|
| 69 | |
|---|
| 70 | class Stats(object): |
|---|
| 71 | time = 0 |
|---|
| 72 | ticks = 0 |
|---|
| 73 | max_objects = 0 |
|---|
| 74 | min_objects = sys.maxint |
|---|
| 75 | total_objects = 0 |
|---|
| 76 | best_time = sys.maxint |
|---|
| 77 | worst_time = 0 |
|---|
| 78 | |
|---|
| 79 | @property |
|---|
| 80 | def avg_objects(self): |
|---|
| 81 | if self.ticks == 0: |
|---|
| 82 | return 0 |
|---|
| 83 | return self.total_objects / float(self.ticks) |
|---|
| 84 | |
|---|
| 85 | @property |
|---|
| 86 | def ticks_per_second(self): |
|---|
| 87 | if self.time == 0: |
|---|
| 88 | return 0 |
|---|
| 89 | return self.ticks / float(self.time) |
|---|
| 90 | |
|---|
| 91 | @property |
|---|
| 92 | def ms_per_tick(self): |
|---|
| 93 | if self.ticks == 0: |
|---|
| 94 | return 0 |
|---|
| 95 | return self.time * 1000.0 / self.ticks |
|---|
| 96 | |
|---|
| 97 | |
|---|
| 98 | class Benchmark(object): |
|---|
| 99 | |
|---|
| 100 | def __init__(self, seed=None, how_long=100, |
|---|
| 101 | ai_controller=DummyAIController, warmup_ticks=0, |
|---|
| 102 | profile=False, debug=False): |
|---|
| 103 | self.seed = seed |
|---|
| 104 | self.how_long = how_long |
|---|
| 105 | self.ai_controller = ai_controller |
|---|
| 106 | self.warmup_ticks = warmup_ticks |
|---|
| 107 | self.profile = profile |
|---|
| 108 | self.debug = debug |
|---|
| 109 | |
|---|
| 110 | def run(self): |
|---|
| 111 | self.stats = Stats() |
|---|
| 112 | self.init() |
|---|
| 113 | self.stats.cpu_speed_before_warmup = get_cpu_speed() |
|---|
| 114 | self.warmup() |
|---|
| 115 | self.stats.cpu_speed_after_warmup = get_cpu_speed() |
|---|
| 116 | if self.profile: |
|---|
| 117 | from profile import Profile |
|---|
| 118 | profiler = Profile() |
|---|
| 119 | profiler.runcall(self.benchmark) |
|---|
| 120 | else: |
|---|
| 121 | self.benchmark() |
|---|
| 122 | self.stats.cpu_speed_after_benchmark = get_cpu_speed() |
|---|
| 123 | if self.profile: |
|---|
| 124 | import pstats |
|---|
| 125 | self.stats.profile_stats = pstats.Stats(profiler) |
|---|
| 126 | return self.stats |
|---|
| 127 | |
|---|
| 128 | def warmup(self): |
|---|
| 129 | tick = 0 |
|---|
| 130 | while tick < self.warmup_ticks: |
|---|
| 131 | self.cycle() |
|---|
| 132 | tick += 1 |
|---|
| 133 | |
|---|
| 134 | def benchmark(self): |
|---|
| 135 | game = self.game |
|---|
| 136 | stats = self.stats |
|---|
| 137 | start = now = time.time() |
|---|
| 138 | while stats.ticks < self.how_long: |
|---|
| 139 | prev = now |
|---|
| 140 | self.cycle() |
|---|
| 141 | now = time.time() |
|---|
| 142 | stats.ticks += 1 |
|---|
| 143 | stats.max_objects = max(stats.max_objects, len(game.world.objects)) |
|---|
| 144 | stats.min_objects = min(stats.min_objects, len(game.world.objects)) |
|---|
| 145 | stats.total_objects += len(game.world.objects) |
|---|
| 146 | stats.best_time = min(stats.best_time, now - prev) |
|---|
| 147 | stats.worst_time = max(stats.worst_time, now - prev) |
|---|
| 148 | stats.time = now - start |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | class LogicBenchmark(Benchmark): |
|---|
| 152 | |
|---|
| 153 | def init(self): |
|---|
| 154 | game = Game.new(ships=2, rng=random.Random(self.seed)) |
|---|
| 155 | game.time_source = DummyTimeSource() |
|---|
| 156 | ships = [obj for obj in game.world.objects if isinstance(obj, Ship)] |
|---|
| 157 | game.controllers += map(self.ai_controller, ships) |
|---|
| 158 | game.wait_for_tick() # first one does nothing serious |
|---|
| 159 | self.game = game |
|---|
| 160 | self.cycle = game.wait_for_tick |
|---|
| 161 | |
|---|
| 162 | |
|---|
| 163 | class GameBenchmark(Benchmark): |
|---|
| 164 | |
|---|
| 165 | def init(self): |
|---|
| 166 | ui = GameUI() |
|---|
| 167 | ui.rng = random.Random(self.seed) |
|---|
| 168 | ui.show_debug_info = self.debug |
|---|
| 169 | ui.init() |
|---|
| 170 | for player_id, is_ai in enumerate(ui.ai_controlled): |
|---|
| 171 | if is_ai: |
|---|
| 172 | ui.toggle_ai(player_id) |
|---|
| 173 | game = ui.game |
|---|
| 174 | game.time_source = DummyTimeSource() |
|---|
| 175 | game.controllers += map(self.ai_controller, ui.ships) |
|---|
| 176 | game.wait_for_tick() # first one does nothing serious |
|---|
| 177 | self.ui = ui |
|---|
| 178 | self.game = game |
|---|
| 179 | |
|---|
| 180 | def cycle(self): |
|---|
| 181 | self.ui.wait_for_tick() |
|---|
| 182 | self.ui.draw() |
|---|
| 183 | event = pygame.event.poll() |
|---|
| 184 | if (event.type == QUIT or |
|---|
| 185 | (event.type == KEYDOWN and event.key in (K_ESCAPE, K_q))): |
|---|
| 186 | self.how_long = 0 |
|---|
| 187 | self.warmup_ticks = 0 |
|---|
| 188 | |
|---|
| 189 | |
|---|
| 190 | def main(): |
|---|
| 191 | parser = optparse.OptionParser() |
|---|
| 192 | parser.add_option('-s', '--seed', default=0, |
|---|
| 193 | help='specify random seed [default: %default]', |
|---|
| 194 | action='store', dest='seed', type='int') |
|---|
| 195 | parser.add_option('-t', '--ticks', default=100, |
|---|
| 196 | help='specify number of game ticks [default: %default]', |
|---|
| 197 | action='store', dest='ticks', type='int') |
|---|
| 198 | parser.add_option('-w', '--warmup', default=100, |
|---|
| 199 | help='warm up for a number of ticks [default: %default]', |
|---|
| 200 | action='store', dest='warmup', type='int') |
|---|
| 201 | parser.add_option('-a', '--ai', default=DummyAIController, |
|---|
| 202 | help='use real AI logic [default: dumb logic]', |
|---|
| 203 | action='store_const', const=AIController, |
|---|
| 204 | dest='ai_controller') |
|---|
| 205 | parser.add_option('-g', '--gui', default=LogicBenchmark, |
|---|
| 206 | help='benchmark drawing and logic [default: just logic]', |
|---|
| 207 | action='store_const', const=GameBenchmark, |
|---|
| 208 | dest='benchmark') |
|---|
| 209 | parser.add_option('-d', '--debug', default=False, |
|---|
| 210 | help='show debug info during benchmark [default: %default]', |
|---|
| 211 | action='store_true', dest='debug') |
|---|
| 212 | parser.add_option('-p', '--profile', default=False, |
|---|
| 213 | help='enable profiling [default: %default]', |
|---|
| 214 | action='store_true', dest='profile') |
|---|
| 215 | parser.add_option('--psyco', default=False, |
|---|
| 216 | help='use Psyco [default: %default]', |
|---|
| 217 | action='store_true', dest='psyco') |
|---|
| 218 | opts, args = parser.parse_args() |
|---|
| 219 | print "=== Parameters ===" |
|---|
| 220 | print |
|---|
| 221 | if opts.psyco: |
|---|
| 222 | try: |
|---|
| 223 | import psyco |
|---|
| 224 | psyco.full() |
|---|
| 225 | except: |
|---|
| 226 | print 'psyco not available' |
|---|
| 227 | else: |
|---|
| 228 | print 'using psyco' |
|---|
| 229 | print 'random seed: %r' % opts.seed |
|---|
| 230 | print 'warmup: %d' % opts.warmup |
|---|
| 231 | print 'ticks: %d' % opts.ticks |
|---|
| 232 | print 'ai: %s' % opts.ai_controller.__name__ |
|---|
| 233 | print 'benchmark: %s' % opts.benchmark.__name__ |
|---|
| 234 | print 'debug: %s' % opts.debug |
|---|
| 235 | benchmark = opts.benchmark(opts.seed, opts.ticks, opts.ai_controller, |
|---|
| 236 | opts.warmup, opts.profile, opts.debug) |
|---|
| 237 | start_time = time.time() |
|---|
| 238 | stats = benchmark.run() |
|---|
| 239 | total_time = time.time() - start_time |
|---|
| 240 | print |
|---|
| 241 | print "=== CPU ===" |
|---|
| 242 | print |
|---|
| 243 | print 'CPU speed before warmup: %.0f MHz' % stats.cpu_speed_before_warmup |
|---|
| 244 | print 'CPU speed after warmup: %.0f MHz' % stats.cpu_speed_after_warmup |
|---|
| 245 | print 'CPU speed after benchmark: %.0f MHz' % stats.cpu_speed_after_benchmark |
|---|
| 246 | print |
|---|
| 247 | print "=== Results ===" |
|---|
| 248 | print |
|---|
| 249 | print 'total time: %.3f seconds' % total_time |
|---|
| 250 | print 'ticks: %d' % stats.ticks |
|---|
| 251 | print 'objects: min=%d avg=%.1f max=%d' % ( |
|---|
| 252 | stats.min_objects, |
|---|
| 253 | stats.avg_objects, |
|---|
| 254 | stats.max_objects) |
|---|
| 255 | print 'ticks per second: avg=%.3f' % stats.ticks_per_second |
|---|
| 256 | print 'ms per tick: min=%.3f avg=%.3f max=%.3f' % ( |
|---|
| 257 | stats.best_time * 1000.0, |
|---|
| 258 | stats.ms_per_tick, |
|---|
| 259 | stats.worst_time * 1000.0) |
|---|
| 260 | |
|---|
| 261 | if opts.profile: |
|---|
| 262 | stats = stats.profile_stats |
|---|
| 263 | stats.strip_dirs() |
|---|
| 264 | print |
|---|
| 265 | print "== Stats by internal time ===" |
|---|
| 266 | print |
|---|
| 267 | stats.sort_stats('time', 'calls') |
|---|
| 268 | stats.print_stats(40) |
|---|
| 269 | print |
|---|
| 270 | print "== Stats by number of calls, with callers ===" |
|---|
| 271 | print |
|---|
| 272 | stats.sort_stats('calls', 'time') |
|---|
| 273 | stats.print_callers(20) |
|---|
| 274 | print |
|---|
| 275 | print "== Stats by cumulative time, with calees ===" |
|---|
| 276 | print |
|---|
| 277 | stats.sort_stats('cumulative', 'calls') |
|---|
| 278 | stats.print_callees(20) |
|---|
| 279 | |
|---|
| 280 | if __name__ == '__main__': |
|---|
| 281 | main() |
|---|