-
-
Notifications
You must be signed in to change notification settings - Fork 154
Performance Comparisons Against Upstream Pygame
This section is for showing performance differences between pygame-ce and upstream pygame
pygame.transform.scale
As of writing, the most recent release versions of pygame-ce
(2.3.2) and pygame
(2.5.2) were tested on Python 3.10, Python 3.11, and Python 3.12.
3 sizes of surface were created and randomly populated with pixels
- 10 x 10 pixels (SMALL)
- 100 x 100 pixels (MEDIUM)
- 1000 x 1000 pixels (LARGE)
6 scale factors were used
- 1.5 (XSMALL)
- 2 (SMALL)
- 5 (SUBMEDIUM)
- 10 (MEDIUM)
- 25 (SUPERMEDIUM)
- 50 (LARGE)
Each surface was scaled up by each scale factor 1,000 times and each iteration was timed with the timeit
library. In the graphs below, some of the times have been filtered out according to the following rule:
Each data set is assumed to be normally distributed. Following that assumption, each data set had its mean (μ) and standard devation (σ) calculated. Any data points that lied outside of the closed interval [μ-2σ, μ+2σ] was assumed to be an outlier and was not plotted.
The tests were performed with a main.py
script run for each python/pygame(-ce) version combo, and after all the test runs were done, visualize.py
was run to create the graphs.
main.py
from timeit import repeat
from random import randint
from platform import python_version
from os.path import exists
from os import mkdir
import json
import pygame
def get_random_color() -> tuple[int, int, int]:
r = randint(0, 255)
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
def fill_surf_with_random_pixels(surf: pygame.Surface) -> None:
cols, rows = surf.get_size()
for col in range(cols):
for row in range(rows):
surf.set_at((col, row), get_random_color())
pygame.init()
screen = pygame.display.set_mode((1, 1))
SMALL_SIZE = 10
MEDIUM_SIZE = 100
LARGE_SIZE = 1000
XSMALL_MULTIPLIER = 1.5
SMALL_MULTIPLIER = 2
SUBMEDIUM_MULTIPLIER = 5
MEDIUM_MULTIPLIER = 10
SUPERMEDIUM_MULTIPLIER = 25
LARGE_MULTIPLIER = 50
small_surf = pygame.Surface((SMALL_SIZE, SMALL_SIZE))
fill_surf_with_random_pixels(small_surf)
medium_surf = pygame.Surface((MEDIUM_SIZE, MEDIUM_SIZE))
fill_surf_with_random_pixels(medium_surf)
large_surf = pygame.Surface((LARGE_SIZE, LARGE_SIZE))
fill_surf_with_random_pixels(large_surf)
iterations = 1_000
def time_scale_with_multiplier(surf: pygame.Surface, multiplier: float) -> list[float]:
print(f"Scaling a surface of size {surf.get_size()} by {multiplier}")
new_width = surf.get_width() * multiplier
new_height = surf.get_height() * multiplier
return repeat(lambda : pygame.transform.scale(surf, (new_width, new_height)), repeat=iterations, number=1)
times = {
"Small Surface": {
"XSmall Multiplier": time_scale_with_multiplier(small_surf, XSMALL_MULTIPLIER),
"Small Multiplier": time_scale_with_multiplier(small_surf, SMALL_MULTIPLIER),
"SubMedium Multiplier": time_scale_with_multiplier(small_surf, SUBMEDIUM_MULTIPLIER),
"Medium Multiplier": time_scale_with_multiplier(small_surf, MEDIUM_MULTIPLIER),
"SuperMedium Multiplier": time_scale_with_multiplier(small_surf, SUPERMEDIUM_MULTIPLIER),
"Large Multiplier": time_scale_with_multiplier(small_surf, LARGE_MULTIPLIER)
},
"Medium Surface": {
"XSmall Multiplier": time_scale_with_multiplier(medium_surf, XSMALL_MULTIPLIER),
"Small Multiplier": time_scale_with_multiplier(medium_surf, SMALL_MULTIPLIER),
"SubMedium Multiplier": time_scale_with_multiplier(medium_surf, SUBMEDIUM_MULTIPLIER),
"Medium Multiplier": time_scale_with_multiplier(medium_surf, MEDIUM_MULTIPLIER),
"SuperMedium Multiplier": time_scale_with_multiplier(medium_surf, SUPERMEDIUM_MULTIPLIER),
"Large Multiplier": time_scale_with_multiplier(medium_surf, LARGE_MULTIPLIER)
},
"Large Surface": {
"XSmall Multiplier": time_scale_with_multiplier(large_surf, XSMALL_MULTIPLIER),
"Small Multiplier": time_scale_with_multiplier(large_surf, SMALL_MULTIPLIER),
"SubMedium Multiplier": time_scale_with_multiplier(large_surf, SUBMEDIUM_MULTIPLIER),
"Medium Multiplier": time_scale_with_multiplier(large_surf, MEDIUM_MULTIPLIER),
"SuperMedium Multiplier": time_scale_with_multiplier(large_surf, SUPERMEDIUM_MULTIPLIER),
"Large Multiplier": time_scale_with_multiplier(large_surf, LARGE_MULTIPLIER)
}
}
if not exists("raw_stats"):
mkdir("raw_stats")
filename = f"raw_stats/({python_version()})"
if not hasattr(pygame, "IS_CE"):
filename += "pygame-output.json"
else:
filename += "pygame-ce-output.json"
with open(filename, "w") as dump_file:
json.dump(times, dump_file, indent=4)
visualize.py
import json
from statistics import mean, stdev
from os.path import exists
from os import mkdir
import matplotlib.pyplot as plt
upstream_data = {}
ce_data = {}
def filter_outliers(data: list[float]) -> list[float]:
data_mean = mean(data)
data_sigma = stdev(data)
filtered_data = [point for point in data if abs(point-data_mean) <= 2 * data_sigma]
return filtered_data
if not exists("figures"):
mkdir("figures")
for python_version in ["3.10.11", "3.11.5", "3.12.0"]:
with open(f"raw_stats/({python_version})pygame-output.json", "r") as upstream:
upstream_data = json.load(upstream)
with open(f"raw_stats/({python_version})pygame-ce-output.json", "r") as ce:
ce_data = json.load(ce)
for size in ["Small", "Medium", "Large"]:
for scale in ["XSmall", "Small", "SubMedium", "Medium", "SuperMedium", "Large"]:
upstream = filter_outliers(upstream_data[f"{size} Surface"][f"{scale} Multiplier"])
ce = filter_outliers(ce_data[f"{size} Surface"][f"{scale} Multiplier"])
fig = plt.figure()
fig.set_size_inches((fig.get_size_inches()[0], fig.get_size_inches()[1]+1))
plt.plot(range(len(upstream)), upstream)
plt.plot(range(len(ce)), ce)
plt.legend(["Upstream Pygame 2.5.2", "Pygame-ce 2.3.2"])
plt.xlabel("Iteration")
plt.ylabel("time taken (seconds)")
title = f"Pygame vs Pygame-ce pygame.transform.scale\n{python_version = }\n"
match size:
case "Small":
title += "10x10 pixel source surface, "
case "Medium":
title += "100x100 pixel source surface, "
case "Large":
title += "1000x1000 pixel source surface, "
match scale:
case "XSmall":
title += "1.5x scale"
case "Small":
title += "2x scale"
case "SubMedium":
title += "5x scale"
case "Medium":
title += "10x scale"
case "SuperMedium":
title += "25x scale"
case "Large":
title += "50x scale"
plt.title(title)
plt.savefig(f"figures/{size}-{scale}.png")
plt.close()
note: visualize.py
has hardcoded python versions because that is what was installed on my system to use, and what main.py
saved the files as. I could have written a parser for it to generalize, but I deemed that not worth the effort right now.
Now for the result graphs (names are in the format ${SURFACE_SIZE}-${SCALE_FACTOR}.png
)