Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Why is Pygame so much slower than plain SDL?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
13 replies to this topic

#1 Storyyeller   Members   -  Reputation: 212

Like
0Likes
Like

Posted 22 March 2012 - 09:07 PM

I decided to try out Pygame. Unfortunately, it seems to be very slow. Even a trival loop that just blits an image over and over gets only 18FPS (yes I did call surf.convert()). I know that SDL is capable of much better because I wrote a complete game in C++ with SDL that runs at well over 60FPS on the same computer.

So my question is, why is Pygame so much slower? As I understand it, Pygame is just a wrapper, with most of the work done inside of SDL, so the timings should be about the same.
I trust exceptions about as far as I can throw them.

Sponsor:

#2 rip-off   Moderators   -  Reputation: 8762

Like
0Likes
Like

Posted 23 March 2012 - 03:45 AM

Can you post your code?

#3 Quasimojo   Members   -  Reputation: 255

Like
0Likes
Like

Posted 23 March 2012 - 11:05 AM

Are you reading the image from disk for every frame?

#4 Storyyeller   Members   -  Reputation: 212

Like
0Likes
Like

Posted 23 March 2012 - 07:38 PM

[source lamg='python']import random, os.pathimport pygamedef loadImage(name): print "Loading ", name surf = pygame.image.load(os.path.join("Images", name)) surf.convert() return surfclass ImageLoader(object): def __getattr__(self,key): self.__dict__[key] = loadImage(key + '.png') return self.__dict__[key]images = ImageLoader()class Game(object): def __init__(self): pygame.init() screen = pygame.display.set_mode((800,600)) pygame.display.set_caption("Achronal Portal") self.screen = screen self.spos = 400,300 self.start = pygame.time.get_ticks() self.ticks = 0.0 def draw(self): draw = self.screen.blit draw(images.background02, (0,0))## draw(images.twibody, self.spos) pygame.display.flip()#### def update(self):## if not pygame.key.get_focused():## return## pressed = pygame.key.get_pressed()## xin = pressed[pygame.K_RIGHT] - pressed[pygame.K_LEFT]## self.spos = self.spos[0] + xin, self.spos[1]## self.ticks += 1## print (pygame.time.get_ticks() - self.start)/self.ticks def run(self): running = 1 while running: event = pygame.event.poll() if event.type == pygame.QUIT: running = 0## self.update() self.draw() self.ticks += 1 print (pygame.time.get_ticks() - self.start)/self.ticksGame().run()[/source]
I trust exceptions about as far as I can throw them.

#5 Washu   Senior Moderators   -  Reputation: 5593

Like
0Likes
Like

Posted 23 March 2012 - 07:47 PM

[source lamg='python']class ImageLoader(object): def __getattr__(self,key): self.__dict__[key] = loadImage(key + '.png') return self.__dict__[key][/source]

I wouldn't do your image loading like that. It's terribly inflexible and won't work well once you're trying to implement it with level files, etc. Other than that, I'm not really seeing anything there that stands out.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.
ScapeCode - Blog | SlimDX


#6 Storyyeller   Members   -  Reputation: 212

Like
0Likes
Like

Posted 23 March 2012 - 07:52 PM

Actually, it only loads each image once. You can see because it prints out the Loading ... mesage only once.
I trust exceptions about as far as I can throw them.

#7 blewisjr   Members   -  Reputation: 622

Like
0Likes
Like

Posted 24 March 2012 - 03:18 AM

I would suggest taking the first image load out of the draw function. Right now you are loading you image based off of your first draw call. I am not saying it is loading more then once or anything as you say it only states it loaded once. I don't see anything else out of the ordinary. Basically what I see by doing this is causing python to do unnecessary redundancy checks on the data in the Dict.

The best way to do this is remove your custom Dict use the one python provides and load your images into it. Because the way you have it written now every time you use a key to access an image python is doing hash table duplication checks and such. This is the line that I would guess is causing your massive slow down.
self.__dict__[key] = loadImage(key + '.png')

Ultimately re-factor your code so that you are not calling loadImage directly off the dict key.

#8 rip-off   Moderators   -  Reputation: 8762

Like
0Likes
Like

Posted 24 March 2012 - 06:27 AM

You are not printing FPS. You are printing total milliseconds / total frames, or the average number of milliseconds per frame. Here is my distillation of your code:

import random, os.path
import pygame

def loadImage(name):
    print "Loading ", name
    surf = pygame.image.load(os.path.join("Images", name))
    surf.convert()
    return surf

pygame.init()
screen = pygame.display.set_mode((800,600))
pygame.display.set_caption("Achronal Portal")
image = loadImage('test.png')
timer = pygame.time.get_ticks()
frames = 0
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.blit(image, (0,0))
    pygame.display.flip()

    frames += 1
    now = pygame.time.get_ticks()
    duration = now - timer
    if duration > 1000:
        seconds = duration / 1000
        fps = frames / seconds
        print "FPS: ", fps
        frames = 0
        timer = now


The above runs at over 180 FPS on my Ubuntu virtual machine (hosted on my Windows 7 laptop). Though I get pretty much the same results when this timing code is included in your original. By this I mean, blewisjr's guess is incorrect, there is nothing intrinsically slow in your original program.

One important point is not printing the FPS every frame. If I modify the program to do that, the FPS varies wildly, down to 30, and up above 100, averaging at maybe 70.

#9 Storyyeller   Members   -  Reputation: 212

Like
0Likes
Like

Posted 24 March 2012 - 09:08 AM

You are not printing FPS. You are printing total milliseconds / total frames, or the average number of milliseconds per frame. Here is my distillation of your code:


I already accounted for that. It printed out 54ms per frame for me, which comes to 18FPS.
I trust exceptions about as far as I can throw them.

#10 rip-off   Moderators   -  Reputation: 8762

Like
0Likes
Like

Posted 24 March 2012 - 05:51 PM

Interesting.

Here is a fairly equivalent of my python program using C++ (correct me if I am missing something).
#include <iostream>
#include <cstdlib>

#include "SDL.h"
#include "SDL_image.h"

int main(int, char**)
{
	if(SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		std::cerr << "Failed to initialise SDL: " << SDL_GetError() << '\n';
		return 1;
	}

	std::atexit(&SDL_Quit);

	SDL_Surface *screen = SDL_SetVideoMode(800, 600, 0, SDL_SWSURFACE);
	if(!screen)
	{
		std::cerr << "Failed to set video mode: " << SDL_GetError() << '\n';
		return 1;
	}

	SDL_WM_SetCaption("Achronal Portal", NULL);

	SDL_Surface *image = IMG_Load("test.png");
	if(!image)
	{
		std::cerr << "Failed to load imae: " << IMG_GetError() << '\n';
		return 1;
	}

	SDL_Surface *temp = SDL_DisplayFormat(image);
	if(temp)
	{
		std::swap(temp, image);
		SDL_FreeSurface(temp);
	}
	else
	{
		std::cerr << "Failed to format image to display: " << SDL_GetError() << '\n';
		return 1;
	}

	int frames = 0;
	Uint32 timer = SDL_GetTicks();
	bool running = true;
	while(running)
	{
		SDL_Event event;
		while(SDL_PollEvent(&event))
		{
			if(event.type == SDL_QUIT)
			{
				running = false;
			}
			else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
			{
				running = false;
			}
		}

		//SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0x00, 0x00, 0xff));
		SDL_Rect dest = {0, 0};
		SDL_BlitSurface(image, NULL, screen, &dest);
		SDL_Flip(screen);

		++frames;

		Uint32 now = SDL_GetTicks();
		Uint32 duration = now - timer;
		if(duration >= 1000)
		{
			Uint32 seconds = duration / 1000;
			Uint32 fps = frames / seconds;
			std::cout << "FPS: " << fps << '\n';
			frames = 0;
			timer = now;
		}
	}

	return 0;
}
The above runs at ~900 FPS on my laptop (no virtual machine this time). My earlier python implementation runs at ~375 FPS outside the VM. This is an approximate speed differential of 42%.

I am using CPython 2.7.2 64 bit and MSVC 2010 express, on a reasonably decent laptop (Core 2 Duo 2.53GHz, 6GB RAM and an ATI RadeonHD 4650). What kind of speed differential do you get? What kind of system are you running?

Another idea is to get a profiler, or wrap the various parts of the game loop in a timer, and compare how long different parts of your program are taking.

#11 Storyyeller   Members   -  Reputation: 212

Like
0Likes
Like

Posted 07 April 2012 - 09:37 AM

My Python FPS increased to about 30 once I started printing out the time only once every 16 frames. The C++ version you posted gives me about 230FPS.

I've got Pentium Dual core 2.10 GHZ with 3Gb RAM and 32bit Windows 7. I'm also running CPython 2.7.2
I trust exceptions about as far as I can throw them.

#12 JTippetts   Moderators   -  Reputation: 8661

Like
0Likes
Like

Posted 07 April 2012 - 12:18 PM

This touches the reason that i have shied away using libraries such as pygame after my initial experiments. After years of experimenting with game frameworks implemented in Lua and Python, I've come to the conclusion that you should not implement your "inner draw loop" within Python or Lua. You spend way too much time trying to optimize it, just to get it into an acceptable range. Much better to implement your inner draw within C or C++, where even an un-optimized version can typically run much faster than an implementation in script. Construct an interface to the inner draw loop such that you can add/remove instances of sprites, but leave the actual render loop to a faster language.

It's not just for rendering, either. Any time you have an inner loop (a loop that iterates over a data set every frame; physics comes to mind) you probably want to offload it into a C/C++ module and implement an interface exposed to script. The right tools for the right job; libraries like pygame, IMO, tend to be counterproductive in this regard.

#13 Storyyeller   Members   -  Reputation: 212

Like
0Likes
Like

Posted 07 April 2012 - 12:23 PM

I was already planning to do the physics in C++, but I figured it might be easier to do all the UI stuff in Python.
I trust exceptions about as far as I can throw them.

#14 Narf the Mouse   Members   -  Reputation: 318

Like
0Likes
Like

Posted 07 April 2012 - 12:41 PM

My advice? IMU, there's compilation tools for Python - Try compiling it. Interpreted languages will always be slower, barring crippleware.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS