Skip to content

Conversation

@cboulay
Copy link
Collaborator

@cboulay cboulay commented Jan 19, 2026

This PR adds a new transp_sync_blocking transport flag that enables synchronous, zero-copy data transfer for stream outlets. Instead of copying sample data into an internal buffer for async delivery, sync mode writes directly from the user's buffer to connected sockets, eliminating memory allocation and copy overhead.

It is intended to be a replacement for #170 , which has not been updated in some time.

Motivation

For high-channel-count, high-sample-rate applications (e.g., 1000+ channels at 30kHz), the async outlet's per-sample memory allocation and copying becomes a significant CPU bottleneck. Sync mode addresses this by:

  • Zero-copy transfer: Sample data is written directly from the user's buffer
  • No per-sample allocation: Timestamps are stored in a small metadata buffer, but sample data isn't copied
  • Blocking semantics: push_sample/push_chunk blocks until data is sent to all consumers

This makes all the difference for me on a lower power embedded system

Usage

lsl::stream_info info("MyStream", "EEG", 1000, 30000, lsl::cf_float32);
lsl::stream_outlet outlet(info, 0, 360, transp_sync_blocking);

std::vector<float> sample(1000);
outlet.push_sample(sample);  // Blocks until sent to all consumers

Limitations

  • String format not supported: Sync mode requires fixed-size samples
  • Blocking: Push operations block until all connected consumers receive data
  • Latency scales with consumers: More consumers = longer push latency

Benchmark Results

Test configuration: 1000 channels, 30kHz sample rate, macOS (Apple Silicon)

CPU Usage by Chunk Size (1 consumer)

Chunk Size Async (µs/sample) Sync (µs/sample) CPU Savings
1 31.18 15.65 50%
4 9.05 5.06 44%
8 7.76 6.37 18%
16 9.76 6.88 30%
30 9.82 8.34 15%

Scaling with Multiple Consumers (chunk=4)

Consumers Async (µs/sample) Sync (µs/sample) CPU Savings
1 10.15 4.95 51%
2 21.95 9.84 55%
3 33.82 15.91 53%

CPU savings remain significant (~50%) across consumer counts. However, push latency increases linearly with consumers in sync mode (async latency stays constant).

Implementation Details

  • New sync_write_handler class manages connected sockets grouped by endianness
  • tcp_server hands off sockets to sync handler after protocol negotiation
  • Optimized enqueue_chunk_sync() batches timestamp+data buffers for efficient gather-write
  • sync_timestamps_ uses std::deque to ensure pointer stability during buffer accumulation

@cboulay cboulay force-pushed the pugixml_fetch_only branch from a33e2a2 to 0f7bdc2 Compare January 19, 2026 17:16
 1. User creates outlet with transp_sync_blocking flag:
  lsl::stream_outlet outlet(info, 0, 360, transp_sync_blocking);
  2. When a consumer connects, the socket is handed off from client_session to sync_write_handler after the feed header handshake (no transfer thread is spawned).
  3. When push_sample() is called:
    - Timestamp is encoded and stored in sync_timestamps_
    - User's data buffer pointer is wrapped in asio::const_buffer (zero copy)
    - If pushthrough=true, all buffers are written to all consumers via blocking gather-write
…per consistent with stream_info and properly throws on construction failure.
  - Fix have_consumers()/wait_for_consumers() to detect sync consumers
  - Handle DEDUCED_TIMESTAMP in sync mode for proper chunk timing
  - Change sync_timestamps_ to deque to prevent pointer invalidation
  - Add optimized enqueue_chunk_sync() for batched chunk transfers
  - Add have_sync_consumers() to tcp_server
  - Add sync outlet tests and benchmark tool
… in the namespace)

  2. Added #include <algorithm> for std::sort
…ync<std::string>, which resolves the Windows linker error
2. Replaced C++17 structured bindings with .first/.second pair access for C++11/14 compatibility
@cboulay cboulay force-pushed the cboulay/sync_outlet branch from f7e32c2 to ce58eb6 Compare January 19, 2026 17:17
Base automatically changed from pugixml_fetch_only to dev January 24, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants