[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] pygame.display.flip slow



Hi,

Thanks for the quick feedback, and apologies if my little program caused some confusion - I wrote it quickly to highlight an issue, but I guess I should have included some results from another machine for comparison.

I made some improvements based on Ian's suggestions:

1. I don't average anything, just output the recorded fps values
2. I removed the argument to tick() - this actually helps to showcase the issue

I ran the code (pasted below) on two machines:

Machine A:
Intel(R) Core(TM) i7-5930K CPU @ 3.50GHz
Python 3.7, Pygame1.9.6

Machine B:
Intel(R) Core(TM) i7-4650U CPU @ 1.70GHz
Python 3.7, Pygame1.9.6

The first benchmark on google results shows A should be significantly faster than B
https://cpu.userbenchmark.com/Compare/Intel-Core-i7-4650U-vs-Intel-Core-i7-5930K/m6364vs2578

I ran pyperformance (https://pyperformance.readthedocs.io/usage.html#installation) on both machines and it seems to confirm that.

However, when I run my test program, Machine B reports FPS > 200, and pygame.display.flip taking 0.002 per call,
while machine A reports FPS < 60, and pygame.display.flip taking 0.016 per call.

This puzzles me: I was expecting a result similar to B on both machines, since my loop does nothing - has no game logic, no physics, no image handling. I was wondering if someone has any ideas to help me find out what's wrong with
my setup in machine A. Btw, enabling fullscreen does not help, not does changing the display resolution while
running in the windowed mode. Both displays are running at 60Hz.

MAchine A:
$ python fps_test.py
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
<VideoInfo(hw = 0, wm = 1,video_mem = 0
         blit_hw = 0, blit_hw_CC = 0, blit_hw_A = 0,
         blit_sw = 0, blit_sw_CC = 0, blit_sw_A = 0,
         bitsize  = 32, bytesize = 4,
         masks =  (16711680, 65280, 255, 0),
         shifts = (16, 8, 0, 0),
         losses =  (0, 0, 0, 8),
         current_w = 1440, current_h = 900
>

[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 54.64480972290039, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 57.47126770019531, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 58.82352828979492, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.52381134033203, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 59.88024139404297, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 58.479530334472656, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961, 59.17159652709961]
         405 function calls in 1.760 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.760    1.760 <string>:1(<module>)
        1    0.002    0.002    1.760    1.760 fps_test.py:11(run)
        1    0.000    0.000    1.760    1.760 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
      100    1.644    0.016    1.644    0.016 {built-in method pygame.display.flip}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      100    0.114    0.001    0.114    0.001 {method 'fill' of 'pygame.Surface' objects}
      100    0.000    0.000    0.000    0.000 {method 'get_fps' of 'Clock' objects}
      100    0.000    0.000    0.000    0.000 {method 'tick' of 'Clock' objects}



Machine B:

$ python fps_test.py
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
<VideoInfo(hw = 0, wm = 1,video_mem = 0
         blit_hw = 0, blit_hw_CC = 0, blit_hw_A = 0,
         blit_sw = 0, blit_sw_CC = 0, blit_sw_A = 0,
         bitsize  = 32, bytesize = 4,
         masks =  (16711680, 65280, 255, 0),
         shifts = (16, 8, 0, 0),
         losses =  (0, 0, 0, 8),
         current_w = 1440, current_h = 900
>

[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 256.4102478027344, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 263.15789794921875, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 285.71429443359375, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 303.0303039550781, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719, 294.1176452636719]
         405 function calls in 0.356 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.356    0.356 <string>:1(<module>)
        1    0.003    0.003    0.356    0.356 fps_test.py:11(run)
        1    0.000    0.000    0.356    0.356 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
      100    0.201    0.002    0.201    0.002 {built-in method pygame.display.flip}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      100    0.150    0.002    0.150    0.002 {method 'fill' of 'pygame.Surface' objects}
      100    0.000    0.000    0.000    0.000 {method 'get_fps' of 'Clock' objects}
      100    0.000    0.000    0.000    0.000 {method 'tick' of 'Clock' objects}



Modified test program:
import pygame

pygame.init()
clock = pygame.time.Clock()
screen_flags = 0  # pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.FULLSCREEN
screen = pygame.display.set_mode((1440, 900), screen_flags)
pygame.display.set_caption("FPS Test")

print(pygame.display.Info())

def run():
    x = 0
    fps = [0] * 100
    while x < 100:
        screen.fill(pygame.Color(0, 0, 0))
        pygame.display.flip()
        clock.tick()
        fps[x] = clock.get_fps()
        x += 1
    print(fps)

import cProfile
cProfile.run('run()')



On Tue, Feb 18, 2020 at 6:29 PM Ian Mallett <ian@xxxxxxxxxxxxxx> wrote:
Hi,

First, there are some problems with this test code:

- The first ten framerates will be 0.0 due to what I consider a misfeature in the way pygame reports framerate; this drags down your average. Even if you ran at a nominal 60Hz, the highest reported framerate is going to be 54fps (wherefore, I'm somewhat confused that you say you got 60Hz on a different machine; perhaps you were running a bit faster; e.g. I see 62Hz a lot on certain machines).

- Averaging the framerates is a bit problematic because that is a reciprocal measure. It is better to average the frame latencies and then reciprocate that to get the average framerate.

- You're attempting to synchronize the framerate to 60Hz, this can cause aliasing issues since pygame is software-backed, not graphics backed, exactly. E.g., if your code is actually 59fps, then the fastest it can update will be 30fps. It would be better to not include the argument to `.tick(...)` at all.

Which brings us to the main reason:

- Setting millions of pixels is not a job for the CPU; it's a job for the graphics card. However, pygame runs on the CPU. As you observe, larger displays run slower. If you want good refresh-bound performance, you probably want to make a GL context and draw with that, without disabling VSync. This is, in fact, what glxgears does, which is why it's running at a reasonable framerate.

Ian