För att ta reda på vad som tar tid i ett program brukar man säga att man profilerar programmet. Detta är inte mycket mer än ett fint ord för att på ett standardiserat sätt att ta tid på en process och analysera var tid spenderas. Profilering är ett fantastiskt verktyg ifall man vet att man har problem med att någon operation tar mer tid än den bör. Utan profilering kan man endast göra intelligenta gissningar och försöka uppskatta huruvida en förändring av programmet har gjort någon nytta och snabbat upp den långsamma sektorn.
I python ingår standardbiblioteket cProfile för detta ändamål. Biblioteket/modulen är relativt enkelt att använda och ger väldigt tydliga resultat.
Mitt pyparallax-projekt tyckte jag ibland var lite segare vid utritning än önskvärt och efter lite studier lyckades jag skapa ett enkelt skript för att profilera modulen:
import pygame import parallax import cProfile # import profiler module # init pygame pygame.init() screen = pygame.display.set_mode((640, 480), pygame.DOUBLEBUF) # init parallax surface ps = parallax.ParallaxSurface() ps.add('../demo/p2.png', 10) ps.add('../demo/p1.png', 10) # create a profiler object p = cProfile.Profile() # run ps.draw via the profiling object for i in range(1000): p.runcall(ps.draw, screen) # create and print stats p.create_stats() p.print_stats()
I korthet
- importeras modulen för profilering
- normal initiering genomförs
- profileringsobjektet p skapas
- p.run_call() anropas med ps.draw angiven som funktion att köra
- profilerings information skrivs ut
detta resulterade i
ncalls tottime percall cumtime percall filename:lineno(function)
1000 0.122 0.000 36.688 0.037 parallax.py:50(draw)
4000 36.547 0.009 36.547 0.009 {method 'blit' of 'pygame.Surface' objects}
1000 0.003 0.000 0.003 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1000 0.003 0.000 0.003 0.000 {method 'get_height' of 'pygame.Surface' objects}
3000 0.015 0.000 0.015 0.000 {method 'get_width' of 'pygame.Surface' objects}
Som man ser i tabellen ovan spenderas klart mest tid i blit-operationen. Efter att läst på lite grann får jag reda på att ifall man använder färg-mappad-transparens (color keyed) dvs, man låter en särskild färg representera transparens, måste varje pixel utvärderas för sig. Snabbt inser jag att man enkelt kan ta bort detta från det bakersta lagret (som alltid måste vara heltäckande). Kod för detta sätts in i parallax-modulen
if len(self.levels) > 0: image.set_colorkey((0xff, 0x00, 0xea))
dvs. aktivera endast färgmappad transparens ifall detta inte är det första (bakersta) lagret.
Vid körning av profilering-skriptet fås nu
ncalls tottime percall cumtime percall filename:lineno(function)
1000 0.105 0.000 15.414 0.015 parallax.py:50(draw)
4000 15.293 0.004 15.293 0.004 {method 'blit' of 'pygame.Surface' objects}
1000 0.003 0.000 0.003 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1000 0.003 0.000 0.003 0.000 {method 'get_height' of 'pygame.Surface' objects}
3000 0.013 0.000 0.013 0.000 {method 'get_width' of 'pygame.Surface' objects}
cumtime har nu halverats vilket är gott nog åt mig. Ytterligare besparingar kan göras genom att buffra olika lager av bakgrunden och endast uppdatera delarna ifall någon av underlagren har uppdaterats. Detta kommer dock öka komplexiteten markant och är i dagsläget inte motiverat.