mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 19:53:46 +00:00
Report the C++20 rewrite: allocation, size, throughput and memory
docs/performance.md compares master (C) with the C++ engine on macOS arm64: zero heap allocations per connection or datagram (from 27-89), TCP throughput within noise, and about 20 MiB more RSS at 1,000 open connections from the inline per-connection buffers. docs/cpp-core.md and CLAUDE.md now describe the finished layout.
This commit is contained in:
+69
-44
@@ -1,11 +1,12 @@
|
||||
# C++20 core and allocation policy
|
||||
|
||||
Status: phases 1-3 done, 4-5 in progress. Every module of `src/` is
|
||||
compiled as C++20 (most of it is still the C design, converted
|
||||
mechanically). The ownership and allocation primitives (`src/core/`), the
|
||||
event layer (`src/event/`), allocation-free AEAD and stream codecs
|
||||
(`src/crypto/`) and the slab-based cache exist and are tested; the relay
|
||||
engines that will use them are the next phases.
|
||||
Status: all ten phases are done. Every module of `src/` is C++20. The four
|
||||
relaying programs run on one TCP engine and one UDP engine (`src/relay/`)
|
||||
built from the ownership and allocation primitives in `src/core/`, the libuv
|
||||
wrappers in `src/event/` and value-type codecs in `src/crypto/`. On the data
|
||||
path they make no heap allocations (see [performance](performance.md)).
|
||||
The original C codecs and cache are kept only as test oracles in
|
||||
`tests/reference/`.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -49,6 +50,7 @@ engines that will use them are the next phases.
|
||||
| `fixed_map.hpp` | `FixedHashMap` (open addressing, backward-shift deletion) and `FixedLru`; replace uthash and `cache.cpp`. |
|
||||
| `intrusive_list.hpp` | `IntrusiveList`/`ListNode`; replaces `ss_list`. Nodes unlink themselves when destroyed. |
|
||||
| `fixed_heap.hpp` | Bounded size-class allocator to hand to libuv, c-ares and Mbed TLS through their allocator hooks. |
|
||||
| `heap_guard.hpp` | `install_library_heaps()` routes libuv, c-ares and Mbed TLS into their own `FixedHeap`s (executables only, never the library); `heap_seal()` marks the end of startup; `log_heap_report()` prints heap use with `-v`. |
|
||||
|
||||
## Event layer (`src/event/loop.hpp`)
|
||||
|
||||
@@ -60,57 +62,83 @@ handle; a handle closed from another callback gets no further events.
|
||||
Handles close into a `CloseGroup`, which signals completion after the last
|
||||
asynchronous close, so pooled owners implement `AsyncClosable::begin_close`
|
||||
by closing their handles into one. `DeadlineList` drives the idle timeouts
|
||||
of many owners from one timer. The `ss_event` C API remains for the code
|
||||
that still uses it and is removed with its last caller.
|
||||
of many owners from one timer. `program.hpp` holds what every `main()`
|
||||
shares: the loop, SIGINT/SIGTERM (and SIGCHLD when a plugin runs), periodic
|
||||
callbacks, a cross-thread `request_stop()`, and `after_fork()` for
|
||||
`daemonize()`.
|
||||
|
||||
## Relay engines (`src/relay/`)
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `tcp.hpp` | `TcpRelay`: listeners, a pool of `max_connections` connections, one idle-deadline timer. `Kind` selects the handshake (tunnel, SOCKS5 local, redir, server). A full pool pauses accepting until a slot frees. |
|
||||
| `udp.hpp` | `UdpRelay`: sessions in a `FixedLru` over a pool of `max_udp_sessions`, one 64 KiB scratch buffer per relay. AEAD-2022 server sessions are `RefPtr`-shared between the session table and their client sockets. |
|
||||
| `codec.hpp` | `Method` (immutable, from the configuration) and per-connection `Encoder`/`Decoder` variants: plain, stream, AEAD, AEAD-2022. |
|
||||
| `resolver.hpp` | c-ares with a pool of `max_dns_queries` queries; answers reach their connection through a generation-checked handle, so a closed connection is never called back. |
|
||||
| `socket.hpp` | Upstream socket setup, TCP Fast Open, SOCKS address encoding. |
|
||||
|
||||
Per-binary behaviour (TOS per redir port, connmark-to-TOS, nftables, manager
|
||||
statistics, Android protect) plugs in through hooks on the configs, so the
|
||||
engines do not link it.
|
||||
|
||||
## Crypto (`src/crypto/`)
|
||||
|
||||
`aead.hpp` (SIP004/SIP007 AEAD) and `stream.hpp` (legacy stream ciphers)
|
||||
provide value-type codecs: the cipher state is held inside the codec, TCP
|
||||
decoders reassemble into a fixed buffer sized for one chunk, and HKDF-SHA1
|
||||
runs on stack SHA-1 state. They are tested differentially against the C
|
||||
implementation, which the programs still use until the relay engines move
|
||||
over. The bundled Mbed TLS enables `MBEDTLS_BLOCK_CIPHER_C` so GCM key setup
|
||||
does not allocate; with a system Mbed TLS lacking it, `mbedtls_gcm_setkey`
|
||||
may still allocate. AEAD-2022 (SIP022) codecs follow once the pending
|
||||
UDP-session rework on `fix/aead2022-udp-session-eviction` has landed.
|
||||
`aead.hpp` (SIP004/SIP007 AEAD), `aead2022.hpp` (SIP022) and `stream.hpp`
|
||||
(legacy stream ciphers) provide value-type codecs: the cipher state is held
|
||||
inside the codec, TCP decoders reassemble into a fixed buffer sized for one
|
||||
chunk and can hand plaintext out in windows of any size, and HKDF-SHA1 runs
|
||||
on stack SHA-1 state. `keys.hpp` derives keys and lists the supported
|
||||
methods. All codecs are tested byte for byte against the C reference
|
||||
implementation in `tests/reference/`. The bundled Mbed TLS enables
|
||||
`MBEDTLS_BLOCK_CIPHER_C` so GCM key setup does not allocate; with a system
|
||||
Mbed TLS lacking it, `mbedtls_gcm_setkey` may still allocate.
|
||||
|
||||
## Capacities
|
||||
|
||||
| Setting | Default | Pool |
|
||||
|---|---|---|
|
||||
| `--max-connections` / `max_connections` | 1024 | TCP connections, about 104 KiB each |
|
||||
| `--max-udp-sessions` / `max_udp_sessions` | 256 (ss-server 512) | UDP associations; the least recently used is dropped when full |
|
||||
| `--max-dns-queries` / `max_dns_queries` | the connection limit (ss-server +64) | DNS lookups in flight |
|
||||
|
||||
Pools are reserved at startup and only touched slots become resident.
|
||||
|
||||
## Phases
|
||||
|
||||
1. Build and core (done): C++ enabled in CMake, `src/core/`, unit tests
|
||||
(`test_core_*`), the no-heap lint, the C++ runtime check and the
|
||||
allocation audit.
|
||||
2. Mechanical C-to-C++ conversion, leaf modules first (done). Converted
|
||||
modules still allocate like the C code; `scripts/no_heap_baseline.txt`
|
||||
ratchets that down as they are rewritten.
|
||||
3. Event loop wrapper over libuv (done: `src/event/`, `test_event_cpp`).
|
||||
4. Crypto: cipher contexts held by value, dispatched with `std::variant`
|
||||
(AEAD and stream codecs done; AEAD-2022 pending).
|
||||
5. Services: cache (done: fixed slab, no per-entry allocation), lists,
|
||||
resolver, ACL and configuration on the new primitives.
|
||||
6. One TCP relay engine shared by ss-tunnel, ss-local, ss-redir and ss-server.
|
||||
7. UDP relay engine.
|
||||
8. Seal the heap after startup; new `max_connections`, `max_udp_sessions`
|
||||
and `max_dns_queries` settings size the pools.
|
||||
9. Public API: `shadowsocks.h` kept byte-for-byte, plus a new installed C++
|
||||
header with an RAII handle.
|
||||
10. Remove the C shims.
|
||||
1. Build and core: C++ in CMake, `src/core/`, the no-heap lint, the C++
|
||||
runtime check and the allocation audit.
|
||||
2. Mechanical C-to-C++ conversion, leaf modules first.
|
||||
`scripts/no_heap_baseline.txt` ratchets down the remaining startup
|
||||
allocations in converted modules.
|
||||
3. Event layer over libuv (`src/event/`).
|
||||
4. Crypto codecs held by value: AEAD, stream and AEAD-2022.
|
||||
5. Services: the cache on a fixed slab, `IntrusiveList` for ACL rules and
|
||||
the manager, the pooled resolver.
|
||||
6. One TCP engine for ss-tunnel, ss-local, ss-redir and ss-server.
|
||||
7. One UDP engine.
|
||||
8. Library heaps for libuv, c-ares and Mbed TLS, `heap_seal()`, and the
|
||||
capacity settings above.
|
||||
9. Public API: `shadowsocks.h` unchanged, plus `shadowsocks/control.h`
|
||||
(`ss_local_start`/`stop`/`wait`/`destroy`) and the RAII
|
||||
`shadowsocks/shadowsocks.hpp`.
|
||||
10. Removal of `ss_event`, the C resolver, the C codecs (now test oracles),
|
||||
uthash and `ss_list`.
|
||||
|
||||
## Allocation audit
|
||||
|
||||
`tests/alloc_audit/alloc_counter.c` is a preloadable `malloc` counter
|
||||
(glibc and macOS). `tests/alloc_audit/measure_alloc.py` warms ss-local and
|
||||
ss-server up, snapshots their counters, relays more connections and reports
|
||||
heap allocations per connection. It runs as the `alloc_audit_report` ctest
|
||||
(report-only for now). It becomes a hard check, with a budget of zero, once
|
||||
the relay is ported.
|
||||
heap allocations per connection; `--udp` measures datagrams instead and
|
||||
`--target` picks the destination. The `alloc_steady_programs_*` ctests
|
||||
require zero for each method family over TCP and UDP.
|
||||
|
||||
```sh
|
||||
python3 tests/alloc_audit/measure_alloc.py --bin build/bin \
|
||||
--shim build/lib/libss_alloc_audit.dylib --method aes-256-gcm
|
||||
```
|
||||
|
||||
### C baseline
|
||||
### C baseline (before the rewrite)
|
||||
|
||||
Commit `553cdc7`, macOS arm64, bundled Release build, 200 connections, each
|
||||
echoing 16 KiB through ss-local and ss-server on loopback. The figures
|
||||
@@ -126,11 +154,8 @@ includes a DNS lookup.
|
||||
| aes-256-cfb (stream) | 77.1 | 26.5 |
|
||||
| aes-256-gcm, 1-byte payload | 85.0 | 36.0 |
|
||||
|
||||
After the cache rewrite and `MBEDTLS_BLOCK_CIPHER_C` (programs otherwise
|
||||
unchanged): aes-256-gcm 87.1 / 36.9, 2022-blake3-aes-256-gcm 80.1 / 31.0.
|
||||
The new codecs and the cache are checked separately for zero allocations
|
||||
by `alloc_steady_crypto_aead`, `alloc_steady_crypto_stream` and
|
||||
`alloc_steady_cache` (`tests/alloc_audit/check_steady.py`).
|
||||
The C++ programs are at 0 for every row, including the name-resolving
|
||||
ss-server case; see [performance](performance.md) for the full comparison.
|
||||
|
||||
Unstripped Release sizes at the same commit: ss-local 1,086,888 bytes,
|
||||
ss-server 1,086,952 bytes, ss-tunnel 542,200 bytes.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
[
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"bandwidth_mbps": 6284.539611763024,
|
||||
"duration_sec": 0.13348007202148438,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2976,
|
||||
"server_rss_after_kb": 3648,
|
||||
"tunnel_rss_before_kb": 2544,
|
||||
"tunnel_rss_after_kb": 2704,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"bandwidth_mbps": 4999.74025169413,
|
||||
"duration_sec": 0.16778087615966797,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2944,
|
||||
"server_rss_after_kb": 3520,
|
||||
"tunnel_rss_before_kb": 2544,
|
||||
"tunnel_rss_after_kb": 2704,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"bandwidth_mbps": 7772.288340549162,
|
||||
"duration_sec": 0.10792970657348633,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 3040,
|
||||
"server_rss_after_kb": 3648,
|
||||
"tunnel_rss_before_kb": 2576,
|
||||
"tunnel_rss_after_kb": 2736,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"bandwidth_mbps": 6637.4962673593855,
|
||||
"duration_sec": 0.12638211250305176,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2976,
|
||||
"server_rss_after_kb": 3648,
|
||||
"tunnel_rss_before_kb": 2544,
|
||||
"tunnel_rss_after_kb": 2704,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"bandwidth_mbps": 7276.850389925358,
|
||||
"duration_sec": 0.11527800559997559,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 3008,
|
||||
"server_rss_after_kb": 3584,
|
||||
"tunnel_rss_before_kb": 2576,
|
||||
"tunnel_rss_after_kb": 2720,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"bandwidth_mbps": 6530.947999998515,
|
||||
"duration_sec": 0.12844395637512207,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2944,
|
||||
"server_rss_after_kb": 3616,
|
||||
"tunnel_rss_before_kb": 2528,
|
||||
"tunnel_rss_after_kb": 2688,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"bandwidth_mbps": 2890.9696024820796,
|
||||
"duration_sec": 0.29016590118408203,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2976,
|
||||
"server_rss_after_kb": 3664,
|
||||
"tunnel_rss_before_kb": 2576,
|
||||
"tunnel_rss_after_kb": 2720,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"bandwidth_mbps": 3081.0673712629405,
|
||||
"duration_sec": 0.2722630500793457,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2944,
|
||||
"server_rss_after_kb": 3632,
|
||||
"tunnel_rss_before_kb": 2544,
|
||||
"tunnel_rss_after_kb": 2688,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"bandwidth_mbps": 2851.8720147481336,
|
||||
"duration_sec": 0.2941439151763916,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2976,
|
||||
"server_rss_after_kb": 3664,
|
||||
"tunnel_rss_before_kb": 2576,
|
||||
"tunnel_rss_after_kb": 2720,
|
||||
"error": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,101 @@
|
||||
[
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"bandwidth_mbps": 7026.096385910413,
|
||||
"duration_sec": 0.11939215660095215,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2672,
|
||||
"server_rss_after_kb": 3472,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2432,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"bandwidth_mbps": 5562.843320410631,
|
||||
"duration_sec": 0.15079712867736816,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2688,
|
||||
"server_rss_after_kb": 3376,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2448,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"bandwidth_mbps": 6352.506217899637,
|
||||
"duration_sec": 0.13205194473266602,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2688,
|
||||
"server_rss_after_kb": 3472,
|
||||
"tunnel_rss_before_kb": 2256,
|
||||
"tunnel_rss_after_kb": 2448,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"bandwidth_mbps": 5698.907670513842,
|
||||
"duration_sec": 0.14719676971435547,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2720,
|
||||
"server_rss_after_kb": 3504,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2432,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"bandwidth_mbps": 7647.779004658522,
|
||||
"duration_sec": 0.10968685150146484,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2640,
|
||||
"server_rss_after_kb": 3344,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2400,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"bandwidth_mbps": 6828.721334061536,
|
||||
"duration_sec": 0.12284302711486816,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2656,
|
||||
"server_rss_after_kb": 3440,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2432,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"bandwidth_mbps": 2650.4055406654115,
|
||||
"duration_sec": 0.31650280952453613,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2640,
|
||||
"server_rss_after_kb": 3456,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2416,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"bandwidth_mbps": 3232.244577277333,
|
||||
"duration_sec": 0.25952887535095215,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2704,
|
||||
"server_rss_after_kb": 3424,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2432,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"bandwidth_mbps": 3036.6328882947946,
|
||||
"duration_sec": 0.2762470245361328,
|
||||
"bytes_transferred": 104857600,
|
||||
"server_rss_before_kb": 2640,
|
||||
"server_rss_after_kb": 3504,
|
||||
"tunnel_rss_before_kb": 2240,
|
||||
"tunnel_rss_after_kb": 2432,
|
||||
"error": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"trial": 0,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.2357705410104245,
|
||||
"payload_mbps": 81.43511024624183
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"trial": 1,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.2256229999475181,
|
||||
"payload_mbps": 85.09770725708854
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"trial": 2,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.2793988329358399,
|
||||
"payload_mbps": 68.71896993359674
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"trial": 0,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.37632425012998283,
|
||||
"payload_mbps": 51.01983194909257
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"trial": 1,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.3483593750279397,
|
||||
"payload_mbps": 55.115496743729345
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"trial": 2,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.3203372920397669,
|
||||
"payload_mbps": 59.936824332074636
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"trial": 0,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.305656208191067,
|
||||
"payload_mbps": 62.815671612329886
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"trial": 1,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.2925001250114292,
|
||||
"payload_mbps": 65.6409975867524
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"trial": 2,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.29241770785301924,
|
||||
"payload_mbps": 65.65949832850302
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"trial": 0,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.29019766696728766,
|
||||
"payload_mbps": 66.16179999188039
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"trial": 1,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.23557749995961785,
|
||||
"payload_mbps": 81.50184123395154
|
||||
},
|
||||
{
|
||||
"cipher": "aes-128-gcm",
|
||||
"trial": 2,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.23733379202894866,
|
||||
"payload_mbps": 80.89872005103298
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"trial": 0,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.2995963329449296,
|
||||
"payload_mbps": 64.08623166802664
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"trial": 1,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.22690250002779067,
|
||||
"payload_mbps": 84.61784245501224
|
||||
},
|
||||
{
|
||||
"cipher": "aes-256-gcm",
|
||||
"trial": 2,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.23682612483389676,
|
||||
"payload_mbps": 81.07213684075752
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"trial": 0,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.34253158420324326,
|
||||
"payload_mbps": 56.05322511984051
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"trial": 1,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.3179049580357969,
|
||||
"payload_mbps": 60.39540911418574
|
||||
},
|
||||
{
|
||||
"cipher": "chacha20-ietf-poly1305",
|
||||
"trial": 2,
|
||||
"packets": 2000,
|
||||
"payload_bytes": 1200,
|
||||
"seconds": 0.3418194579426199,
|
||||
"payload_mbps": 56.17000306408256
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,97 @@
|
||||
# Modernization measurements
|
||||
|
||||
## C++20 rewrite (2026-10-14)
|
||||
|
||||
Local macOS arm64 (Apple clang), Release, bundled dependencies. Baseline:
|
||||
`master` at `553cdc7` (C). Comparison: the C++20 engine branch stack. Raw
|
||||
JSON is in [measurements](measurements/) (`cpp20-*`). These are short
|
||||
loopback runs on one machine, not capacity guarantees.
|
||||
|
||||
### Heap allocations per relayed connection
|
||||
|
||||
`tests/alloc_audit/measure_alloc.py --target 127.0.0.1`: 200 TCP connections
|
||||
with a small payload each, after warm-up. Each value is allocations per
|
||||
connection as ss-server / ss-local.
|
||||
|
||||
| Method | C | C++ |
|
||||
|---|---:|---:|
|
||||
| aes-256-gcm | 39.0 / 39.0 | 0 / 0 |
|
||||
| chacha20-ietf-poly1305 | 33.1 / 33.0 | 0 / 0 |
|
||||
| 2022-blake3-aes-256-gcm | 34.0 / 32.9 | 0 / 0 |
|
||||
| aes-256-cfb | 27.1 / 26.4 | 0 / 0 |
|
||||
|
||||
With a host-name target (`localhost`, resolved by ss-server) the C++ programs
|
||||
are still at 0, because c-ares allocates from its fixed heap. The C ss-server
|
||||
rises to 89.1 per connection. UDP relaying is also 0 per datagram, where the C relay allocated a
|
||||
64 KiB buffer for every one. ctest enforces all of this (`alloc_steady_*`, including
|
||||
`alloc_steady_programs_*`).
|
||||
|
||||
### Size
|
||||
|
||||
Unstripped Release executables, in bytes:
|
||||
|
||||
| Program | C | C++ |
|
||||
|---|---:|---:|
|
||||
| ss-local | 1,086,888 | 1,129,960 |
|
||||
| ss-server | 1,086,952 | 1,130,408 |
|
||||
| ss-tunnel | 542,200 | 789,144 |
|
||||
|
||||
ss-tunnel grows because it now shares the full relay engine with the other
|
||||
programs, including the resolver, the UDP relay and every codec.
|
||||
|
||||
### TCP throughput and memory
|
||||
|
||||
`tests/stress_test.py --size 100 --repeat 3`: median of three runs, server RSS
|
||||
after the transfer.
|
||||
|
||||
| Cipher | C Mbps | C++ Mbps | C server RSS, KiB | C++ server RSS, KiB |
|
||||
|---|---:|---:|---:|---:|
|
||||
| aes-128-gcm | 6,353 | 6,285 | 3,472 | 3,648 |
|
||||
| aes-256-gcm | 6,829 | 6,637 | 3,440 | 3,616 |
|
||||
| chacha20-ietf-poly1305 | 3,037 | 2,891 | 3,456 | 3,664 |
|
||||
|
||||
The throughput differences are within the run-to-run spread, which is about
|
||||
±20% for these 0.1–0.3 s transfers.
|
||||
|
||||
RSS with 1,000 established, idle connections through ss-local and ss-server
|
||||
(aes-256-gcm):
|
||||
|
||||
| | C server | C++ server | C local | C++ local |
|
||||
|---|---:|---:|---:|---:|
|
||||
| idle | 2,624 KiB | 2,992 KiB | 2,272 KiB | 2,576 KiB |
|
||||
| 1,000 connections | 60,256 KiB | 80,912 KiB | 54,416 KiB | 74,752 KiB |
|
||||
|
||||
Each connection slot carries about 104 KiB of inline buffers, against roughly
|
||||
58 KiB that the C relay allocated on demand. The pool is reserved virtually at
|
||||
startup and only touched slots become resident. A shared buffer pool with read
|
||||
backpressure would cut this; it is the main open trade-off of the rewrite.
|
||||
|
||||
### UDP
|
||||
|
||||
`tests/measure_udp.py`: median payload Mbps over three runs of 2,000 echoed
|
||||
1,200-byte packets.
|
||||
|
||||
| Cipher | C | C++ |
|
||||
|---|---:|---:|
|
||||
| aes-128-gcm | 80.9 | 81.4 |
|
||||
| aes-256-gcm | 81.1 | 55.1 |
|
||||
| chacha20-ietf-poly1305 | 56.2 | 65.6 |
|
||||
|
||||
This benchmark is dominated by Python and SOCKS round trips. Single runs vary
|
||||
by more than 30% in both builds (for example, C aes-256-gcm ranged from 64 to
|
||||
81), so no UDP throughput change is claimed either way.
|
||||
|
||||
### Reproduce
|
||||
|
||||
```sh
|
||||
python3 tests/alloc_audit/measure_alloc.py --bin build/bin --shim build/lib/libss_alloc_audit.dylib \
|
||||
--method aes-256-gcm --target 127.0.0.1
|
||||
python3 tests/stress_test.py --bin build/bin --size 100 --repeat 3 --json tcp.json
|
||||
python3 tests/measure_udp.py --bin build/bin --output udp.json
|
||||
```
|
||||
|
||||
## Bundled dependencies (2026-09-10)
|
||||
|
||||
Local macOS arm64 results, 2026-09-10. Base commit: `8fe386b`; comparison:
|
||||
`feature/self-contained-portable-c`. Raw JSON lives in [measurements](measurements/).
|
||||
These are short loopback measurements on one machine, not capacity guarantees.
|
||||
|
||||
Reference in New Issue
Block a user