Benchmark Results

All benchmarks were performed against a nRF54L15 DK peripheral running bleRPC firmware with E2E encryption enabled (AES-128-GCM). MTU was negotiated to 247 bytes. Every central negotiated a 15ms connection interval.

Throughput Comparison

Python (macOS)

56.6
KB/s flash read

CI: 15ms

iOS (iPhone 16)

55.5
KB/s flash read

CI: 15ms

Android (Pixel 5)

83.5
KB/s flash read

CI: 15ms

Web (Chrome, macOS)

57.4
KB/s flash read

CI: 15ms

Detailed Results by Platform

Python Central (macOS, bleak)

Connection interval: 15ms (macOS negotiation) — test code

BenchmarkResultDetails
flash_read_throughput56.6 KB/s10x 8192 bytes, 141.3 ms/call
flash_read_overhead30.1 ms/call1 byte x 20 calls
echo_roundtrip30.0 ms/call50 calls
data_write_throughput6.5 KB/s200 bytes x 20 calls, 30.1 ms/call
counter_stream (P→C)3.3 ms/item20 items in 66 ms
counter_upload (C→P)2.9 ms/item20 items in 59 ms
Functional TestResultDetails
echo_basicPASS
echo_emptyPASS
echo_max_lengthPASS256-char string
flash_read_basicPASS16 bytes
flash_read_8kbPASS8192 bytes
data_write_basicPASS1024 bytes
data_write_8kbPASS8192 bytes
multi_container_echoPASS250-char string
counter_streamPASS5 items
counter_stream_largePASS20 items
counter_uploadPASS5 items
counter_upload_largePASS20 items

iOS Central (iPhone 16, CoreBluetooth)

Connection interval: 15ms (iOS negotiation) — test code

BenchmarkResultDetails
flash_read_throughput55.5 KB/s10x 8192 bytes, 144.1 ms/call
flash_read_overhead30.0 ms/call1 byte x 20 calls
echo_roundtrip30.6 ms/call50 calls
data_write_throughput6.4 KB/s200 bytes x 20 calls, 30.6 ms/call
counter_stream (P→C)3.1 ms/item20 items in 62 ms
counter_upload (C→P)3.8 ms/item20 items in 75 ms
Functional TestResultDetails
echo_basicPASS
echo_emptyPASS
flash_read_basicPASS64 bytes
flash_read_8kbPASS8192 bytes
data_writePASS64 bytes
counter_streamPASS5 items
counter_uploadPASS5 items

Android Central (Pixel 5)

Connection interval: 15ms (Android negotiation) — test code

BenchmarkResultDetails
flash_read_throughput83.5 KB/s10x 8192 bytes, 95.8 ms/call
flash_read_overhead38.4 ms/call1 byte x 20 calls
echo_roundtrip38.2 ms/call50 calls
data_write_throughput4.8 KB/s200 bytes x 20 calls, 40.7 ms/call
counter_stream (P→C)2.8 ms/item20 items in 55 ms
counter_upload (C→P)5.9 ms/item20 items in 118 ms
Functional TestResultDetails
echo_basicPASS
echo_emptyPASS
flash_read_basicPASS64 bytes
flash_read_8kbPASS8192 bytes
data_writePASS64 bytes
counter_streamPASS5 items
counter_uploadPASS5 items

Web Central (Chrome, macOS, Web Bluetooth)

Connection interval: 15ms (macOS negotiation) — test code

BenchmarkResultDetails
flash_read_throughput57.4 KB/s10x 8192 bytes, 139.3 ms/call
flash_read_overhead30.0 ms/call1 byte x 20 calls
echo_roundtrip30.0 ms/call50 calls
data_write_throughput6.5 KB/s200 bytes x 20 calls, 30.0 ms/call
counter_stream (P→C)3.1 ms/item20 items in 61 ms
counter_upload (C→P)2.8 ms/item20 items in 56 ms

Measured with the central_web demo (Chrome, encrypted, MTU 247). Results match the Python (macOS) central closely — the same transport-agnostic protocol layer (@blerpc/protocol-ts) runs unchanged in the browser.

Performance Notes

Connection Interval Impact

The connection interval (CI) is the primary factor in round-trip latency: a request–response cycle costs two connection events (the central writes the request, the peripheral notifies the response). The firmware pins its preferred interval to 15ms (PREF_MIN_INT = PREF_MAX_INT = 12) and every central tested accepts it:

Asking for a range is what costs you. With the earlier 15–30ms window (PREF_MAX_INT=24) every central picked 30ms — the power-efficient end — and the round-trip doubled to ~60ms. That 30ms was never an OS floor; it was the peripheral’s own request. Two things that did not get in the way: Android needs no requestConnectionPriority(CONNECTION_PRIORITY_HIGH), because the peripheral’s parameter update request is honoured regardless of the app’s connection priority, and the iPhone did not show the 15ms→30ms scale-up that Apple’s QA1931 warns some devices apply. The trade-off is power draw.

Note for anyone reproducing these numbers: the peripheral only sends its parameter update request CONFIG_BT_CONN_PARAM_UPDATE_TIMEOUT (5s) after connecting, and centrals start elsewhere — macOS and iOS at 30ms, the Pixel 5 at 45ms. Every benchmark above waits 5.5s after connecting so that it measures the negotiated interval rather than the central’s initial one.

Encryption Overhead

E2E encryption adds 20 bytes per transaction (4-byte counter + 16-byte AES-GCM tag). For large payloads (8 KB), this overhead is negligible (<0.3%). The AES-128-GCM encryption/decryption itself is hardware-accelerated on modern mobile devices and takes <1ms even for 8 KB payloads.

Throughput Optimizations