Skip to content

perf: add an explicit Buffer compression policy while preserving Python RNS compatibility #18

Description

@mytecor

Problem

buffer.RawChannelWriter.Write automatically attempts bzip2 compression for every payload larger than 32 bytes. It tries up to three candidate lengths (len/1, len/2, and len/3) before falling back to an uncompressed MDU-sized stream message.

This matches the current Python RNS RawChannelWriter algorithm, but neither implementation exposes a supported way for a stream application to declare that its payload is already compressed or effectively incompressible. TLS, SSH, archives, media, encrypted application protocols, and generic TCP tunnels therefore pay the compression cost for almost every Channel message without saving wire bytes.

This is especially expensive because Buffer is a stream abstraction: the same failed compression probes are repeated throughout a long-lived byte stream.

Measured impact

Reticulum-Go v1.2.0, macOS arm64, Go 1.27.1, private Backbone/TCP -> Link -> Channel -> Buffer path on loopback, incompressible crypto/rand payload:

workload elapsed / throughput
1 MiB single stream 2.392 s / 0.42 MiB/s
10 MiB single stream 17.540 s / 0.57 MiB/s
512-byte request + 2048-byte response on an established Link 6.314 ms per round trip
10 concurrent x 1 MiB 9.007 s total / 1.11 MiB/s aggregate

The comparison transport on the same host and workload reaches 98-170 MiB/s for the single-stream rows. Link establishment is not the dominant cost: a fresh connection plus one-byte ping/pong is 344 microseconds.

A compression-only Python RNS 1.5.2 probe over 1 MiB of random data measured:

  • 0.756 s when input is pre-fragmented to 457-byte payloads;
  • 9.096 s with the reference writer shape, where each failed call probes up to 16 KiB but advances only one uncompressed MDU.

The performance problem is therefore not a Go-vs-Python wire behavior mismatch. It is inherited automatic-compression behavior that has no supported application policy control.

Compatibility requirement

Any solution must preserve full interoperability with Python RNS nodes:

  1. Do not change StreamDataMessage wire encoding or compression flag semantics.
  2. Keep the existing automatic policy as the default so current Go behavior continues to match Python RNS.
  3. A sender that disables compression must emit the existing uncompressed StreamDataMessage form (Compressed == false), which Python RNS already accepts.
  4. Receivers must continue to accept both compressed and uncompressed stream messages regardless of their local writer policy.
  5. Prefer an API shape that can also be introduced in Python RNS, with equivalent semantics and naming where practical. This should not create a Go-specific protocol dialect.

Proposed solution

Add an explicit writer compression policy, for example:

type CompressionPolicy uint8

const (
    CompressionAuto CompressionPolicy = iota // current Python-compatible default
    CompressionDisabled
)

type WriterOptions struct {
    Compression CompressionPolicy
}

func NewRawChannelWriterWithOptions(streamID int, ch *channel.Channel, opts WriterOptions) *RawChannelWriter

The existing NewRawChannelWriter, CreateWriter, and CreateBidirectionalBuffer should retain CompressionAuto. Equivalent option-aware buffered constructors would let applications opt out without replacing the stock Buffer framing or defining a custom Channel message type.

Please add benchmarks covering:

  • incompressible input;
  • highly compressible input;
  • mixed input;
  • automatic vs disabled policy;
  • small interactive writes and sustained multi-MiB streams.

It may also be worth discussing an adaptive policy later, but a deterministic opt-out is important for applications that already know the content characteristics.

Python RNS comparison

Python RNS master currently has the same three-probe bzip2 algorithm in RNS/Buffer.py. RawChannelWriter, create_writer, and create_bidirectional_buffer do not accept a compression option. RNS.Resource separately supports auto_compress=False, but that API does not provide Buffer stream semantics.

The fixed Go MaxDataLen vs Python's live channel.mdu is a separate parity/correctness issue already recorded in #17. This issue is intentionally scoped to compression policy and performance.

Alternatives considered

  • Changing the default to no compression: rejected because it would diverge from Python RNS behavior and could regress low-bandwidth links.
  • Changing the wire format: unnecessary and incompatible; the existing uncompressed stream-message representation is sufficient.
  • Downstream monkey patches or custom stream message types: avoidable fragmentation of the ecosystem and not acceptable for applications that require stock Python-node interoperability.
  • Using Resource(auto_compress=false): not a replacement for a continuous bidirectional Buffer stream.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions