Memory Architecture: Aegis Packet Engine
1. Zero-Allocation Pipeline Principles
In low-latency systems, dynamic memory management (malloc, free, new, delete) is prohibited during execution. Aegis achieves zero heap allocation on the fast path by leveraging pre-allocated memory pools.
Startup (Heap allocation permitted)
│
▼
+-------------------------------------+
| PacketBufferPool FlowEntryPool |
| (Flat arrays of pre-allocated blocks|
+-------------------------------------+
│
▼
Packet Processing Ingestion
+-------------------------------------+
| Acquire index -> Parse -> Track |
| (No new/malloc, uses indices/views) |
+-------------------------------------+
│
▼
Packet Reclamation
+-------------------------------------+
| Release index back to free list |
+-------------------------------------+
2. Pre-allocated Packet Buffer Pool & Memory Mapping
Aegis defines a PacketBufferPool that manages a single contiguous block of memory divided into fixed-size segments.
Contiguous Buffer Pool (Mapped at startup):
+---------------------------------------------------------------------------------+
| PacketBlock 0 | PacketBlock 1 | PacketBlock 2 | ... |
| [2048 Bytes] | [2048 Bytes] | [2048 Bytes] | |
+--------------------+--------------------+--------------------+------------------+
^ ^
| |
| (Ref Pointer) | (Ref Pointer)
| |
+--------------------+ |
| PacketJob 0 | |
| - block_idx = 0 | |
| - eth_offset = 0 | |
| - ip_offset = 14 | |
+--------------------+ |
|
+--------------------+
| PacketJob 1 |
| - block_idx = 1 |
| - eth_offset = 0 |
| - ip_offset = 14 |
+--------------------+
Key Data Structures:
- Buffer Block:
- Free Index Queue:
- A lock-free SPSC/MPMC queue storing index IDs of free blocks:
0, 1, 2, ..., PoolSize-1. - Acquire a block:
size_t idx = free_indices.pop(); - Release a block:
3. Flat Cache-Friendly Flow Table
Rather than using a pointer-heavy node-based hash map (like std::unordered_map which does a heap allocation for every insertion), Aegis implements a dense bucket hash table or a flat pre-allocated map.
Design Choices:
- Pre-allocated Buckets:
- Collision Resolution:
- Chaining is done via indices pointing to other nodes in the flat
FlowPoolarray, ensuring all data is sequentially and contiguously stored in memory. This improves cache-line prefetching. - Fast Path threads run with zero allocation when tracking new connections. If the map runs out of capacity, it drops the flow or evicts the oldest via a circular list (LRU tracking).
4. Avoiding std::string Allocations
The original ParsedPacket structure uses std::string src_ip; and std::string dest_ip; to represent parsed IP addresses. This is extremely slow because it requires a heap allocation for the string copy.
Aegis Representation:
- IPv4 Address:
uint32_t(4 bytes). - IPv6 Address:
std::array<uint8_t, 16>(16 bytes). - MAC Address:
std::array<uint8_t, 6>(6 bytes). - Formating: Formatting to human-readable strings (e.g.
192.168.1.1) is only done during reporting, logging, or CLI output. The core engine passes and matches these fields using raw integer operations. - Payload Views: Parsers expose payload fields via
std::string_viewwhich points directly into the raw packet buffer block memory.