Optimization Tips
PythonExtra is fast enough for real-time games — if you’re careful.
But if your app starts lagging, there are three usual culprits:
- Drawing too much
- Inefficient algorithms
- Memory allocation & GC abuse
This page shows how to spot those issues and fix them, with concrete examples.
1. Avoid Unnecessary Overdraw
Section titled “1. Avoid Unnecessary Overdraw”You can easily get 20+ FPS with full-screen redraws — as long as you’re not redrawing what doesn’t need to change.
❌ Bad: Redrawing things you’re about to overwrite
Section titled “❌ Bad: Redrawing things you’re about to overwrite”# Clear the screen, then draw a full tileset right over itdrect(0, 0, DWIDTH-1, DHEIGHT-1, C_WHITE)
for y in range(0, DHEIGHT, 16): for x in range(0, DWIDTH, 16): drect(x, y, x+15, y+15, C_GRAY) # Overwrites previous draw
You’re wasting time filling pixels with white only to overwrite them immediately.
✅ Better: Just draw the tiles
Section titled “✅ Better: Just draw the tiles”for y in range(0, DHEIGHT, 16): for x in range(0, DWIDTH, 16): drect(x, y, x+15, y+15, C_GRAY)
Unless you need to clear parts of the screen (like UI or old objects), don’t redraw them.
2. Avoid Rebuilding Data Structures
Section titled “2. Avoid Rebuilding Data Structures”Constantly creating new lists/tuples/objects every frame leads to spikes in garbage collection, especially if they’re small.
❌ Bad: Reallocating every frame
Section titled “❌ Bad: Reallocating every frame”# Builds a new list of coords every timepixels = [(x * 10, y * 10) for x, y in snake]
This creates N tuples per frame, and GC hates it.
✅ Better: Use preallocated buffers (but still not perfect)
Section titled “✅ Better: Use preallocated buffers (but still not perfect)”# Still makes tuples, but reuses the listpixels = [(0, 0)] * 100
for i, (x, y) in enumerate(snake): pixels[i] = (x * 10, y * 10)
✅✅ Even better: Use flat bytes
or bytearray
Section titled “✅✅ Even better: Use flat bytes or bytearray”# 100 pairs = 200 bytesbuf = bytearray(200)
for i, (x, y) in enumerate(snake): buf[i*2] = x buf[i*2+1] = y
bytes
/bytearray
are 4x cheaper per value than tuples- Especially useful when values fit in 0–255 range (like grid coordinates or sprite IDs)
- You get CPU cache friendliness and less GC overhead
3. Avoid Allocating Thousands of Small Values
Section titled “3. Avoid Allocating Thousands of Small Values”If you’re building a huge array of tiny tuples, you’re feeding the garbage collector a buffet of objects it has to track and clean.
❌ Bad: 1,000 tiny tuples
Section titled “❌ Bad: 1,000 tiny tuples”positions = [(x, x) for x in range(1000)]
That’s 1,000 allocations — and it gets worse if you do this in a loop.
✅ Better: Use a flat array or shared buffer
Section titled “✅ Better: Use a flat array or shared buffer”positions = bytearray(2000)
for i in range(1000): positions[i*2] = i % 256 positions[i*2+1] = i // 4
- Uses one object instead of 1,000
- Fits better in memory
- No GC required
This trick is essential for tilemaps, image buffers, path grids, etc.
4. Avoid Doing Heavy Work Every Frame
Section titled “4. Avoid Doing Heavy Work Every Frame”❌ Bad: Sorting a list every tick
Section titled “❌ Bad: Sorting a list every tick”def find_closest(entities): return sorted(entities, key=distance)[0]
# In the main loopclosest = find_closest(enemies)
Even for 30–50 items, this adds up fast.
✅ Better: Cache or stagger expensive work
Section titled “✅ Better: Cache or stagger expensive work”if frame % 10 == 0: closest = min(enemies, key=distance)
Spread expensive calculations across frames, or only update when data changes.
TL;DR – What to Watch
Section titled “TL;DR – What to Watch”Problem | Avoid this | Do this instead |
---|---|---|
Overdrawing | Clearing then overdrawing | Only draw what’s visible/changed |
GC abuse | Allocating lists/tuples constantly | Preallocate or use bytearray |
Algorithm bottlenecks | Sorting/pathfinding every frame | Update on timer or when needed only |
Extra Notes
Section titled “Extra Notes”drect()
includes both x2/y2 — so full-screen isdrect(0, 0, DWIDTH-1, DHEIGHT-1, color)
dupdate()
is mandatory to display changes. Call it once per frame.- For input-heavy loops,
clearevents()
andcleareventflips()
are your friends. - Partial redraws (only updating specific screen regions) can improve performance but are tricky. Unless you’re drawing fewer than 3 things per frame, full redraw is simpler and usually fast enough.
Need help diagnosing your frame drops?
Ask the 👨💻 PythonExtra ChatGPT — just paste your loop or logic!