Skip to content

Performance Comparison

bavix/gripmock against the original tokopedia/gripmock. Every number comes from bench/. Full run: 29 minutes.

Runtime architecture

tokopedia/gripmockbavix/gripmock
Protobuf handlingprotoc runs at startupin-process, no code generation
Server processgenerated Go server compiled and spawnedsingle application
Stub lookupinternal gRPC to HTTP hopin-process
Runtime dependencies in imageprotobuf, protoc-gen-go, protoc-gen-go-grpcnone

Results

Throughput

Peak requests per second.

StubsScenariobavixbavix nativetokopedia
500equals29 80436 7516 221
500contains29 57936 0696 191
500matches28 95535 924986
500miss15 91418 6854 091
10 000equals29 38636 174383
10 000contains29 41937 407330
10 000matches28 62134 59132
10 000miss11 22012 637135
500 000equals30 52738 60932
500 000contains30 26537 62431
500 000matches27 62633 5341
500 000miss11 53612 8794

bavix/gripmock resolves a request through an index covering all three matcher kinds, including regular expressions that reduce to an anchored literal. tokopedia/gripmock scans the stub list.

Throughput, equals matcher

Throughput, no matching stub

Matcher kinds at 500 stubs. tokopedia/gripmock drops from 6 221 to 986 req/s on regular expressions; bavix/gripmock drops from 29 804 to 28 955:

Throughput by matcher kind

Latency

At 500 stubs, concurrency 100:

avgp50p95p99
bavix3.33 ms2.91 ms6.75 ms9.44 ms
bavix native2.69 ms2.30 ms5.69 ms8.18 ms
tokopedia16.06 ms16.10 ms22.90 ms24.08 ms

Latency distribution

p99 against stub count, and the same call without queueing at concurrency 1:

Stubsbavix p99bavix native p99tokopedia p99tokopedia at c=1
5009 ms8 ms24 ms0.80 ms
10 00010 ms8 ms552 ms8.72 ms
500 0009 ms8 ms3485 ms85.98 ms

bavix/gripmock answers in 0.20 ms at concurrency 1, at every stub count.

CPU and memory

StubsCPU avgRequests/s per coreAt restPeak under load
500bavix348 %8 564153 MB795 MB
500bavix native355 %10 352184 MB730 MB
500tokopedia338 %1 84171 MB108 MB
10 000bavix348 %8 439166 MB936 MB
10 000bavix native356 %10 167201 MB794 MB
10 000tokopedia155 %24784 MB135 MB
500 000bavix336 %9 088989 MB2522 MB
500 000bavix native350 %11 0281025 MB2520 MB
500 000tokopedia230 %14800 MB1057 MB

All three saturate the four CPUs granted to them. Per core bavix/gripmock delivers 4.7 times more requests at 500 stubs and 649 times more at 500 000.

Memory per 1000 requests/s, bavix/gripmock against tokopedia/gripmock: 26.7 MB against 17.4 MB at 500 stubs, 31.9 MB against 352.5 MB at 10 000, 82.6 MB against 33 031 MB at 500 000.

CPU efficiency

Memory at rest

Startup

Until the service answers gRPC reflection, one stub loaded, 50 container starts each.

minavgmax
bavix0.347 s0.399 s0.453 s
bavix native0.169 s0.174 s0.228 s
tokopedia1.577 s2.205 s3.418 s

Startup readiness

Image size

Compressed layers from the registry manifest, read from published tags.

linux/amd64linux/arm64
bavix/gripmock:3.18.419.17 MB18.49 MB
tkpd/gripmock:v1.14226.29 MB219.90 MB

Image size

Method

Both servers get the same stub file byte for byte, the same proto, the same limits, the same client invocation, one at a time.

ScenarioStub setOutcome
equalsinput.equalsevery request matches
containsinput.containsevery request matches
matchesinput.matches, anchored regex per stubevery request matches
missinput.equalsno request can match
  • Requests come from names.csv, one row per stub, shuffled with a fixed seed. In insertion order a scanning engine answers request N after N comparisons.
  • miss asserts ERROR partial {}, because the two engines return different status codes for an unmatched request.
  • A level stops at BENCH_REQUESTS or its time cap, whichever comes first.
  • A run is rejected if the container stops mid-measurement, if any request fails at the transport level, if the outcome disagrees with the scenario, or if the load generator reports that it was itself the bottleneck. All 72 measurements passed. Throughput derived from measured latency and concurrency agrees with the reported figure within 1 % in every row.
  • Every report carries the generator's own CPU accounting, so a figure can be audited rather than taken on trust. The client held 2.3 of the host's 12 cores at the fastest row and never raised generator_limited.
  • Startup is measured from docker run until helloworld.Greeter answers reflection. docker-proxy binds the published port at container creation, and the health service is registered before the mocked one, so neither a TCP probe nor bare reflection marks readiness.
  • CPU comes from docker stats for containers and from /proc for the native process, since ps -o %cpu averages over process lifetime. Averages only: docker stats peaks read above the container's own CPU limit, because its window does not align with the 100 ms CFS quota periods.
  • Charts use a linear axis; bar length is proportional to value. Bars below half a percent of the axis are drawn at that floor. Metrics spanning more than about fifty times — p99 against stub count, memory per request — are tables.

Calibration

make bench-calibrate measures the settings the run holds fixed. On this host:

Client floor78 904 req/sthe same document against a no-op target inside the generator
2 CPUs19 218 req/sp99 14.8 ms
4 CPUs29 907 req/sp99 9.4 ms — chosen
6 CPUs38 535 req/sp99 7.8 ms
Concurrency 10030 170 req/sp99 10 ms — chosen
Concurrency 20029 575 req/sp99 20 ms
Concurrency 40029 909 req/sp99 39 ms
Concurrency 80029 553 req/sp99 72 ms
50 000 req/level1.8 s window3.6 % spread over repeats
150 000 req/level5.3 s window1.2 % spread
300 000 req/level10.6 s window0.7 % spread — chosen
1 connection29 480 req/s7.1 % client CPU per 1000 req/s
8 connections28 308 req/s7.9 % client CPU per 1000 req/s — chosen

The client floor is not a ceiling for a real run: the no-op target lives inside the generator, so both ends share the same cores and the same CPU counter. Read it as what the document costs before an engine is involved — 2.6 times the fastest measured engine row, which is why no row here is generator-bound.

Throughput is flat from concurrency 100 to 800 while p99 grows sevenfold, so concurrency 100 is the last level that buys anything. CPU scales close to linearly across 2, 4 and 6 CPUs; 4 keeps the engine inside half the host and leaves the rest to the client.

The run refuses to start unless the host has at least twice BENCH_CPUS cores and is idle, because an unrelated job competing for CPU moves the figures by tens of percent.

Reproducing

Requires Docker, Go and grpctestify v1.10.0 or newer.

bash
make bench            # measure, then regenerate every chart
make bench-run        # measure only, writes bench/results-<count>/
make bench-chart      # redraw charts from the last measurement
make bench-calibrate  # re-measure the settings, writes bench/results-calibration/

bench/run.sh drives both the measurement and the calibration, bench/tests/ holds the scenarios, bench/chart.go renders the SVGs.

VariableDefault
BAVIX_IMAGEbavix/gripmock:3.18.4
TKPD_IMAGEtkpd/gripmock:v1.14
BENCH_COUNTS500 10000 500000
BENCH_SCENARIOSequals contains matches miss
BENCH_CONCURRENCY_LEVELS1,100
BENCH_REQUESTS300000
BENCH_DURATION30s
BENCH_SWEEP_DURATION10s
BENCH_WARMUP1s
BENCH_STARTUP_RUNS50
BENCH_CONNECTIONS8
BENCH_REQUEST_TIMEOUT180s
BENCH_CPUS4
BENCH_MEMORY8g
BENCH_MAX_LOAD1.5

Environment

MachineAMD Ryzen 5 5600G, 6 cores / 12 threads, 30 GiB RAM
OSDebian GNU/Linux 12 (bookworm), kernel 6.1.0-44-amd64
Architecturex86_64; both images linux/amd64, nothing emulated
Docker29.4.1
Gogo1.26.2
jqjq-1.6
grpctestifyv1.10.0
bavixbavix/gripmock:3.18.4, built from this tree
bavix nativesame tree as a host binary, GOMAXPROCS=4
tokopediatkpd/gripmock:v1.14
Container limits4 CPUs, 8 GiB
Run time29 min

Known deviations

  • bavix/gripmock reports 29 804 req/s at 500 stubs and 30 527 at 500 000 — faster with more stubs, by about 2 %, reproduced across three sweeps. Unexplained. At 500 stubs each stub is matched thousands of times per run against a handful at 500 000.
  • The Docker build runs 19 to 23 % below the native process in every throughput row. Under grpctestify v1.9.5 the same gap read 9 to 11 %: the client was then the slower half of the pair, and it compressed the difference. tokopedia/gripmock has no native mode; it compiles a gRPC server from the proto at startup.
  • A single client connection reaches 4 % more throughput than eight, at lower client CPU per request. The run still uses eight, because one HTTP/2 connection multiplexes every stream through one flow-control window and that is a property of the client, not of the engine under test.
  • Image pull times are not measured.