|
ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
|
🗃️ Pre-allocated memory buffers for efficient allocation More...
Files | |
| file | buffer_pool.c |
| 💾 Lock-free memory pool with atomic operations | |
| file | buffer_pool.h |
| 🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation | |
Data Structures | |
| struct | buffer_node |
| Node header embedded before user data. More... | |
| struct | buffer_pool |
| Unified buffer pool with lock-free fast path. More... | |
Macros | |
| #define | BUFFER_POOL_MAX_BYTES (337 * 1024 * 1024) |
| Maximum total bytes the pool can hold (337 MB) | |
| #define | BUFFER_POOL_SHRINK_DELAY_MS 5000 |
| Time in milliseconds before unused buffers are freed (5 seconds) | |
| #define | BUFFER_POOL_MIN_SIZE 64 |
| Minimum buffer size to pool (smaller allocations use malloc directly) | |
| #define | BUFFER_POOL_MAX_SINGLE_SIZE (4 * 1024 * 1024) |
| Maximum single buffer size to pool (larger allocations use malloc directly) | |
| #define | BUFFER_POOL_MAGIC 0xBF00B001U |
| Magic value to identify pooled buffers. | |
| #define | BUFFER_POOL_MAGIC_FALLBACK 0xBF00FA11U |
| Magic value for malloc fallback buffers (not in pool) | |
| #define | POOL_ALLOC(size) buffer_pool_alloc(NULL, (size)) |
| #define | POOL_FREE(data, size) buffer_pool_free(NULL, (data), (size)) |
Typedefs | |
| typedef struct buffer_node | buffer_node_t |
| Node header embedded before user data. | |
| typedef struct buffer_pool | buffer_pool_t |
| Unified buffer pool with lock-free fast path. | |
Functions | |
| buffer_pool_t * | buffer_pool_create (size_t max_bytes, uint64_t shrink_delay_ms) |
| Create a new buffer pool. | |
| void | buffer_pool_destroy (buffer_pool_t *pool) |
| Destroy a buffer pool and free all memory. | |
| void * | buffer_pool_alloc (buffer_pool_t *pool, size_t size) |
| Allocate a buffer from the pool (lock-free fast path) | |
| void | buffer_pool_free (buffer_pool_t *pool, void *data, size_t size) |
| Free a buffer back to the pool (lock-free) | |
| void | buffer_pool_shrink (buffer_pool_t *pool) |
| Force shrink the pool (free old unused buffers) | |
| void | buffer_pool_get_stats (buffer_pool_t *pool, size_t *current_bytes, size_t *used_bytes, size_t *free_bytes) |
| Get pool statistics (atomic reads) | |
| void | buffer_pool_log_stats (buffer_pool_t *pool, const char *name) |
| Log pool statistics. | |
| void | buffer_pool_init_global (void) |
| void | buffer_pool_cleanup_global (void) |
| buffer_pool_t * | buffer_pool_get_global (void) |
🗃️ Pre-allocated memory buffers for efficient allocation
A mostly lock-free memory pool using atomic operations for the fast path. Allocations and frees use CAS on a lock-free stack. Only shrinking needs a lock.
Default max: 337 MB (supports 32 clients at 144fps)
Welcome! Let's talk about the buffer pool system—one of the secret weapons for making ascii-chat's real-time video streaming fast and smooth.
You know how constantly calling malloc() and free() can slow things down? Well, imagine doing that 30 times per second for video frames, per client! The buffer pool solves this by pre-allocating a bunch of memory buffers up front, so when you need one, it's ready to go. No waiting for the system allocator, no frame drops, no latency spikes—just grab a buffer and get to work.
Think of it like having a stack of clean plates ready for a dinner party. Instead of washing a plate every time someone needs one, you just grab from the stack. Much faster!
Implementation: lib/buffer_pool.c/h
The buffer pool isn't one-size-fits-all. Instead, it provides four different size classes, each optimized for different types of data you'll be working with:
| Size Class | Buffer Size | Pool Count | Total Memory | What's it good for? |
|---|---|---|---|---|
| Small | 1 KB | 1024 | 1 MB | Audio packets (nice and compact) |
| Medium | 64 KB | 64 | 4 MB | Small video frames |
| Large | 256 KB | 32 | 8 MB | Large video frames |
| XLarge | 2 MB | 64 | 128 MB | HD video frames (the big stuff) |
Total pre-allocated memory: ~141 MB (yeah, it's a decent chunk, but remember—this eliminates malloc overhead for thousands of allocations per second!)
So how does the buffer pool decide which buffer to give you? It's pretty straightforward:
Here's how it works in practice:
Individual buffer in the pool:
Pool for one size class:
Multi-size pool coordinator:
For most cases, you'll want to use the global singleton pool. It's simple and convenient— one pool for your whole application:
CRITICAL: You must pass the same size to buffer_pool_free() that you used in buffer_pool_alloc(). The pool needs to know which size class to return the buffer to. If you mix this up, bad things happen!
For isolated subsystems, create dedicated pools:
Track hit rate for performance tuning:
Per-size-class analysis:
Log comprehensive stats:
Output example:
* [buffer_pool] Global pool statistics: * Small (1KB): hits=45231 misses=12 peak=890/1024 (86.9%) * Medium (64KB): hits=8901 misses=156 peak=48/64 (75.0%) * Large (256KB): hits=3421 misses=89 peak=28/32 (87.5%) * XLarge (2MB): hits=142 misses=3 peak=45/64 (70.3%) * Overall hit rate: 98.7% *
Okay, let's talk numbers. How much faster is the buffer pool compared to regular malloc/free? The answer: dramatically faster.
| Operation | Buffer Pool | malloc/free | Speedup |
|---|---|---|---|
| Allocate 64KB | 120 ns | 2,400 ns | 20x faster |
| Allocate 256KB | 135 ns | 8,100 ns | 60x faster |
| Free 64KB | 95 ns | 1,200 ns | 12.6x faster |
| Free 256KB | 98 ns | 3,800 ns | 38.8x faster |
Test system: AMD Ryzen 9 5950X, 64GB DDR4-3200, Linux 6.1
But what does this mean for real-time video? Glad you asked!
For 30 FPS video (you've got 33.3ms per frame to do everything):
Now scale that up to 9 clients × 30 FPS = 270 frames/sec:
The buffer pool is fully thread-safe:
Synchronization: Single mutex protects all four pools. This is acceptable because buffer operations are very fast (<200ns). For higher concurrency, consider per-pool mutexes.
Adjust pool sizes in buffer_pool.h based on workload:
Rule of thumb:
Watch for malloc fallbacks in production:
| #define BUFFER_POOL_MAGIC 0xBF00B001U |
#include <buffer_pool.h>
Magic value to identify pooled buffers.
Definition at line 58 of file buffer_pool.h.
| #define BUFFER_POOL_MAGIC_FALLBACK 0xBF00FA11U |
#include <buffer_pool.h>
Magic value for malloc fallback buffers (not in pool)
Definition at line 60 of file buffer_pool.h.
| #define BUFFER_POOL_MAX_BYTES (337 * 1024 * 1024) |
#include <buffer_pool.h>
Maximum total bytes the pool can hold (337 MB)
Definition at line 46 of file buffer_pool.h.
| #define BUFFER_POOL_MAX_SINGLE_SIZE (4 * 1024 * 1024) |
#include <buffer_pool.h>
Maximum single buffer size to pool (larger allocations use malloc directly)
Definition at line 55 of file buffer_pool.h.
| #define BUFFER_POOL_MIN_SIZE 64 |
#include <buffer_pool.h>
Minimum buffer size to pool (smaller allocations use malloc directly)
Definition at line 52 of file buffer_pool.h.
| #define BUFFER_POOL_SHRINK_DELAY_MS 5000 |
#include <buffer_pool.h>
Time in milliseconds before unused buffers are freed (5 seconds)
Definition at line 49 of file buffer_pool.h.
| #define POOL_ALLOC | ( | size | ) | buffer_pool_alloc(NULL, (size)) |
#include <buffer_pool.h>
Definition at line 177 of file buffer_pool.h.
| #define POOL_FREE | ( | data, | |
| size | |||
| ) | buffer_pool_free(NULL, (data), (size)) |
#include <buffer_pool.h>
Definition at line 178 of file buffer_pool.h.
| typedef struct buffer_node buffer_node_t |
#include <buffer_pool.h>
Node header embedded before user data.
Layout in memory: [buffer_node_t header][user data...] ^– pointer returned to caller
| typedef struct buffer_pool buffer_pool_t |
#include <buffer_pool.h>
Unified buffer pool with lock-free fast path.
| void * buffer_pool_alloc | ( | buffer_pool_t * | pool, |
| size_t | size | ||
| ) |
#include <buffer_pool.h>
Allocate a buffer from the pool (lock-free fast path)
| pool | Buffer pool (NULL = use global pool) |
| size | Size of buffer to allocate |
Definition at line 121 of file buffer_pool.c.
References buffer_node::_pad, buffer_pool_get_global(), BUFFER_POOL_MAGIC, BUFFER_POOL_MAGIC_FALLBACK, BUFFER_POOL_MAX_SINGLE_SIZE, buffer_node::magic, buffer_node::pool, SAFE_MALLOC, SAFE_MALLOC_ALIGNED, and buffer_node::size.
Referenced by acip_send_ascii_frame(), acip_send_audio_batch(), acip_send_audio_opus_batch(), acip_send_error(), acip_send_image_frame(), acip_send_remote_log(), av_send_audio_opus_batch(), framebuffer_peek_latest_multi_frame(), framebuffer_write_frame(), framebuffer_write_multi_frame(), image_new_from_pool(), packet_queue_enqueue(), packet_queue_enqueue_packet(), packet_receive(), packet_send_via_transport(), process_encrypted_packet(), receive_packet_secure(), send_ascii_frame_packet(), send_audio_batch_packet(), send_image_frame_packet(), send_packet_secure(), tcp_client_send_audio_opus(), threaded_send_audio_opus(), and video_frame_buffer_create().
| void buffer_pool_cleanup_global | ( | void | ) |
#include <buffer_pool.h>
Definition at line 408 of file buffer_pool.c.
References buffer_pool_destroy(), and buffer_pool_log_stats().
Referenced by asciichat_shared_init(), and server_main().
| buffer_pool_t * buffer_pool_create | ( | size_t | max_bytes, |
| uint64_t | shrink_delay_ms | ||
| ) |
#include <buffer_pool.h>
Create a new buffer pool.
| max_bytes | Maximum bytes the pool can hold (0 = use default) |
| shrink_delay_ms | Time before unused buffers freed (0 = use default) |
Definition at line 70 of file buffer_pool.c.
References BUFFER_POOL_MAX_BYTES, BUFFER_POOL_SHRINK_DELAY_MS, ERROR_MEMORY, ERROR_THREAD, format_bytes_pretty(), log_info, max_bytes, mutex_init(), SAFE_FREE, SAFE_MALLOC, SET_ERRNO, shrink_delay_ms, and shrink_mutex.
Referenced by buffer_pool_init_global(), and packet_queue_create_with_pools().
| void buffer_pool_destroy | ( | buffer_pool_t * | pool | ) |
#include <buffer_pool.h>
Destroy a buffer pool and free all memory.
| pool | Pool to destroy |
Definition at line 105 of file buffer_pool.c.
References mutex_destroy(), SAFE_FREE, and shrink_mutex.
Referenced by buffer_pool_cleanup_global(), and packet_queue_destroy().
| void buffer_pool_free | ( | buffer_pool_t * | pool, |
| void * | data, | ||
| size_t | size | ||
| ) |
#include <buffer_pool.h>
Free a buffer back to the pool (lock-free)
| pool | Buffer pool (NULL = auto-detect from buffer) |
| data | Buffer to free |
| size | Size of buffer (used for fallback, can be 0 if pooled) |
Definition at line 214 of file buffer_pool.c.
References BUFFER_POOL_MAGIC, BUFFER_POOL_MAGIC_FALLBACK, buffer_pool_shrink(), log_error, buffer_node::magic, buffer_node::pool, SAFE_FREE, and buffer_node::size.
Referenced by acds_client_handler(), acds_session_create(), acds_session_join(), acds_session_lookup(), acip_client_receive_and_dispatch(), acip_send_ascii_frame(), acip_send_audio_batch(), acip_send_audio_opus_batch(), acip_send_error(), acip_send_image_frame(), acip_send_remote_log(), acip_server_receive_and_dispatch(), audio_ring_buffer_destroy(), av_send_audio_opus_batch(), client_crypto_handshake(), crypto_handshake_client_auth_response(), crypto_handshake_client_complete(), crypto_handshake_client_key_exchange(), crypto_handshake_server_auth_challenge(), crypto_handshake_server_complete(), framebuffer_clear(), framebuffer_write_frame(), framebuffer_write_multi_frame(), image_destroy(), image_destroy_to_pool(), packet_queue_enqueue(), packet_queue_enqueue_packet(), packet_queue_free_packet(), packet_queue_try_dequeue(), packet_receive(), packet_send_via_transport(), process_encrypted_packet(), receive_packet_secure(), send_ascii_frame_packet(), send_audio_batch_packet(), send_image_frame_packet(), send_packet_secure(), server_crypto_handshake(), tcp_client_send_audio_opus(), threaded_send_audio_opus(), and video_frame_buffer_destroy().
| buffer_pool_t * buffer_pool_get_global | ( | void | ) |
#include <buffer_pool.h>
Definition at line 418 of file buffer_pool.c.
Referenced by buffer_pool_alloc(), packet_queue_enqueue(), packet_queue_enqueue_packet(), video_frame_buffer_create(), and video_frame_buffer_destroy().
| void buffer_pool_get_stats | ( | buffer_pool_t * | pool, |
| size_t * | current_bytes, | ||
| size_t * | used_bytes, | ||
| size_t * | free_bytes | ||
| ) |
#include <buffer_pool.h>
Get pool statistics (atomic reads)
Definition at line 332 of file buffer_pool.c.
| void buffer_pool_init_global | ( | void | ) |
#include <buffer_pool.h>
Definition at line 397 of file buffer_pool.c.
References buffer_pool_create(), and log_info.
Referenced by asciichat_shared_init().
| void buffer_pool_log_stats | ( | buffer_pool_t * | pool, |
| const char * | name | ||
| ) |
#include <buffer_pool.h>
Log pool statistics.
Definition at line 354 of file buffer_pool.c.
References format_bytes_pretty(), log_info, and max_bytes.
Referenced by buffer_pool_cleanup_global(), and packet_queue_destroy().
| void buffer_pool_shrink | ( | buffer_pool_t * | pool | ) |
#include <buffer_pool.h>
Force shrink the pool (free old unused buffers)
| pool | Buffer pool |
Definition at line 268 of file buffer_pool.c.
References mutex_trylock, mutex_unlock, SAFE_FREE, shrink_delay_ms, shrink_mutex, and buffer_node::size.
Referenced by buffer_pool_free().