Skip to content

Performance

The compiler is measured against clang on nineteen programs, each written twice: once in the xc language and once in Objective-C with ARC, doing the same work with the same algorithm. Both are built at -O3 and both print a checksum, and a run only counts if the two checksums agree.

Ratios are xcc time divided by clang time, so lower is faster and 1.00 means parity.

arm64x86-64
Geometric mean0.931.03
Arithmetic mean1.061.15
Within 0.3x-2.0x of clang18 of 1916 of 19

On arm64 the suite is slightly faster than clang overall. On x86-64 it is within three percent.

The geometric mean is the one to read. Averaging ratios arithmetically is misleading: a benchmark at 2.00x and one at 0.50x are exactly compensating, and their arithmetic mean is 1.25x while their geometric mean is 1.00x. The arithmetic figure is given only for completeness.

Times are seconds for the timed region, best of five, measured with the released 0.62 xcc. Each run waits until the machine doing the timing is otherwise idle.

benchmarkarm64 xccarm64 clangratiox86-64 xccx86-64 clangratio
arc_array0.995.560.181.163.720.31
method_call0.962.880.331.351.900.71
string_scan1.002.620.381.623.150.52
arc_alloc1.031.410.730.952.060.46
sieve1.211.580.771.850.812.30
call_depth1.011.070.941.060.951.11
array_sum0.960.961.001.201.440.84
hash_mix1.261.231.031.011.120.91
branch_mix1.221.101.110.820.811.01
int_accum1.421.271.120.970.971.00
bit_ops1.421.271.130.970.971.00
poly_dispatch1.371.201.140.971.230.79
struct_copy1.331.091.211.130.791.44
int_muldiv1.231.021.212.021.701.19
sort_small1.331.031.292.241.491.50
array_map1.691.181.432.121.571.35
float_math1.260.851.481.510.742.03
mem_copy1.680.941.801.541.051.46
matrix_mul1.831.001.832.431.202.02

The fastest results are where the runtime does the work: arc_array, method_call and string_scan are reference counting, dynamic dispatch and string scanning, and those are library code rather than generated code. The slowest are sieve, float_math and matrix_mul on x86-64, all of which clang vectorises more aggressively than xcc does.

The compiler that ships. The numbers come from the xcc in the download.

Two different Objective-C runtimes. The clang column is Apple’s Foundation and objc_msgSend on arm64/macOS, and GNUstep with libobjc2 on x86-64/Linux. Those are different implementations of dispatch and of reference counting, so the clang times compare within a platform and not across one. The same is true of the ratios that come from them.

Timed regions of about one second. Each benchmark times its own inner loop rather than the process, and the loops are sized so the region runs for roughly a second. Shorter runs were tried and abandoned: at a few milliseconds the measurement is dominated by everything that is not the program.

Alignment noise on x86-64. Before 0.61, an individual x86-64 figure could move by ten to fifteen percent between builds whose hot function was instruction-for-instruction identical, because where a loop landed relative to a 32-byte fetch boundary depended on how much unrelated code preceded it. xcc now aligns every loop head on x86-64 to a 32-byte boundary and the start of .text to 64 bytes, which fixes where a loop lands; see Optimisation. The summary is still a geometric mean over nineteen programs rather than any single number.

The benchmark sources are in benchmark/src, one .xc and one .m per program, and the runner builds and times both:

python3 benchmark/run.py --version v0.62 --opt O3 --repeats 5

The x86-64 legs cross-build here and run on a configured Linux host; without one the runner measures the local platform only and says so. Results land in benchmark/<version>/results.json.