Ronan Arraes Jardim Chagas

The Performance of SatelliteToolbox.jl SGP4/SDP4 for Operational Activities

The SatelliteToolbox.jl ecosystem is a set of packages for the Julia language that enables a wide range of analyses for space missions, from mission design studies to the day-to-day activities of an operational flight dynamics team.

Among those activities, few are as demanding on a single algorithm as orbit propagation with SGP4/SDP4. The catalog of known space objects is distributed as mean elements — TLEs and, more recently, CCSDS orbit mean-elements messages (OMM) — and those elements only have meaning when propagated with the SGP4/SDP4 algorithm they were fitted for. Collision detection is the clearest example of why its performance matters: to screen a satellite for conjunctions, we must propagate its orbit against every object in the known space object database over the entire screening window, and repeat the whole process every time new element sets are published. At that scale, the cost of a single propagation call is multiplied by tens of millions, and the propagator’s speed directly determines how often and how deeply an operations team can screen its fleet.

SatelliteToolbox.jl covers this workflow end to end: it fetches mean elements from the most common databases (space-track.org and Celestrak) in both TLE and OMM formats, and implements the SGP4/SDP4 algorithm to propagate them.

This raises a question I hear often: can a Julia implementation match the performance of the established SGP4 implementations used in operational contexts? To answer it, I benchmarked the SGP4 propagator of SatelliteToolbox.jl against three other implementations:

LanguageLibraryVersion
JuliaSatelliteToolbox.jl1.0.0
C++Vallado’s reference implementation (via CelesTrak)latest
Rustsgp4-rs2.4.0
JavaOrekit13.0.3

Vallado’s C++ code is the reference against which SGP4 implementations are traditionally verified, Orekit is the de facto standard library for operational flight dynamics on the JVM, and sgp4-rs is a modern, well-tested Rust implementation.

Methodology#

Every benchmark follows exactly the same protocol:

  1. Parse the shared TLE — the classic ISS test case from Vallado’s SGP4 verification set — and initialize the propagator once, outside the timed region.
  2. Warm up with 10 untimed propagation calls.
  3. Time 1,000,000 propagation calls with the offset cycling over one day in 1-minute steps: t = (i mod 1441) minutes since the TLE epoch. The timed loop runs 5 times and the fastest repetition is reported, suppressing OS scheduling, CPU frequency-ramp, and JIT noise.
  4. Accumulate the sum of all position components as a checksum, which prevents the compiler from eliminating the loop and cross-validates the implementations.

The TLE used in all benchmarks is:

ISS (ZARYA)
1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927
2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537

All implementations use the WGS-72 gravity model (the TLE standard) and output the state vector in the TEME frame. The Rust benchmark uses the from_elements_afspc_compatibility_mode constructor of sgp4-rs so that its epoch and sidereal-time handling matches the other implementations. With this configuration, all four implementations produce bit-identical checksums, so we are comparing the exact same computation.

Each benchmark goes through the library’s public “propagate at time t” API, including whatever per-call objects that implies. This measures what a user actually gets, not a hand-tuned inner kernel.

The machine is a MacBook Pro with an Apple M3 Pro and 18 GB of RAM, running macOS 26.6.1, Julia 1.12.6, Apple clang 21, rustc 1.97.1, and OpenJDK 26.0.2.

Results#

LanguageLibraryCallsTotal [ms]Time / Call [ns]
Rustsgp4-rs1,000,000176.152176.2
C++Vallado SGP41,000,000176.849176.8
JuliaSatelliteToolbox.jl1,000,000180.870180.9
JavaOrekit1,000,0001174.5131174.5

The first conclusion is that C++, Rust, and Julia are statistically tied, at roughly 175–180 ns per propagation. The run-to-run variation of the three fastest implementations on this machine is around ±4% and their ordering shuffles between executions. Any ranking among them within a few nanoseconds is not meaningful.

The second conclusion is that Orekit is about 6.5× slower than the other three. This is not a JIT problem: the JVM is fully warmed up when the reported repetition runs.

What This Means Operationally#

At ~180 ns per propagation, SatelliteToolbox.jl can propagate the entire public catalog (~30,000 objects) at 1-minute resolution over a full day (about 43 million propagations) in under 8 seconds on a single core. Since the propagations are independent, this scales trivially across threads.

In other words, the SGP4 implementation of SatelliteToolbox.jl performs at the level of the C++ reference implementation while keeping all the advantages of working in Julia: interactive analysis, composability with the rest of the ecosystem, and no two-language problem between prototyping and operations.