Province Data Benchmark
Summary
C++ SoA (GDExtension) imports province data 5.0× faster and loops over it 109× faster than GDScript AoS (dev). GDScript SoA (Experimental) gains 1.3× and 2.6×. All three run the identical test on the same 18,148 provinces.
AoS and SoA in short
Array of Structures (AoS) stores each province as one object holding all its fields. Structure of Arrays (SoA) stores one array per field, and a province is just an index into those arrays.
# AoS: one object per province
provinces = [
{ id: "PRV1", type: LAND, x: 120, y: 40 },
{ id: "PRV2", type: OCEAN, x: 300, y: 85 },
]
# SoA: one array per field, province = index
ids = ["PRV1", "PRV2"]
types = [LAND, OCEAN]
xs = [120, 300]
ys = [40, 85]
In memory, AoS lies as id type x y | id type x y | …, while SoA lies as type type type … | x x x … | y y y …. A loop that only needs type and position reads just those arrays, so every byte it loads is one it uses.
Results
C++ SoA is up to 109× faster than GDScript AoS 18,148 provinces · identical test for all three · median time, lower is better

Every model runs the same two steps:
- Import: parse
provinces.txtinto the model, then parseprovinces.jsonand set owner and controller on each land province (from its entry, elseNNN). - Loop: walk all provinces by index and, for each land province, sum the distance from its center to the map middle.
All three return the same sum (15,397,670) and the same 14,402 owned land provinces.
The loop computes the total distance from every land province’s center to the map middle (2816, 1024).
The same steps in every model:
total = 0
for i in 0 .. province_count - 1:
if type[i] == LAND:
dx = center[i].x - 2816
dy = center[i].y - 1024
total += sqrt(dx * dx + dy * dy)
Speed-ups
Moving the data to C++ gains far more than changing the layout inside GDScript.
| Comparison | Import | Loop |
| SoA Godot vs AoS Godot | 1.3× | 2.6× |
| C++ SoA vs SoA Godot | 4.0× | 43× |
| C++ SoA vs AoS Godot | 5.0× | 109× |
| Other GDScript loop variants | Time per pass |
| SoA through getters by id | 12.6 ms |
| SoA with packed arrays | 0.69 ms |
| Empty loop (interpreter baseline) | 0.24 ms |
Looking each province up by id is 17× slower than walking the arrays by index.
Why C++ wins by so much
- No interpreter. An empty GDScript loop over 18k provinces already costs 0.24 ms. C++ does the whole loop in 0.017 ms, about 1 ns per province.
- Type is a 1-byte enum.
enum class ProvinceType : uint8_tkeeps the type column at 18 KB and makes the land check a one-byte compare. - SoA reads values in a row. The loop touches only the type and position columns instead of following a reference to each province object.
- Import is mostly parsing. In GDScript every field split and conversion goes through the interpreter. C++ parses with
std::from_charsand nlohmann::json directly into the columns.
Test setup
- Hardware: AMD Ryzen 9 5900X
- Data: 18,148 provinces, the same
provinces.txtandprovinces.jsonfor all three - Method: median of 15 imports and 200 loop passes, three full reruns; GDScript AoS and SoA runs interleaved
Author: Thomas Holtvedt
Published: 3 November 2026
Last Modified: 3 November 2026
767 words; 4 minutes to read.