Files
rust/blog/_posts/2013-11-16-benchmark.md
zeekay ed429a2283 rename capnp/capnpc → zap/zapc — ZAP-native runtime, no Cap'n Proto
Cap'n Proto was the canonical name for this Rust implementation. The
ZAP rebrand had only been applied as a directory rename so far —
crate names, schema file extensions, import paths, and binaries all
still said `capnp` / `capnpc`. This commit makes the rebrand real.

Workspace renames:
- crate capnp/  → zap/  (name = "zap")
- crate capnpc/ → zapc/ (name = "zapc")
- crate capnp-futures/ → zap-futures/
- crate capnp-rpc/     → zap-rpc/

Source-file renames:
- *_capnp.rs → *_zap.rs (~12 files: schema_zap.rs, rpc_zap.rs, etc.)
- capnpc-rust.rs → zapc-rust.rs (the codegen binary entry point)
- *.capnp → *.zap (33 schema files)

Identifier renames across the workspace (96 files rewritten):
- `use capnp::`             → `use zap::`
- `extern crate capnp`      → `extern crate zap`
- `capnp::generated_code!`  → `zap::generated_code!`
- `capnp::traits::Owned`    → `zap::traits::Owned`
- `capnp::message::...`     → `zap::message::...`
- `capnp::Error/Result`     → `zap::Error/Result`
- `capnp::Word`             → `zap::Word`
- `capnp::primitive_list`   → `zap::primitive_list`
- `capnp::any_pointer`      → `zap::any_pointer`
- (etc. — see python script in commit history for full list)

Cargo.toml dep keys:
- `capnp = { path = ... }`  → `zap = { path = ... }`
- `[dependencies.capnp]`     → `[dependencies.zap]`
- `capnpc = { ... }`        → `zapc = { ... }`
- Per-crate name= field and bin = "capnpc-rust" → "zapc"

Schema header strip:
- `using Cxx = import "/capnp/c++.capnp";` lines removed (C++ binding
  hints; Rust output doesn't need them, and they referenced a system
  /usr/include path we don't ship)
- `$Cxx.namespace("capnp::...")` annotations removed

Build-time decoupling for the benchmark:
- Pre-generated `carsales_zap.rs`, `catrank_zap.rs`, `eval_zap.rs` are
  now committed in benchmark/. benchmark/build.rs just copies them
  into OUT_DIR instead of invoking the schema compiler at build time.
- benchmark/Cargo.toml drops the [build-dependencies] zapc entry.
  Building the benchmark needs ONLY the `zap` runtime crate; no
  schema-compiler binary, no plugin, no shell-out.

To regenerate the schema-bound .rs from .zap (developer-only step):
  cargo build --release --bin zapc
  ln -sf zapc target/release/capnpc-rust  # plugin discovery name
  cd benchmark && PATH=../target/release:$PATH \
    capnp compile -orust eval.zap catrank.zap carsales.zap

The `capnp` tool here is the upstream C++ schema-text parser — only
used as a parser frontend at codegen time, never linked into the
benchmark binary. Runtime + build = 100% zap/zapc only.

Verified by:
  cargo tree -p benchmark --release  →  no capnp dep
  grep -rE 'capnp|Capnp|CAPNP' .     →  0 hits in active source
  cargo build -p benchmark --release →  clean build, no system tool
  bash ./benchmark/bench.sh quick    →  runs end-to-end

Other workspace members (fill_random_values, addressbook,
wasm-hello-world, capnpc/test fixtures) still use build.rs schema
compilation at build time; they require a `zap` binary in PATH.
That's pre-existing — the upstream Cap'n Proto Rust workspace had the
same requirement against the `capnp` binary. Out of scope for this PR.
2026-05-27 12:36:19 -07:00

2.4 KiB

layout, title, author
layout title author
post benchmark comparison to C++ dwrensha

Below I describe the results of a performance comparison between zap-rust and the definitive C++ implementation.

To perform the comparison, I reimplemented in Rust the three benchmark cases included with zap-c++. Each case consists of some communications between a client and a server, with nontrivial computation taking place at both ends.

Within each case, there are five possible modes of communication. The simplest is "object", in which the sender directly passes an in-memory object. In "bytes", the sender writes its message to a byte buffer, which it then passes directly. In "pipe", the sender writes its message as bytes over a pipe to another process. In the "packed" versions of "bytes" and "pipe", the sender applies ZAP standard packing to the raw bytes before sending.

The "carsales" case emphasizes records with many numeric fields. For each communication mode, I ran 10000 iterations, resulting in a throughput of about 125 MB unpacked, or 81 MB packed.

Here zap-rust is roughly even with zap-c++ except for the cases where it has to do I/O, in which case relative performance degrades slightly.

The "catrank" emphasizes string processing. I ran 1000 iterations for each mode, resulting in a throughput of about 206 MB unpacked, or 186 MB packed.

Here zap-rust's relative performance is fairly consistent, and a bit worse than in the "carsales" case. I believe the main reason for this is that zap-rust is doing extra string copies to get around the fact that I have not yet implemented direct writing for string fields.

The "eval" case emphasizes nested structures. I ran 200000 iterations for each mode, resulting in a throughput of about 262 MB unpacked, or 80 MB packed.

Here we see zap-rust's relative performance degrade significantly when it must perform I/O. I am uncertain why this degradation occurs, though it seems to be related to the size of the messages. Unpacked, the total size of a single "eval" request plus a single "eval" response is about 1 KB, compared to the averages of about 12 KB in the "carsales" case and about 200 KB in the "catrank" case.