Skip to content

Track actor-model memory and performance optimizations #194

Description

@artrixdotdev

Context

Audit of the current Rust actor model and lightweight profiling on Fri May 22 2026. No memory-related changes were made.

Measurements

  • cargo test --workspace: passed, 17 unit/integration tests and 21 doctests passed; 7 runtime tests and 8 doctests ignored.
  • cargo nextest run --workspace: passed, 17 tests in about 0.027s after build.
  • cargo clippy --workspace --all-targets -- -D warnings: passed.
  • hyperfine --warmup 2 'cargo test --workspace --quiet': mean 1.402s ± 0.047s.
  • perf stat ... cargo test --workspace --quiet: 1.43s elapsed, 161,042 page faults, 11.19B instructions, 7.52B cycles.
  • Sampled peak RSS while running cargo test --workspace --quiet: about 281,636 KiB. This includes Cargo, rustdoc, and test-process overhead, not only the library runtime.
  • Release artifacts:
    • target/release/tortillas: 872K
    • target/release/liblibtortillas.rlib: 14M

Observations

  • Torrent and peer actor boundaries are a good fit for isolating per-peer protocol state, torrent scheduling state, and tracker communication.
  • Several request paths clone potentially large state:
    • TorrentRequest::Bitfield returns Arc::new(self.bitfield.clone())
    • TorrentRequest::HasInfoDict clones Info
    • TorrentRequest::Export clones metainfo, info, bitfield, and block maps
  • PeerActor::determine_interest clones both peer and torrent bitfields before computing interest. This can become expensive with many pieces and peers.
  • Piece validation currently reads a full piece into memory after validating it, then sends that Bytes to the piece manager. This is simple but creates a per-completed-piece allocation equal to piece size.
  • Outgoing PeerMessages::Piece serialization builds a new payload buffer containing headers plus the block bytes. That copies block payloads on upload.
  • PieceStoreActor serializes all disk writes and validations through one actor, which simplifies safety but can become a throughput bottleneck under many peers and torrents.
  • Peer pending block requests use Arc<DashSet<_>> even though access appears actor-local. That likely adds unnecessary synchronization and allocation overhead.
  • Default torrent mailbox is bounded at 64, but peer actor mailbox sizing and backpressure should be reviewed for heavy piece traffic.
  • reqwest with default features plus tokio = full likely pulls in more dependencies and runtime surface than needed. This affects binary and build size more than hot-path memory, but is worth trimming before release.

Follow-up Work

  • Add deterministic microbenchmarks for:
    • message encode/decode
    • scheduler request selection
    • bitfield interest checks
    • piece validation/write flow
  • Add an end-to-end local swarm benchmark that avoids public trackers and records throughput, allocations/RSS, peer count, and block latency.
  • Profile a real download and seeding scenario with heap tools available in CI or dev docs, such as heaptrack or Valgrind Massif where available.
  • Replace bitfield and info clones on hot actor requests with shared immutable state or borrowed computations where possible.
  • Consider changing PeerActor::pending_block_requests from Arc<DashSet<_>> to an actor-local HashSet if no cross-thread access is required.
  • Investigate streaming or zero-copy paths for piece validation and upload serialization.
  • Review mailbox capacities and backpressure policy for piece-heavy workloads.
  • Trim dependency features, especially tokio = full and reqwest defaults, once runtime requirements are clear.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions