Best Practices¶
Learn how to use ZeroIPC effectively and avoid common pitfalls.
Quick Guidelines¶
Do's¶
- Use fixed-width integer types (
int32_tnotint) - Document shared data structures and types
- Choose appropriate table sizes for your workload
- Clean up test shared memory segments
- Use meaningful names for segments and structures
- Handle errors appropriately (check return values)
- Test cross-language compatibility early
Don'ts¶
- Don't use platform-dependent types (
long,size_t) for shared data - Don't assume structure exists without checking
- Don't fill tables to 100% capacity
- Don't create circular dependencies
- Don't ignore type size mismatches
- Don't leak shared memory segments
- Don't rely on undefined behavior
Best Practices by Topic¶
Performance Tips¶
Optimize for maximum throughput and minimum latency:
- Choose appropriate data structures
- Minimize contention
- Use proper memory ordering
- Batch operations when possible
- Pre-allocate structures
- Monitor utilization
Common Pitfalls¶
Avoid these frequent mistakes:
- Type size mismatches
- Table overflow
- Memory leaks
- Race conditions
- Deadlocks (with user locks)
- Name collisions
- Incorrect cleanup
Type Safety¶
Maintain consistency across languages:
- Use shared type definitions
- Document type mappings
- Create type verification utilities
- Test cross-language compatibility
- Use fixed-width types
- Align structure members
Error Handling¶
Handle errors gracefully:
- Check return values
- Handle
std::optional/Nonereturns - Validate inputs
- Provide clear error messages
- Fail fast on corruption
- Log errors appropriately
Testing Applications¶
Test shared memory applications:
- Unit test each component
- Integration test cross-language
- Stress test under high concurrency
- Test error paths
- Verify cleanup
- Use unique names in tests
Code Style Guide¶
C++ Style¶
// Good: Clear, safe, idiomatic
#include <zeroipc/memory.h>
#include <zeroipc/array.h>
void process_data() {
// Use RAII for automatic cleanup
zeroipc::Memory mem("/sensors", 1024*1024);
// Use fixed-width types
zeroipc::Array<int32_t> data(mem, "readings", 1000);
// Check optional returns
if (auto value = data.at(0)) {
std::cout << "First value: " << *value << "\n";
}
// Clear names
constexpr size_t SENSOR_COUNT = 100;
}
// Bad: Unclear, unsafe, non-idiomatic
void bad_example() {
auto m = new zeroipc::Memory("/x", 999999); // Manual alloc
zeroipc::Array<long> d(m, "d", 9999); // Platform-dependent type
cout << d[0]; // No error checking
// Memory leak - never deleted
}
Python Style¶
# Good: Clear, type-safe, Pythonic
from zeroipc import Memory, Array
import numpy as np
from typing import Optional
def process_data() -> None:
"""Process sensor data from shared memory."""
# Use context managers (when available)
mem = Memory("/sensors")
# Explicit dtype
data = Array(mem, "readings", dtype=np.int32)
# Check None
value = data[0]
if value is not None:
print(f"First value: {value}")
# Clear constants
SENSOR_COUNT = 100
# Bad: Unclear, fragile
def bad_example():
m = Memory("/x")
d = Array(m, "d") # Missing dtype!
print(d[0]) # May crash
Architecture Patterns¶
Pattern 1: Publisher-Subscriber¶
One publisher, multiple subscribers:
// Publisher
zeroipc::Memory mem("/events", 10*1024*1024);
zeroipc::Stream<Event> events(mem, "stream", 1000);
while (running) {
Event e = generate_event();
events.emit(e);
}
// Subscribers (many processes)
zeroipc::Memory mem("/events");
zeroipc::Stream<Event> events(mem, "stream");
events.subscribe([](Event& e) {
process_event(e);
});
Pattern 2: Request-Response¶
Use futures for RPC-like patterns:
// Server
zeroipc::Memory mem("/rpc", 10*1024*1024);
zeroipc::Future<Response> result(mem, "response");
Request req = get_request();
Response res = process(req);
result.set_value(res);
// Client
zeroipc::Memory mem("/rpc");
zeroipc::Future<Response> result(mem, "response", true); // Read-only
if (auto res = result.get_for(std::chrono::seconds(5))) {
use_response(*res);
} else {
handle_timeout();
}
Pattern 3: Pipeline¶
Chain processing stages:
// Stage 1: Raw data
zeroipc::Stream<RawData> raw(mem, "raw", 1000);
// Stage 2: Validated data
auto validated = raw.filter(mem, "validated",
[](RawData& d) { return d.is_valid(); });
// Stage 3: Transformed data
auto transformed = validated.map(mem, "transformed",
[](RawData& d) { return transform(d); });
// Stage 4: Final processing
transformed.subscribe([](TransformedData& d) {
final_process(d);
});
Documentation Templates¶
Shared Type Definition¶
Create shared headers:
shared_types.hpp (C++):
#pragma once
#include <cstdint>
namespace shared {
struct SensorReading {
float temperature;
float humidity;
uint64_t timestamp;
};
constexpr size_t MAX_SENSORS = 100;
constexpr char SEGMENT_NAME[] = "/sensors";
} // namespace shared
shared_types.py (Python):
"""Shared type definitions for sensor application."""
import numpy as np
# Match C++ struct SensorReading
SENSOR_READING_DTYPE = np.dtype([
('temperature', np.float32),
('humidity', np.float32),
('timestamp', np.uint64),
])
MAX_SENSORS = 100
SEGMENT_NAME = "/sensors"
Checklist¶
Use this checklist for new projects:
- Defined shared types in both languages
- Chosen appropriate table size
- Documented segment names
- Added error handling
- Created cleanup procedures
- Written unit tests
- Written integration tests
- Tested cross-language compatibility
- Added monitoring/debugging support
- Documented deployment requirements
Next Steps¶
Dive deeper into specific topics: