Skip to content

Performance Comparisons Against Upstream Pygame

Andrew Coffey edited this page Nov 29, 2023 · 4 revisions

This section is for showing performance differences between pygame-ce and upstream pygame

pygame.transform.scale

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)

Small-XSmall.png

Small-XSmall

Small-Small.png

Small-Small

Small-SubMedium.png

Small-SubMedium

Small-Medium.png

Small-Medium

Small-SuperMedium.png

Small-SuperMedium

Small-Large.png

Small-Large

Medium-XSmall.png

Medium-XSmall

Medium-Small.png

Medium-Small

Medium-SubMedium.png

Medium-SubMedium

Medium-Medium.png

Medium-Medium

Medium-SuperMedium.png

Medium-SuperMedium

Medium-Large.png

Medium-Large

Large-XSmall.png

Large-XSmall

Large-Small.png

Large-Small

Large-SubMedium.png

Large-SubMedium

Large-Medium.png

Large-Medium

Large-SuperMedium.png

Large-SuperMedium

Large-Large.png

Large-Large