Profilering med python

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.

Etiketter: , , , ,

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s


%d bloggare gillar detta: