Go ECS Benchmarks

August 21, 2026 · View on GitHub

Comparative benchmarks for Go Entity Component System (ECS) implementations.

Disclaimer: This repository is maintained by the author of Ark.

Benchmark candidates

ECSTestedLatestActivity
Arkv0.8.3GitHub Tag GitHub Release DateLast commit
Donburiv1.15.7GitHub Tag GitHub Release DateLast commit
go‑gameengine‑ecsv0.9.0GitHub Tag GitHub Release DateLast commit
GOKev3.1.0GitHub Tag GitHub Release DateLast commit
unitoftime/ecsv0.0.3GitHub Tag GitHub Release DateLast commit
Voltv1.7.0GitHub Tag GitHub Release DateLast commit

Candidates are always displayed in alphabetical order.

In case you develop or use a Go ECS that is not in the list and that want to see here, please open an issue or make a pull request. See the section on Contributing for details.

In case you are a developer or user of an implementation included here, feel free to check the benchmarked code for any possible improvements. Open an issue if you want a version update.

Features

ECSType-safe APIID-based APIRelationsEvents[1]Batches[2]Command buffer
Ark
Donburi
go‑gameengine‑ecs
GOKe
unitoftime/ecs
Volt

[1] ECS lifecycle events, allowing to react to entity creation, component addition, ...
[2] Faster batch operations for entity creation etc.

Benchmarks

Last run: Fri, 21 Aug 2026 21:48:28 UTC
CPU: INTEL(R) XEON(R) PLATINUM 8573C

For each benchmark, the left plot panel and the table show the time spent per entity, while the right panel shows the total time.

Note that the Y axis has logarithmic scale in all plots. So doubled bar or line height is not doubled time!

All components used in the benchmarks have two float64 fields. The initial capacity of the world is set to 1024 where this is supported.

Query

N entities with components Position and Velocity. 10x N entities with components Position.

  • Query all [Position, Velocity] entities, and add the velocity vector to the position vector.

query2comp

NArkArk (tables)DonburiggecsGOKeuotVolt
159.79ns59.16ns51.44ns36.76ns11.56ns10.08ns62.92ns
416.72ns14.97ns23.86ns10.80ns3.46ns4.03ns16.46ns
164.76ns4.02ns17.53ns4.72ns1.26ns2.42ns4.86ns
642.60ns1.52ns16.23ns3.53ns0.76ns2.15ns2.19ns
2561.26ns0.75ns15.56ns3.02ns0.54ns2.27ns1.36ns
1k1.07ns0.57ns15.45ns2.97ns0.50ns1.95ns1.25ns
16k1.09ns0.70ns17.60ns3.20ns0.68ns1.93ns1.13ns
256k1.39ns1.12ns17.18ns3.05ns1.16ns2.14ns1.33ns
1M1.52ns1.08ns25.83ns2.94ns1.17ns2.17ns1.38ns

Note: Donburi, unitoftime/ecs, and Volt use a callback-based approach for their query loops. As a result, iteration speed may degrade if the callback contains complex logic and the Go compiler is unable to inline it.

Query fragmented, inner

Query where the matching entities are fragmented over 32 archetypes.

N entities with components Position and Velocity. Each of these N entities has some combination of components C1, C2, ..., C5, so entities are fragmented over up to 32 archetypes.

  • Query all [Position, Velocity] entities, and add the velocity vector to the position vector.

query32arch

NArkArk (tables)DonburiGOKeggecsuotVolt
157.15ns59.05ns49.64ns12.08ns40.94ns9.96ns66.58ns
420.79ns24.97ns31.45ns10.50ns13.01ns8.77ns58.19ns
1613.29ns18.61ns27.30ns9.33ns7.57ns12.98ns50.92ns
647.32ns9.66ns20.77ns5.01ns5.11ns7.87ns26.47ns
2563.56ns3.35ns18.78ns2.46ns3.83ns3.74ns7.67ns
1k1.90ns1.33ns15.97ns1.03ns3.28ns2.37ns3.32ns
16k1.12ns0.74ns23.30ns0.72ns3.12ns1.98ns1.25ns
256k1.44ns1.12ns26.54ns1.21ns2.89ns2.17ns1.41ns
1M1.41ns1.08ns64.88ns1.23ns2.86ns2.18ns1.40ns

Note: Donburi, unitoftime/ecs, and Volt use a callback-based approach for their query loops. As a result, iteration speed may degrade if the callback contains complex logic and the Go compiler is unable to inline it.

Query fragmented, outer

Query where there are 256 non-matching archetypes.

N entities with components Position and Velocity. Another 4 * N entities with Position and some combination of 8 components C1, ..., C8, so these entities are fragmented over up to 256 archetypes.

  • Query all [Position, Velocity] entities, and add the velocity vector to the position vector.

query256arch

NArkArk (tables)DonburiggecsGOKeuotVolt
157.05ns65.85ns52.01ns46.78ns11.52ns10.16ns81.13ns
416.66ns14.68ns23.90ns20.41ns3.67ns5.59ns45.22ns
164.76ns4.12ns17.90ns12.36ns1.31ns2.79ns33.22ns
642.31ns1.46ns16.22ns11.34ns0.79ns2.47ns30.97ns
2561.41ns0.77ns15.64ns5.04ns0.56ns2.16ns8.73ns
1k1.06ns0.58ns17.50ns3.47ns0.50ns1.95ns2.97ns
16k1.07ns0.71ns16.70ns2.87ns0.69ns1.93ns1.20ns
256k1.49ns1.09ns17.53ns2.88ns1.17ns2.28ns1.33ns
1M1.45ns1.08ns19.10ns2.93ns1.15ns2.22ns1.35ns

Note: Donburi, unitoftime/ecs, and Volt use a callback-based approach for their query loops. As a result, iteration speed may degrade if the callback contains complex logic and the Go compiler is unable to inline it.

Component random access

N entities with component Position. All entities are collected into a slice, and the slice is shuffled.

  • Iterate the shuffled entities.
  • For each entity, get its Position and sum up their X fields.

random

NArkDonburiGOKeggecsuotVolt
12.84ns8.05ns2.96ns8.20ns34.91ns14.06ns
43.10ns7.96ns3.10ns8.46ns34.58ns13.84ns
164.35ns7.93ns3.66ns13.12ns35.08ns13.77ns
642.84ns8.03ns3.13ns13.78ns37.39ns13.88ns
2562.85ns8.36ns3.09ns13.46ns39.04ns13.77ns
1k2.93ns11.45ns3.27ns16.78ns40.81ns13.76ns
16k6.46ns38.97ns5.58ns28.62ns60.72ns18.62ns
256k9.08ns180.70ns14.22ns117.61ns176.19ns35.91ns
1M47.92ns260.90ns51.08ns165.57ns241.18ns124.68ns

Create entities

  • Create N entities with components Position and Velocity.

The operation is performed once before benchmarking, to exclude memory allocation, archetype creation etc. See the benchmark below for entity creation with allocation.

create2comp

NArkArk (batch)DonburiggecsGOKeuotVolt
1179.94ns175.45ns991.84ns257.70ns175.20ns334.01ns469.15ns
471.06ns51.08ns509.31ns143.51ns47.52ns194.85ns244.46ns
1645.76ns19.90ns337.48ns160.20ns16.07ns161.63ns162.27ns
6440.92ns14.16ns229.91ns131.52ns6.80ns139.47ns115.30ns
25630.44ns13.27ns196.29ns93.92ns5.45ns107.22ns73.12ns
1k29.76ns12.15ns182.12ns90.58ns4.63ns273.57ns76.12ns
16k23.60ns8.80ns193.90ns94.80ns8.68ns276.95ns73.82ns
256k25.19ns8.72ns194.94ns196.40ns10.81ns463.05ns72.84ns
1M25.36ns9.23ns168.86ns211.85ns13.56ns480.03ns71.94ns

Create entities, allocating

  • Create N entities with components Position and Velocity.

Each round is performed on a fresh world. This reflects the creation of the first entities with a certain components set in your game or application. As soon as things stabilize, the benchmarks for entity creation without allocation (above) apply.

Low N values might be biased by things like archetype creation and memory allocation, which is handled differently by different implementations.

create2comp_alloc

NArkArk (batch)DonburiGOKeggecsuotVolt
19.18us9.33us5.84us90.03ns19.71us3.44us2.04us
42.36us2.33us1.72us88.16ns4.50us1.19us810.46ns
16636.81ns596.54ns721.39ns58.71ns1.28us475.51ns329.60ns
64186.66ns153.98ns395.64ns65.85ns498.43ns267.52ns182.96ns
25670.56ns53.45ns285.43ns52.63ns315.50ns200.83ns125.45ns
1k38.32ns23.51ns277.38ns58.49ns149.86ns160.65ns101.08ns
16k48.66ns39.29ns401.29ns68.05ns233.62ns182.10ns123.20ns
256k61.76ns39.38ns474.32ns56.99ns652.15ns259.82ns136.58ns
1M51.18ns36.64ns433.97ns56.68ns2.00us344.44ns136.67ns

Create large entities

  • Create N entities with 10 components C1, ..., C10.

The operation is performed once before benchmarking, to exclude things like archetype creation and memory allocation.

create10comp

NArkArk (batch)DonburiggecsGOKeuotVolt
1179.69ns169.51ns1.85us346.60ns182.97ns518.08ns2.16us
472.04ns50.23ns1.07us237.84ns51.14ns442.95ns1.66us
1648.69ns20.25ns851.23ns281.21ns16.15ns318.45ns1.30us
6441.05ns13.76ns613.56ns159.59ns6.84ns291.21ns894.73ns
25639.23ns11.84ns539.85ns135.60ns5.87ns205.10ns817.21ns
1k26.32ns10.02ns546.31ns137.61ns22.55ns427.09ns791.10ns
16k24.49ns8.79ns621.91ns146.35ns22.14ns530.53ns796.29ns
256k24.85ns8.90ns543.08ns249.92ns24.43ns616.20ns782.94ns
1M24.93ns9.27ns623.29ns261.40ns28.69ns629.36ns791.34ns

Add/remove component

N entities with component Position.

  • Add Velocity to all entities.
  • Remove Velocity from all entities.

One iteration is performed before the benchmarking starts, to exclude memory allocation.

add_remove

NArkArk (batch)DonburiggecsGOKeuotVolt
197.57ns167.68ns399.10ns416.70ns472.89ns342.68ns259.53ns
4107.20ns45.52ns405.35ns419.92ns130.79ns338.46ns266.61ns
16107.08ns17.76ns402.16ns451.89ns44.01ns348.94ns265.20ns
64106.93ns10.75ns403.83ns452.29ns22.76ns350.81ns265.18ns
256105.62ns8.70ns396.26ns452.83ns16.87ns360.57ns262.34ns
1k104.14ns8.70ns400.32ns467.81ns17.14ns733.56ns261.68ns
16k103.57ns9.49ns432.37ns506.61ns35.74ns793.13ns263.08ns
256k104.54ns10.61ns405.65ns820.63ns44.41ns1.24us253.50ns
1M103.61ns11.74ns407.66ns939.90ns46.10ns1.41us254.93ns

Add/remove component, large entity

N entities with component Position and 10 further components C1, ..., C10.

  • Add Velocity to all entities.
  • Remove Velocity from all entities.

One iteration is performed before the benchmarking starts, to exclude memory allocation.

add_remove_large

NArkArk (batch)DonburiggecsGOKeuotVolt
1337.06ns440.33ns1.11us843.89ns651.69ns834.47ns1.39us
4385.52ns122.62ns1.14us870.82ns179.78ns853.43ns1.36us
16389.82ns47.93ns1.12us890.39ns65.20ns827.53ns1.34us
64392.16ns25.45ns1.11us868.04ns37.36ns873.81ns1.34us
256381.17ns22.10ns1.10us880.77ns38.00ns910.07ns1.33us
1k414.88ns20.55ns1.17us898.10ns89.89ns1.47us1.38us
16k434.18ns22.26ns1.26us1.09us117.90ns1.58us1.54us
256k538.60ns45.43ns1.33us1.55us138.98ns2.03us1.58us
1M478.06ns41.55ns1.31us1.63us177.46ns2.08us1.59us

Delete entities

N entities with components Position and Velocity.

  • Delete all entities

delete2comp

NArkArk (batch)DonburiGOKeggecsuotVolt
194.23ns143.17ns227.67ns1.08us411.38ns109.61ns254.69ns
447.85ns39.70ns93.18ns259.69ns194.07ns56.98ns142.49ns
1647.68ns14.29ns67.87ns78.04ns147.26ns47.63ns96.05ns
6433.66ns6.55ns45.00ns25.42ns100.31ns38.18ns62.08ns
25625.31ns4.82ns36.54ns13.78ns77.28ns32.80ns44.18ns
1k22.21ns3.61ns28.39ns11.11ns72.89ns27.76ns43.58ns
16k15.67ns3.19ns27.98ns11.98ns72.83ns28.95ns33.78ns
256k16.34ns3.36ns27.29ns12.72ns147.86ns52.64ns34.30ns
1M16.73ns3.50ns28.10ns13.60ns275.72ns74.35ns35.64ns

Delete large entities

N entities with 10 components C1, ..., C10.

  • Delete all entities

delete10comp

NArkArk (batch)DonburiggecsGOKeuotVolt
1159.79ns216.83ns397.73ns502.88ns1.04us107.37ns669.10ns
4125.20ns62.50ns215.90ns307.89ns268.46ns52.73ns480.52ns
16114.93ns23.00ns132.91ns231.65ns90.06ns49.28ns287.82ns
6477.14ns10.79ns98.10ns146.53ns33.43ns35.30ns213.86ns
25677.26ns8.11ns84.74ns127.62ns18.50ns31.10ns189.54ns
1k52.30ns6.64ns83.14ns105.62ns27.63ns28.63ns181.23ns
16k49.20ns7.62ns83.70ns117.51ns21.30ns26.48ns180.49ns
256k72.84ns7.71ns77.61ns254.58ns31.88ns52.12ns180.53ns
1M79.52ns15.11ns148.99ns446.70ns56.77ns68.81ns182.33ns

Create world

  • Create a new world
NArkDonburiGOKeggecsuotVolt
134.07us3.51us925.95us276.03us3.43us23.36us

Popularity

Given that all tested projects are on Github, we can use the star history as a proxy here.

Star History Chart

Running the benchmarks

Run the benchmarks using the following command:

go run . -test.benchtime=10x

On PowerShell use this instead:
go run . --% -test.benchtime=10x

The benchtime limit is required for some of the benchmarks that have a high setup cost which is not timed. They would take forever otherwise. The benchmarks can take up to one hour to complete.

To run a selection of benchmarks, add their names as arguments:

go run . query2comp query32arch

To create the plots, run plot/plot.py. The following packages are required:

  • numpy
  • pandas
  • matplotlib
pip install -r ./plot/requirements.txt
python plot/plot.py

Contributing

Developers of ECS frameworks are welcome to add their implementation to the benchmarks. However, there are a few (quality) criteria that need to be fulfilled for inclusion:

  • All benchmarks must be implemented, which means that the ECS must have the required features
  • The ECS must be working properly and not exhibit serious flaws; it will undergo a basic review by maintainers
  • The ECS must be sufficiently documented so that it can be used without reading the code
  • There must be at least basic unit tests
  • Unit tests must be run in the CI of the repository
  • The ECS must not be tightly coupled to a particular game engine, particularly graphics stuff
  • There must be tagged release versions; only tagged versions will be included here

Developers of included frameworks are encouraged to review the benchmarks, and to fix (or point to) misuse or potential optimizations.

Automated results PRs

Pull requests from the bench-results-update branch are opened automatically by the publish job in .github/workflows/benchmarks.yml, after a benchmark run on main — they only refresh README.md and docs/results/** (the plotted numbers/images), never benchmark code, and don't need the code-review criteria above. That branch is force-pushed on every run, so there's at most one such PR open at a time, reused rather than duplicated. Everything else (bench/**, go.mod, workflow, docs) is normal work, reviewed as usual.