ReactiveECS vs Overseer: Reactive ECS is 4.6x faster than Overseer for 100k entities

Gesee
Fri, 27 Jun 2025 16:43:21 +0000
General Usage

Hello guys.

I recently ran a comparison between ReactiveECS.jl (RECS) and Overseer.jl, two powerful ECS (Entity-Component-System) frameworks. The results are eye-opening and show that both frameworks have unique strengths for game development and simulations. Here’s a comparison of their performance, features, and use cases, complete with benchmarks and code for you to try out!

Context

I tested a simple PhysicSystem performing a translation on a fixed number of entities, each with two components: Transform and Physic. The goal was to compare the speed and memory efficiency of RECS and Overseer for a common game engine task.

Test configuration:

OS: Windows 10

CPU: Intel Pentium T4400 @ 2.2 GHz

RAM: 2 GB DDR3

Julia: v1.10.5

Benchmarks

Here are the results for the translation task across different entity counts:

Entity Count | Overseer | RECS (without vectorization) | RECS (with vectorization)
10 | 521 ns | 13.9 µs | 14.9 µs
64 | 1.96 µs | 17.4 µs | 17.7 µs
100 | 2.86 µs | 13.5 µs | 18.2 µs
1,000 | 27.53 µs | 78.4 µs | 48.1 µs
10,000 | 265 µs | 271.1 µs | 97.6 µs
100,000 | 2.78 ms | 606.7 µs | 163.8 µs

Allocations

Overseer : 0 allocations

RECS : 6 allocations

These allocations are constant across the entities count.

Key observations:

Overseer shines for small entity counts (<4K), with lower latency (e.g., 521 ns vs 13.9 µs for 10 entities).

RECS takes the lead for larger entity counts (>4K), with 163.8 µs vs 2.78 ms for 100K entities (~17x faster with vectorization).

Even at low entity counts, RECS’s 13-80 µs is negligible compared to a 60 FPS frame budget (~16.6 ms), making both frameworks viable for games.

Entity creation:

1 entity:

Overseer: 1.587 µs (4 allocations)

RECS: 1.4 µs (8 allocations)

RECS (allocate entity): 864 ns (2 allocations)

10K entities:

Overseer: ~20 ms

RECS: ~3 ms

RECS outperforms Overseer for entity creation, especially at scale.

Entity destruction:

Overseer (schedule_delete! + delete_scheduled): 2 µs

RECS: 161 ns

Both scale linearly (O(n)), but RECS is ~12x faster for destruction.

These result shows that RECS outperforms Overseer at scale. While Overseer ourperforms RECS for a lower number of entities, especially for processing

Test Code

Here’s the code I used for the benchmarks, so you can reproduce the results:

ReactiveECS.jl

using ReactiveECS
using LoopVectorization
using BenchmarkTools

COUNT = 100_000

@component Transform begin
    x::Float32
    y::Float32
end

@component Physic begin
    velocity::Float32
end

@system PhysicSystem begin
    delta::Float32
end
@system RenderSystem

function ReactiveECS.run!(world, sys::PhysicSystem, data)
    E = world[sys]
    indices::Vector{Int} = data.value
    L = length(indices)

    transforms = E.Transform
    physics = E.Physic

    x_pos = transforms.x
    velo = physics.velocity
    dt::Float32 = sys.delta

    @inbounds for i in indices
        x_pos[i] += velo[i]*dt
    end

    return transforms
end
world = ECSManager()
phys_sys = PhysicSystem()
subscribe!(world, phys_sys, (TransformComponent, PhysicComponent))
run_system!(phys_sys)

# Create 100K entities
request_entities(world, COUNT, (TransformComponent, PhysicComponent))

# Benchmark
@btime begin
    dispatch_data(world)
    blocker(world)
end

Overseer.jl

using Overseer
using BenchmarkTools

COUNT = 100_000

@component struct Transform
    x::Float64
    y::Float64
end

@component struct Physic
    vx::Float64
end

struct PhysicSystem <: System end
Overseer.requested_components(::PhysicSystem) = (Transform, Physic)

function Overseer.update(::PhysicSystem, m::AbstractLedger)
    for e in @entities_in(m, Transform && Physic)
        e[Transform] = Transform(e.x + e.vx, e.y)
    end
end

m = Ledger(Stage(:sim, [PhysicSystem()]))

for i=1:COUNT
    Overseer.Entity(m,
        Health(50),
        Transform(-5.0, 5.0),
        Physic(1.0)
    )
end
@btime Overseer.update(m)

Strengths of ReactiveECS.jl

RECS is designed for high performance and reactive modularity, making it ideal for large-scale games or simulations.

Blazing performance:

Uses a Struct of Arrays (SoA) layout for cache-friendly access.

Leverages LoopVectorization.jl (@turbo, @simd): the test shows 163.8 µs for 100K entities.

Entity pooling (e.g., 161 ns for destruction) minimizes allocation overhead.

Reactive modularity:

The listen_to mechanism enables dynamic system pipelines, where a system can listen to the output of another.

get_into_flow allows runtime system injection, perfect for evolving game mechanics or modding.

Supports hierarchies via NodeTree.jl and advanced events via Notifyers.jl (merge, filtering, priorities).

Drawback: The package is slower than Overseer for lower entity count, mostly due to systems’s synchronization with blocker(ecs).

Strengths of Overseer.jl

Overseer is a robust, user-friendly ECS inspired by EnTT, prioritizing determinism and simplicity with solid performance.

Solid performance:

Uses contiguous vectors and SparseIntSet for efficient iteration, achieving 2.78 ms for 100K entities with 0 allocations in the test.

Scales well for small to medium entity counts (<4K).

Simplicity:

The @entities_in macro is intuitive, making it accessible to ECS newcomers.

Maturity: Used in projects like Glimpse.jl and Trading.jl, Overseer is more battle-tested than RECS, with clear documentation.

Use case: Perfect for rapid prototyping, small-to-medium games, or simulations where determinism and ease of use are key.

Drawback: Performance drops at large scales due to lack of SIMD vectorization or reactive pipelines, limiting flexibility for dynamic systems.

Discussion

Choose RECS for high-performance games or simulations with large entity counts (100K+) and dynamic, real-time systems. Its speed and modularity make it a game-changer for ambitious projects.

Choose Overseer for simpler, deterministic projects, where ease of use is a priority.

4 posts - 3 participants