Benchmark lab / Before–After comparison

libmaxminddb lookups, measured
on AMD EPYC 7763 64-Core Processor

Before 88ad5f46d9c884fc4a8533374b457cb9e3ee121f (upstream main) versus After fd08b229052ae34e9f2c3c3df4f64ed9d43f4347 (three commits: specialized packed traversal, allocation-free short MMDB_get_value() paths, then automatic radix-16 preparation with aligned native child records). Same harness source, identical compiler flags, static libraries, 120 measured runs and 1.20 billion measured lookups.

CPU AMD EPYC 7763 64-Core ProcessorArchitecture x86-64Cores 2 / 4 threadsOS Ubuntu 24.04.5 LTS · Linux 7.0.0-1017-azureCompiler GCC 13.3.0 -O3 -DNDEBUGRepetitions 5 per configuration, alternating orderRecorded 2026-10-31T12:13:34+00:00
GeoLite2-City throughput
×1.89
+89.0% geometric mean of 8 configurations · 95% CI ×1.77–1.97 · 8 faster, 0 slower, 0 inconclusive
Random lookups, GeoLite2-City
×2.22
4 configurations (IPv4/IPv6 × 1/2 threads, 10M queries) · range ×1.92–×2.59
p99 latency, GeoLite2-City
4/4
configurations with a lower pooled p99 · geometric-mean p99 ratio ×0.65 · 0 higher
Open + preparation cost
26.3 ms
GeoLite2-City MMDB_open, warm cache (Before 0.087 ms)
Additional reader memory
+33.1 MiB
GeoLite2-City heap after open · process RSS after open 3.4 MiB → 64.6 MiB

All values come from x86_64/performance-comparison-data/summary.json. Speedup = After / Before median throughput; change = (speedup − 1) × 100; latency reduction = (1 − After / Before) × 100. Hover or focus any bar, candle or cell for exact values; every value is also in the tables below.

Throughput at 10M queries

Total work is fixed: 1 worker performs 10,000,000 lookups, 2 workers 5,000,000 each.

IPv4 Throughput — Random Lookups (10M)

GeoLite2-City · uniformly random addresses (85.9% hits) · MMDB_lookup_sockaddr · 10,000,000 queries in total, split across 1 and 2 pinned workers · median of 5 runs, 95% bootstrap CI

IPv4 Throughput — Random Lookups (10M). GeoLite2-City · uniformly random addresses (85.9% hits) · MMDB_lookup_sockaddr · 10,000,000 queries total↑ Higher is betterBefore · 88ad5f4After · fd08b22Speedup (After / Before)1 thread10,000,000 per workerBefore4.64 M/sAfter11.62 M/s▲×2.51 +150.7%95% CI ×1.94–2.82 · faster2 threads5,000,000 per workerBefore12.55 M/sAfter32.55 M/s▲×2.59 +159.4%95% CI ×1.91–3.47 · faster010203040Aggregate throughput (M lookups/s, higher is better)

IPv4 Throughput — Random Lookups + Field Decode (10M)

GeoLite2-City · uniformly random addresses (85.9% hits) · MMDB_lookup_sockaddr + MMDB_get_value(country → iso_code) · 10,000,000 queries in total, split across 1 and 2 pinned workers · median of 5 runs, 95% bootstrap CI

IPv4 Throughput — Random Lookups + Field Decode (10M). GeoLite2-City · uniformly random addresses (85.9% hits) · MMDB_lookup_sockaddr + MMDB_get_value(country → iso_code) · 10,000,000 queries total↑ Higher is betterBefore · 88ad5f4After · fd08b22Speedup (After / Before)1 thread10,000,000 per workerBefore2.42 M/sAfter3.27 M/s▲×1.35 +35.3%95% CI ×1.19–1.67 · faster2 threads5,000,000 per workerBefore5.18 M/sAfter6.98 M/s▲×1.35 +34.8%95% CI ×1.11–1.62 · faster0246810Aggregate throughput (M lookups/s, higher is better)

IPv6 Throughput — Random Lookups (10M)

GeoLite2-City · uniformly random addresses (0.013% hits) · MMDB_lookup_sockaddr · 10,000,000 queries in total, split across 1 and 2 pinned workers · median of 5 runs, 95% bootstrap CI

IPv6 Throughput — Random Lookups (10M). GeoLite2-City · uniformly random addresses (0.013% hits) · MMDB_lookup_sockaddr · 10,000,000 queries total↑ Higher is betterBefore · 88ad5f4After · fd08b22Speedup (After / Before)1 thread10,000,000 per workerBefore31.03 M/sAfter59.68 M/s▲×1.92 +92.3%95% CI ×1.91–1.93 · faster2 threads5,000,000 per workerBefore62.13 M/sAfter119.8 M/s▲×1.93 +92.7%95% CI ×1.85–1.99 · faster050100150Aggregate throughput (M lookups/s, higher is better)

IPv6 Throughput — Random Lookups + Field Decode (10M)

GeoLite2-City · uniformly random addresses (0.013% hits) · MMDB_lookup_sockaddr + MMDB_get_value(country → iso_code) · 10,000,000 queries in total, split across 1 and 2 pinned workers · median of 5 runs, 95% bootstrap CI

IPv6 Throughput — Random Lookups + Field Decode (10M). GeoLite2-City · uniformly random addresses (0.013% hits) · MMDB_lookup_sockaddr + MMDB_get_value(country → iso_code) · 10,000,000 queries total↑ Higher is betterBefore · 88ad5f4After · fd08b22Speedup (After / Before)1 thread10,000,000 per workerBefore30.89 M/sAfter59.24 M/s▲×1.92 +91.8%95% CI ×1.91–1.93 · faster2 threads5,000,000 per workerBefore61.29 M/sAfter118.1 M/s▲×1.93 +92.7%95% CI ×1.89–2.00 · faster050100150Aggregate throughput (M lookups/s, higher is better)

Latency distribution

Separate latency campaign: one actual operation in every 16 is timed individually; workers are merged and repetitions pooled before percentiles are computed. Lower whisker min → p50, body p50 → p95, upper whisker p95 → p99, ◆ p50; the maximum is listed at the right of each row and in tooltips.

Candlestick Percentile Rank — IPv4 Lookups (10M)

GeoLite2-City · uniformly random addresses (85.9% hits) · MMDB_lookup_sockaddr · 10,000,000 queries in total · pooled samples of 5 repetitions per bar

Candlestick Percentile Rank — IPv4 Lookups (10M). GeoLite2-City · uniformly random addresses (85.9% hits) · MMDB_lookup_sockaddr · 10,000,000 queries total↓ Lower is betterBefore · 88ad5f4After · fd08b22minp50p95p99Lower whisker: min → p50 · body: p50 → p95 · upper whisker: p95 → p99◆ marks p50 · maximum listed at the right of each row (off-scale)p99 · max (sampled)1 thread▼p50 −56% · p99 −26%Before611 nsmax 37.0 µsAfter451 nsmax 36.8 µs2 threads▼p50 −56% · p99 −21%Before622 nsmax 35.0 µsAfter491 nsmax 30.5 µs0200400600800Sampled per-lookup latency (ns, linear scale, lower is better)

Candlestick Percentile Rank — IPv6 Lookups (10M)

GeoLite2-City · uniformly random addresses (0.013% hits) · MMDB_lookup_sockaddr · 10,000,000 queries in total · pooled samples of 5 repetitions per bar

Candlestick Percentile Rank — IPv6 Lookups (10M). GeoLite2-City · uniformly random addresses (0.013% hits) · MMDB_lookup_sockaddr · 10,000,000 queries total↓ Lower is betterBefore · 88ad5f4After · fd08b22minp50p95p99Lower whisker: min → p50 · body: p50 → p95 · upper whisker: p95 → p99◆ marks p50 · maximum listed at the right of each row (off-scale)p99 · max (sampled)1 thread▼p50 −2% · p99 −45%Before110 nsmax 38.3 µsAfter61 nsmax 54.7 µs2 threads▼p50 −2% · p99 −45%Before110 nsmax 37.7 µsAfter61 nsmax 71.5 µs0255075100125Sampled per-lookup latency (ns, linear scale, lower is better)

Thread scaling

Scaling = throughput(N) / throughput(1) of the same version; parallel efficiency = scaling / N × 100. Configurations that scale above 100% do so because the measured core cycles per lookup of each worker drop as workers are added (see the chart footers); the cause was not isolated.

Thread Scaling — Random Lookups

GeoLite2-City · MMDB_lookup_sockaddr

Thread Scaling — Random Lookups. GeoLite2-City · throughput(N) / throughput(1) of the same version · labels: scaling at 2 workers · parallel efficiency↑ Higher is betterBefore · 88ad5f4After · fd08b22Ideal linear scaling (N ×)IPv4 · 10M queries0×1×2×3×4×12Worker threads2.80× · 140%2.71× · 135%IPv6 · 10M queries0×1×2×3×12Worker threads2.01× · 100%2.00× · 100%

Thread Scaling — Random Lookups + Field Decode

GeoLite2-City · MMDB_lookup_sockaddr + MMDB_get_value(country → iso_code)

Thread Scaling — Random Lookups + Field Decode. GeoLite2-City · throughput(N) / throughput(1) of the same version · labels: scaling at 2 workers · parallel efficiency↑ Higher is betterBefore · 88ad5f4After · fd08b22Ideal linear scaling (N ×)IPv4 · 10M queries0×1×2×3×12Worker threads2.14× · 107%2.14× · 107%IPv6 · 10M queries0×1×2×3×12Worker threads1.99× · 100%1.98× · 99%

Memory

Measured in fresh processes with the same preparation and warm-up as the throughput campaign. Process RSS is a whole-process metric; the library heap (mallinfo2 in-use bytes after MMDB_open) isolates reader and index allocations.

Memory Usage — Reader State and Process RSS

Fresh process per measurement, median of 3 · heap from mallinfo2 · RSS is whole-process

Memory Usage — Reader State and Process RSS. Fresh process per measurement · median of 3 · random lookups · heap = mallinfo2 in-use bytes; RSS = whole process↓ Lower is betterBefore · 88ad5f4After · fd08b22Change (After vs Before)GeoLite2-Citylibrary heap after MMDB_openBefore1.7 KiBAfter33.1 MiB+33.1 MiBGeoLite2-Cityprocess RSS after MMDB_openBefore3.4 MiBAfter64.6 MiB+61.2 MiBGeoLite2-Citypeak RSS, 10M IPv4 lookups, 1 threadBefore178.9 MiBAfter220.4 MiB+41.5 MiB (+23.2%)GeoLite2-Citypeak RSS, 10M IPv6 lookups, 1 threadBefore288.6 MiBAfter334.8 MiB+46.2 MiB (+16.0%)Synthetic mixed fixturelibrary heap after MMDB_openBefore1.5 KiBAfter8.5 MiB+8.5 MiBSynthetic mixed fixtureprocess RSS after MMDB_openBefore5.5 MiBAfter20.0 MiB+14.5 MiBSynthetic mixed fixturepeak RSS, 10M IPv4 lookups, 1 threadBefore161.3 MiBAfter175.8 MiB+14.5 MiB (+9.0%)Synthetic mixed fixturepeak RSS, 10M IPv6 lookups, 1 threadBefore281.7 MiBAfter290.2 MiB+8.5 MiB (+3.0%)0100200300400Memory (MiB, lower is better)

GeoLite2-City: library heap after MMDB_open 1.7 KiB → 33.1 MiB; process RSS after open 3.4 MiB → 64.6 MiB. Peak RSS during 10M-query lookups is shown with the identical query corpus included.

Startup and index preparation

After builds its native child array and 65,536-entry root tables inside MMDB_open; MMDB_NO_PREPARE skips that work. Opening time is measured separately from steady-state lookups.

Startup — Database Opening and Index Preparation

Fresh process per open, pinned to one core · warm and cold page cache

Startup — Database Opening and Index Preparation. Fresh process per open · pinned to the fastest core · medians (30 warm, 10 cold opens per variant) · After includes automatic radix-16 preparation↓ Lower is betterBefore · 88ad5f4After · fd08b22Change (After vs Before)GeoLite2-CityMMDB_open, warm page cacheBefore0.087 msAfter26.3 ms+26.24 msGeoLite2-CityMMDB_open, cold page cacheBefore0.996 msAfter53.2 ms+52.20 msSynthetic mixed fixtureMMDB_open, warm page cacheBefore0.084 msAfter8.57 ms+8.49 msSynthetic mixed fixtureMMDB_open, cold page cacheBefore0.958 msAfter16.6 ms+15.62 ms020406080Time to open (ms, lower is better)

MMDB_open on GeoLite2-City with a warm page cache: 0.087 ms → 26.3 ms (After prepares the native tree and radix-16 tables before returning).

Allocations

Heap Allocations per Lookup + Field Decode

Instrumented binaries only · measured lookup phase

Heap Allocations per Lookup + Field Decode. Separate instrumented binaries (-Wl,--wrap=malloc,calloc,realloc,free) · 10M queries · 1 thread · measured lookup phase only↓ Lower is betterBefore · 88ad5f4After · fd08b22Change (After vs Before)GeoLite2-City · IPv4uniformly random addresses (85.9% hits)Before0.859After0.00020.6 → 0.0 B/opGeoLite2-City · IPv4addresses inside populated networks (100.0% hits)Before1.000After0.00024.0 → 0.0 B/opGeoLite2-City · IPv6uniformly random addresses (0.013% hits)Before0.000After0.0000.0 → 0.0 B/opGeoLite2-City · IPv6addresses inside populated networks (100.0% hits)Before1.000After0.00024.0 → 0.0 B/opSynthetic mixed fixture · IPv4uniformly random addresses (8.4% hits)Before0.084After0.0001.3 → 0.0 B/opSynthetic mixed fixture · IPv4addresses inside populated networks (100.0% hits)Before1.000After0.00016.0 → 0.0 B/opSynthetic mixed fixture · IPv6uniformly random addresses (7.9% hits)Before0.079After0.0001.3 → 0.0 B/opSynthetic mixed fixture · IPv6addresses inside populated networks (100.0% hits)Before1.000After0.00016.0 → 0.0 B/op0.000.250.500.751.001.25Heap allocations per lookup (lower is better)

Before allocates one temporary lookup-path array per MMDB_get_value call on a hit; After uses a stack buffer for paths of up to seven components and allocates nothing.

Every throughput configuration

Throughput Speedup Matrix — GeoLite2-City

Rows: family × profile × operation · columns: worker threads

Throughput Speedup Matrix — GeoLite2-City. All 8 configurations · geometric mean ×1.89 (+89.0%) · 8 faster, 0 slower, 0 inconclusive↑ Higher is betterEach cell: median throughput of After · fd08b22 divided by Before · 88ad5f410M1 thread10M2 threadsIPv4 · Random lookups2.51×2.59×IPv6 · Random lookups1.92×1.93×IPv4 · Random lookups + field decode1.35×1.35×IPv6 · Random lookups + field decode1.92×1.93×Cell = median throughput speedup (After / Before); color: mint = faster, red = slower. Unmarked cells are conclusive gains (95% CI above 1×).▼ marks a conclusive regression (95% CI below 1×); ≈ marks an inconclusive difference (95% CI spans 1×).Color scale0.5×0.8×1×1.5×2×4×

Attribution between the traversal changes

A separate three-way campaign isolates the specialized packed traversal (After opened with MMDB_NO_PREPARE) from the automatic radix-16/native preparation, on identical corpora with 5 repetitions per variant in rotating order. Ratios are medians with 95% bootstrap intervals.

GeoLite2-CityIPv4Random10M15.996.7714.49×1.13 [1.04–1.40]×2.14 [1.52–2.37]×2.42 [1.72–3.00]
GeoLite2-CityIPv4Random10M212.6415.5333.39×1.23 [1.18–1.26]×2.15 [2.10–2.24]×2.64 [2.58–2.75]
GeoLite2-CityIPv6Random10M130.7133.1059.33×1.08 [1.07–1.09]×1.79 [1.76–1.80]×1.93 [1.90–1.95]
GeoLite2-CityIPv6Random10M262.0066.05118.6×1.07 [1.06–1.08]×1.80 [1.74–1.81]×1.91 [1.86–1.95]

Result tables

Rows follow the filters above unless “show every configuration” is checked. Click a column header to sort.

Throughput

GeoLite2-CityRandomLookups + Field DecodeIPv410M185.88%2.423.27×1.35▲ +35.3%×1.19–1.67▲ faster10.9%7.6%4,139.4 ms3,059.5 ms100.0100.01343992
GeoLite2-CityRandomLookups + Field DecodeIPv410M285.88%5.186.98×1.35▲ +34.8%×1.11–1.62▲ faster8.2%11.8%1,930.6 ms1,432.2 ms99.399.61244922
GeoLite2-CityRandomLookups + Field DecodeIPv610M10.01%30.8959.24×1.92▲ +91.8%×1.91–1.93▲ faster0.4%0.2%323.7 ms168.8 ms100.1100.110555
GeoLite2-CityRandomLookups + Field DecodeIPv610M20.01%61.29118.1×1.93▲ +92.7%×1.89–2.00▲ faster1.3%1.5%163.1 ms84.7 ms99.599.910554
GeoLite2-CityRandomLookupsIPv410M185.88%4.6411.62×2.51▲ +150.7%×1.94–2.82▲ faster8.6%13.2%2,156.9 ms860.3 ms100.0100.0700279
GeoLite2-CityRandomLookupsIPv410M285.88%12.5532.55×2.59▲ +159.4%×1.91–3.47▲ faster12.2%14.5%796.9 ms307.2 ms99.899.6516199
GeoLite2-CityRandomLookupsIPv610M10.01%31.0359.68×1.92▲ +92.3%×1.91–1.93▲ faster0.1%0.3%322.2 ms167.6 ms99.9100.210554
GeoLite2-CityRandomLookupsIPv610M20.01%62.13119.8×1.93▲ +92.7%×1.85–1.99▲ faster1.4%2.0%160.9 ms83.5 ms100.099.710454

Latency (pooled sampled percentiles)

GeoLite2-CityRandomLookupsIPv410M13,125,000181 ns80 ns▼ −55.8%391 ns240 ns611 ns451 ns▼ −26.2%37.0 µs36.8 µs40 ns40 ns
GeoLite2-CityRandomLookupsIPv410M23,125,000180 ns80 ns▼ −55.6%390 ns260 ns622 ns491 ns▼ −21.1%35.0 µs30.5 µs40 ns40 ns
GeoLite2-CityRandomLookupsIPv610M13,125,00051 ns50 ns▼ −2.0%90 ns60 ns110 ns61 ns▼ −44.5%38.3 µs54.7 µs40 ns40 ns
GeoLite2-CityRandomLookupsIPv610M23,125,00051 ns50 ns▼ −2.0%90 ns60 ns110 ns61 ns▼ −44.5%37.7 µs71.5 µs40 ns40 ns

Memory

GeoLite2-CityIPv410M1152.63.464.60.033.10.228.3178.8220.3178.9220.4▲ +23.2%
GeoLite2-CityIPv610M1267.03.464.60.033.10.228.3281.5334.7288.6334.8▲ +16.0%
Synthetic mixed fixtureIPv410M1152.65.520.00.08.52.38.3161.1175.7161.3175.8▲ +9.0%
Synthetic mixed fixtureIPv610M1267.05.520.00.08.52.38.3281.6290.1281.7290.2▲ +3.0%

Startup

GeoLite2-CityAfterMMDB_NO_PREPARE + MMDB_create_lookup_contextwarm300.086 ms0.083 ms – 0.087 ms26.3 ms33.1 MiB64.5 MiB28.3 MiB
GeoLite2-CityAfterMMDB_NO_PREPARE + MMDB_create_lookup_contextcold101.09 ms0.928 ms – 1.20 ms51.7 ms33.1 MiB64.5 MiB28.3 MiB
GeoLite2-CityAfterMMDB_open (default flags)warm3026.3 ms26.2 ms – 26.6 ms—33.1 MiB64.5 MiB28.3 MiB
GeoLite2-CityAfterMMDB_open (default flags)cold1053.2 ms51.2 ms – 53.3 ms—33.1 MiB64.5 MiB28.3 MiB
GeoLite2-CityAfterMMDB_open with MMDB_NO_PREPAREwarm300.087 ms0.084 ms – 0.090 ms—0.0 MiB3.3 MiB0.2 MiB
GeoLite2-CityAfterMMDB_open with MMDB_NO_PREPAREcold101.11 ms1.05 ms – 1.18 ms—0.0 MiB3.3 MiB0.2 MiB
GeoLite2-CityBeforeMMDB_open (default flags)warm300.087 ms0.085 ms – 0.090 ms—0.0 MiB3.3 MiB0.2 MiB
GeoLite2-CityBeforeMMDB_open (default flags)cold100.996 ms0.981 ms – 1.03 ms—0.0 MiB3.3 MiB0.2 MiB
Synthetic mixed fixtureAfterMMDB_NO_PREPARE + MMDB_create_lookup_contextwarm300.084 ms0.082 ms – 0.089 ms8.54 ms8.5 MiB19.9 MiB8.3 MiB
Synthetic mixed fixtureAfterMMDB_NO_PREPARE + MMDB_create_lookup_contextcold100.973 ms0.906 ms – 1.02 ms14.9 ms8.5 MiB18.4 MiB6.8 MiB
Synthetic mixed fixtureAfterMMDB_open (default flags)warm308.57 ms8.00 ms – 8.73 ms—8.5 MiB19.9 MiB8.3 MiB
Synthetic mixed fixtureAfterMMDB_open (default flags)cold1016.6 ms16.1 ms – 16.8 ms—8.5 MiB18.4 MiB6.8 MiB
Synthetic mixed fixtureAfterMMDB_open with MMDB_NO_PREPAREwarm300.084 ms0.082 ms – 0.087 ms—0.0 MiB5.3 MiB2.3 MiB
Synthetic mixed fixtureAfterMMDB_open with MMDB_NO_PREPAREcold101.02 ms0.994 ms – 1.11 ms—0.0 MiB3.3 MiB0.2 MiB
Synthetic mixed fixtureBeforeMMDB_open (default flags)warm300.084 ms0.082 ms – 0.089 ms—0.0 MiB5.3 MiB2.3 MiB
Synthetic mixed fixtureBeforeMMDB_open (default flags)cold100.958 ms0.890 ms – 0.980 ms—0.0 MiB3.3 MiB0.2 MiB

Allocations

GeoLite2-CityIPv4Populated-NetworkLookups + Field DecodeAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv4Populated-NetworkLookups + Field DecodeBefore1.000024.001.0000199,568
GeoLite2-CityIPv4Populated-NetworkLookupsAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv4Populated-NetworkLookupsBefore0.00000.000.0000199,568
GeoLite2-CityIPv4RandomLookups + Field DecodeAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv4RandomLookups + Field DecodeBefore0.858820.611.0000199,568
GeoLite2-CityIPv4RandomLookupsAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv4RandomLookupsBefore0.00000.000.0000199,568
GeoLite2-CityIPv6Populated-NetworkLookups + Field DecodeAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv6Populated-NetworkLookups + Field DecodeBefore1.000024.001.0000199,568
GeoLite2-CityIPv6Populated-NetworkLookupsAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv6Populated-NetworkLookupsBefore0.00000.000.0000199,568
GeoLite2-CityIPv6RandomLookups + Field DecodeAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv6RandomLookups + Field DecodeBefore0.00010.001.0000199,568
GeoLite2-CityIPv6RandomLookupsAfter0.00000.000.00002434,736,071
GeoLite2-CityIPv6RandomLookupsBefore0.00000.000.0000199,568
Synthetic mixed fixtureIPv4Populated-NetworkLookups + Field DecodeAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv4Populated-NetworkLookups + Field DecodeBefore1.000016.001.0000119,496
Synthetic mixed fixtureIPv4Populated-NetworkLookupsAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv4Populated-NetworkLookupsBefore0.00000.000.0000119,496
Synthetic mixed fixtureIPv4RandomLookups + Field DecodeAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv4RandomLookups + Field DecodeBefore0.08421.351.0000119,496
Synthetic mixed fixtureIPv4RandomLookupsAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv4RandomLookupsBefore0.00000.000.0000119,496
Synthetic mixed fixtureIPv6Populated-NetworkLookups + Field DecodeAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv6Populated-NetworkLookups + Field DecodeBefore1.000016.001.0000119,496
Synthetic mixed fixtureIPv6Populated-NetworkLookupsAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv6Populated-NetworkLookupsBefore0.00000.000.0000119,496
Synthetic mixed fixtureIPv6RandomLookups + Field DecodeAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv6RandomLookups + Field DecodeBefore0.07851.261.0000119,496
Synthetic mixed fixtureIPv6RandomLookupsAfter0.00000.000.0000168,941,387
Synthetic mixed fixtureIPv6RandomLookupsBefore0.00000.000.0000119,496

Hardware and methodology

CPU model
AMD EPYC 7763 64-Core Processor
Architecture
x86-64 (x86_64-linux-gnu)
Topology
1 socket · 2 physical cores · 4 logical CPUs · SMT active
CPUs available to the benchmark
4 (affinity 0–3; cgroup CPU quota: max 100000)
Worker placement
first SMT sibling of each physical core, in CPU number order: CPUs 0, 2 — never oversubscribed
Caches
L1d cache 64 KiB (2 instances), L1i cache 64 KiB (2 instances), L2 cache 1 MiB (2 instances), L3 cache 32 MiB (1 instance)
Instruction sets (relevant)
abm avx avx2 bmi1 bmi2 constant_tsc movbe nonstop_tsc popcnt rdpru sse4_2
Frequency policy
not visible from this machine (no cpufreq interface)
Memory
15.6 GiB RAM · 14.5 GiB available at start · THP always [madvise] never
OS / kernel
Ubuntu 24.04.5 LTS · Linux 7.0.0-1017-azure · clocksource tsc · virtualization: microsoft
Compilers
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 · cmake version 3.31.6 · ninja 1.13.2 · ldd (Ubuntu GLIBC 2.39-0ubuntu8.9) 2.39
Library build
/usr/bin/gcc -DMMDB_LITTLE_ENDIAN=1 -DPACKAGE_VERSION=\"1.14.1\" -I/home/runner/work/libmaxminddb/libmaxminddb/benchmarks/artifacts/campaign/src/after -I/home/runner/work/libmaxminddb/libmaxminddb/benchmarks/artifacts/campaign/src/after/include -I/home/runner/work/libmaxminddb/libmaxminddb/benchmarks/artifacts/campaign/build/after-release/generated -O3 -DNDEBUG -std=c99 -fPIC -o CMakeFiles/maxminddb.dir/src/maxminddb.c.o -c /home/runner/work/libmaxminddb/libmaxminddb/benchmarks/artifacts/campaign/src/after/src/maxminddb.c (GCC default target: -march=x86-64 -mtune=generic; no ISA-specific flags)
Hardware counters
perf_event_paranoid=4: perf unavailable; core cycles read with AMD RDPRU (APERF)
Runner
GitHub Actions github-hosted runner GitHub Actions 1000000450 · image ubuntu24 20261026.354.1 · run 41200090568 · 0x00F6/libmaxminddb@bdd1e05
Host activity
GitHub-hosted runner; system CPU busy median 44.2% (p90 50.5%), load average median 1.63
Versions
Before 88ad5f46d9c884fc4a8533374b457cb9e3ee121f and After fd08b229052ae34e9f2c3c3df4f64ed9d43f4347, exported with git archive into isolated source trees (pinned submodules copied and verified). Static libraries built with CMake Release (-O3 -DNDEBUG) by the same GCC; the same bench.c links each archive. ldd shows no libmaxminddb shared object; symbol checks confirm which revision each binary contains.
Operations
lookup = MMDB_lookup_sockaddr on pre-built sockaddr structures (no text parsing in the timed loop). decode = lookup plus MMDB_get_value on hits (country → iso_code on GeoLite2-City, id on the synthetic fixture). Records without the field count as absent, identically in both versions.
Workloads
random: SplitMix64 (seed 0x6a09e667f3bcc909) over the whole IPv4 or IPv6 space. populated networks: every reachable data leaf is enumerated with MMDB_read_node, uniformly reservoir-sampled (2^20 networks), host bits randomized; IPv6 enumeration skips the IPv4 subtree and its aliases. Identical corpora (SHA-256 recorded) for both versions.
Throughput protocol
5 measured repetitions per configuration; one prepared process per version, triggered one repetition at a time, order alternating (even pairs Before first). Each repetition warms up with 1,000,000 queries in total, then workers pass a spin start gate. Throughput = completed lookups / wall-clock time from gate release to the last worker finishing. Thread-local checksums, no shared counters, no per-query timers.
Latency protocol
Separate campaign. Every 16th operation is timed with two CLOCK_MONOTONIC_RAW reads (back-to-back pair: min 20 ns, median 30 ns, p99 31 ns; included, not subtracted). Exact integer-nanosecond histograms from all workers and repetitions are merged before nearest-rank percentiles floor((n − 1) · p).
Statistics
Medians with 95% percentile bootstrap intervals (10,000 resamples, seed 0xc0ffee3). A difference is conclusive only when the interval of the ratio of medians excludes 1. Geometric means weight every configuration equally.
Memory and startup
Fresh processes: 3 per memory configuration (several at once, each pinned to its own core; nothing is timed) and 30 warm and 10 cold opens per startup variant (one at a time, pinned to one core, rotating order). RSS stages read from /proc/self/status; the lookup-phase peak resets VmHWM through /proc/self/clear_refs after warm-up. Heap = mallinfo2 in-use bytes; database mapping residency from /proc/self/smaps.
Counters
CPU utilization = thread CPU time (RUSAGE_THREAD) / (wall time × workers). Core cycles per lookup = APERF delta via RDPRU on each pinned worker CPU / completed lookups. Cache-miss and branch-miss counters are unavailable (perf_event_paranoid = 4).

Workloads and correctness

GeoLite2-CityIPv4Populated-Network1000000010,000,0000100.000%9,998,02500410815da80a23cf800b8dbd8a322b5aad802d632f1b8cf3e469da43b14852cddyes
GeoLite2-CityIPv4Random100000008,588,2271,411,77385.882%8,586,236003528f5efab4d18b2dfe3a50868ef6367332363e0d4b9d3dd8c4de22fa2d973c8yes
GeoLite2-CityIPv6Populated-Network1000000010,000,0000100.000%9,996,59700cd8e47a1fe0a7d4bf75b2cde0c8500b149f434d262e23ca0d3996945dddf8473yes
GeoLite2-CityIPv6Random100000001,2539,998,7470.013%1,25300311a3227aa5156a74c859f29e513144f6d08efaf8f9cecc3c49813606978f7a0yes
Synthetic mixed fixtureIPv4Populated-Network1000000010,000,0000100.000%10,000,00000401a55b8c4a3679edb0b94872f10a9b29294cf81b9e2148ccce34a7b7a1a327eyes
Synthetic mixed fixtureIPv4Random10000000841,6509,158,3508.417%841,650003528f5efab4d18b2dfe3a50868ef6367332363e0d4b9d3dd8c4de22fa2d973c8yes
Synthetic mixed fixtureIPv6Populated-Network1000000010,000,0000100.000%10,000,000007c57ac8d242fd93ec5ce44ad608a4a91fe889446013bbe8c97a7146bb8949609yes
Synthetic mixed fixtureIPv6Random10000000785,1319,214,8697.851%785,13100311a3227aa5156a74c859f29e513144f6d08efaf8f9cecc3c49813606978f7a0yes

Validation: checksums identical across versions and repetitions: True · completed counts and zero errors: True · corpora identical between versions: True · ordered per-query differential digests identical on 8 corpora: True.

Limitations

Reproduction

# From the repository root. Needs Linux, GCC, Clang, CMake + Ninja, Autotools, OpenSSL headers,
# Python 3.11+, Node.js and Chrome. Builds both revisions, runs every campaign and the test suites, then
# regenerates benchmarks/reports; the measurements took 5 min on the machine above.
benchmarks/reproduce.sh /path/to/GeoLite2-City.mmdb   # build 2026-10-30, SHA-256 9e52c481…9d5563
# GitHub Actions measures x86-64 and AArch64 runners for every pushed benchmarks/* branch
# (.github/workflows/benchmarks.yml); this report comes from run 41200090568.

Exported figures

Generated 2026-10-31T12:19:30+00:00 by benchmarks/reporting/report.py from saved results. Measured on AMD EPYC 7763 64-Core Processor. Before 88ad5f46d9c884fc4a8533374b457cb9e3ee121f · After fd08b229052ae34e9f2c3c3df4f64ed9d43f4347.