性能优化技巧
Aeron 以极致性能为目标,代码中集成了大量底层优化技巧。本文档系统性地整理这些技巧,覆盖 Java 和 C/C++,每项均提供精确的源码引用。
1. Lock-Free 与 CAS
Aeron 避免使用互斥锁,所有并发操作通过 CAS(Compare-And-Swap)实现。
VarHandle 背景:Java 8 中原子操作依赖
sun.misc.Unsafe(内部 API,JDK 9+ 计划移除)和AtomicXxx类(每个变量一个对象,有 GC 压力)。Java 9 引入VarHandle作为替代——对任意对象的任意字段执行 CAS、get/set-volatile、memory fence,无额外对象分配,类型安全,由MethodHandles.lookup().findVarHandle()在类加载时一次性绑定。Aeron 中所有 Java 侧 CAS 均基于 VarHandle 实现。
1.1 Java:CAS 保护单次语义
多个关键入口使用 VarHandle.compareAndSet 确保 close()/conclude() 只执行一次。失败的 CAS 直接返回,无阻塞。
// 声明
private static final VarHandle IS_CLOSED_VH;
static {
IS_CLOSED_VH = MethodHandles.lookup().findVarHandle(
Aeron.class, "isClosed", boolean.class);
}
// 使用 — 只有一个线程能进入关闭逻辑
public void close() {
if (IS_CLOSED_VH.compareAndSet(this, false, true)) {
// ... 关闭资源 ...
}
}
同样模式用于:
- Counter.java:31-43,123 — 计数器关闭
- MediaDriver.java:543-554,735 — 驱动上下文关闭
- CommonContext.java:553-567,650 —
getAndSet防止多次 conclude
1.2 Java:多 Producer 并发写 Term Buffer
多个 publisher 线程通过原子加法竞争 term buffer 的 tail counter,无需锁。
ConcurrentPublication.java:359
final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, alignedLength);
final int termId = termId(rawTail);
final int termOffset = termOffset(rawTail, termLength);
getAndAddLong 是原子的 fetch-and-add,底层是 Unsafe.getAndAddLong(x86: lock xadd)。
1.3 Java:CAS 驱动 Term 翻转
LogBufferDescriptor.java:1034-1042
public static boolean casRawTail(
final UnsafeBuffer metadataBuffer, final int partitionIndex,
final long expectedRawTail, final long updateRawTail) {
final int index = TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex);
return metadataBuffer.compareAndSetLong(index, expectedRawTail, updateRawTail);
}
当 term 写满时,通过 CAS 翻转 tail counter 到下一个 term。只有一个生产者能成功翻转。
1.4 C:x86-64 内联汇编 CAS
aeron_atomic64_gcc_x86_64.h:69-80
inline bool aeron_cas_int64(volatile int64_t *dst, int64_t expected, int64_t desired)
{
int64_t original;
__asm__ __volatile__(
"lock; cmpxchgq %2, %1"
: "=a"(original), "+m"(*dst)
: "r"(desired), "0"(expected)
: "memory", "cc");
return original == expected;
}
直接使用 lock cmpxchgq 指令,无库函数开销。LOCK 前缀保证多核原子性。
1.5 C:跨平台 CAS 抽象
同一套 API,多种底层实现:
| 平台 | 文件 | 实现 |
|---|---|---|
| GCC x86-64 | aeron_atomic64_gcc_x86_64.h | lock cmpxchgq / lock xaddq |
| C11 (ARM) | aeron_atomic64_c11.h:65-78 | atomic_compare_exchange_strong |
| MSVC x86-64 | aeron_atomic64_msvc.h:80-86 | _InterlockedCompareExchange64 |
1.6 C:MPSC Ring Buffer CAS
while (!aeron_cas_int64(
&(ring_buffer->descriptor->tail_position),
tail,
tail + (int32_t)required_capacity + (int32_t)padding));
多 producer 通过 CAS 竞争 ring buffer 的 tail position,CAS 失败则自旋重试。这是 Aeron 内部 IPC 通信的基础。
1.7 Java/C:Memory Barrier 精确控制
x86-64 上利用 TSO(Total Store Order),Acquire/Release 只需编译器屏障:
aeron_atomic64_gcc_x86_64.h:23,30
#define AERON_GET_ACQUIRE(dst, src) \
dst = (src); \
__asm__ __volatile__("" ::: "memory") // 仅编译器屏障,无硬件指令
#define AERON_SET_RELEASE(dst, src) \
__asm__ __volatile__("" ::: "memory"); \
(dst) = (src)
ARM 上需要真实硬件 fence:
#define AERON_GET_ACQUIRE(dst, src) \
dst = (src); \
atomic_thread_fence(memory_order_acquire)
#define AERON_SET_RELEASE(dst, src) \
atomic_thread_fence(memory_order_release); \
(dst) = (src)
Java 侧精确 fence:写入协议头时使用 storeStoreFence 确保帧长度先于其他字段可见。
termBuffer.putLongRelease(offset + FRAME_LENGTH_FIELD_OFFSET, ...);
VarHandle.storeStoreFence(); // 确保帧长度先于剩余头字段可见
// ... 写入其余头字段 ...
PublicationImage.java:429,786 使用 setRelease + storeStoreFence 确保状态变更的写顺序,loadLoadFence + getAcquire 确保读顺序——精确控制 happens-before 关系,而非依赖重量级锁。
2. 堆外内存与零拷贝
Aeron 避免 JVM 堆分配数据,所有消息数据和元数据存于堆外内存,通过 UnsafeBuffer 访问。
2.1 Java:mmap + UnsafeBuffer 组合
Log Buffer 通过 FileChannel.map() 映射到虚拟内存,然后用 UnsafeBuffer 包裹实现零拷贝访问。
final MappedByteBuffer mappedBuffer = fileChannel.map(READ_WRITE, 0, logLength);
mappedBuffer.order(ByteOrder.LITTLE_ENDIAN);
logMetaDataBuffer = new UnsafeBuffer(
mappedBuffer, (int)(logLength - LOG_META_DATA_LENGTH), LOG_META_DATA_LENGTH);
同一文件在 Driver 端也 mmap,实现真正的共享内存——写入立即可见于另一进程。
final MappedByteBuffer mappedBuffer = logChannel.map(READ_WRITE, 0, logLength);
mappedBuffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < PARTITION_COUNT; i++) {
termBuffers[i] = new UnsafeBuffer(mappedBuffer, i * termLength, termLength);
}
2.2 Java:显式释放 mmap
JDK 未提供 MappedByteBuffer 的公开 unmap() 方法,Aeron 通过 Agrona 的 BufferUtil.free() 反射调用 sun.misc.Cleaner 显式释放。
for (int i = 0; i < mappedByteBuffers.length; i++) {
BufferUtil.free(mappedByteBuffers[i]); // 反射调用 Cleaner
}
2.3 Java:CNC 单文件多视图切片
一个 mmap 文件被切成多个 UnsafeBuffer 视图,零拷贝共享底层内存。
CncFileDescriptor.java:333-414
// 每个子区域是同一个 MappedByteBuffer 的不同 offset/length 视图
public static UnsafeBuffer createToDriverBuffer(ByteBuffer buffer, DirectBuffer meta) {
return new UnsafeBuffer(buffer, META_DATA_LENGTH, meta.getInt(TO_DRIVER_BUFFER_LENGTH_OFFSET));
}
public static UnsafeBuffer createToClientsBuffer(...) { ... }
public static UnsafeBuffer createCountersMetaDataBuffer(...) { ... }
2.4 Java:缓存行对齐的堆外分配
EVENT_RING_BUFFER = new ManyToOneRingBuffer(new UnsafeBuffer(
BufferUtil.allocateDirectAligned(
getSizeAsInt(BUFFER_LENGTH_PROP_NAME, BUFFER_LENGTH_DEFAULT) + TRAILER_LENGTH,
CACHE_LINE_LENGTH)));
allocateDirectAligned 确保缓冲区起始地址对齐到 64 字节缓存行,避免跨行访问的额外开销。
2.5 Java:应用消息堆外分配
final UnsafeBuffer buffer = new UnsafeBuffer(
BufferUtil.allocateDirectAligned(256, 64));
应用层消息也直接分配在堆外,Publication.offer() 避免 JNI 拷贝。
2.6 C:mmap 日志文件
mapping->addr = mmap(NULL, mapping->length, PROT_READ | PROT_WRITE, flags, fd, 0);
C 实现直接使用 POSIX mmap,映射后通过指针算术访问:
mapped_raw_log->term_buffers[i].addr = (uint8_t *)mapped_raw_log->mapped_file.addr + (i * term_length);
2.7 C:对齐内存分配
int aeron_alloc_aligned(void **ptr, size_t *offset, size_t size, size_t alignment) {
#ifdef HAVE_POSIX_MEMALIGN
int rc = posix_memalign(ptr, alignment, size);
#else
// fallback: overallocate and adjust pointer
intptr_t addr = (intptr_t)*ptr;
*offset = alignment - (addr & (alignment - 1));
#endif
}
C 驱动和客户端均通过 aeron_alloc_aligned 确保关键缓冲区对齐到缓存行。
3. VarHandle 进阶模式
背景见 §1。本节深入 VarHandle 在 Aeron 中的几种典型用法。
3.1 防重入模式(getAndSet)
CommonContext.java:553-567,650
private static final VarHandle IS_CONCLUDED_VH;
static {
IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(
CommonContext.class, "isConcluded", boolean.class);
}
public CommonContext conclude() {
if ((boolean) IS_CONCLUDED_VH.getAndSet(this, true))
throw new ConcurrentConcludeException();
// ... 一次性初始化逻辑 ...
}
getAndSet 原子地设置 true 并返回旧值,保证只有一个调用进入初始化代码。
3.2 状态变更序列号模式(setRelease + fence + getAcquire)
声明 4 个 VarHandle 保护两组(SM 变更 + Loss 变更)的 begin/end 版本号:
private static final VarHandle BEGIN_SM_CHANGE_VH;
private static final VarHandle END_SM_CHANGE_VH;
private static final VarHandle BEGIN_LOSS_CHANGE_VH;
private static final VarHandle END_LOSS_CHANGE_VH;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
BEGIN_SM_CHANGE_VH = lookup.findVarHandle(PublicationImage.class, "beginSmChange", long.class);
// ... 其余同理
}
写入端(生产者线程):
BEGIN_LOSS_CHANGE_VH.setRelease(this, changeNumber); // StoreRelease
VarHandle.storeStoreFence(); // 确保后续写入有序
lossTermId = termId;
lossTermOffset = termOffset;
lossLength = length;
END_LOSS_CHANGE_VH.setRelease(this, changeNumber); // StoreRelease
读取端(消费者线程):
VarHandle.loadLoadFence(); // 刷新读到最新值
if (changeNumber == (long) BEGIN_SM_CHANGE_VH.getAcquire(this)) { ... }
这是 Seqlock(序列锁) 的轻量变体——用版本号 + 内存屏障实现无锁读写同步。
3.3 VarHandle 使用统计
| VarHandle 操作 | 使用次数 | 场景 |
|---|---|---|
compareAndSet | 3 | close() 防重入 |
getAndSet | 8+ | conclude() 一次性初始化 |
setRelease | 3 | 状态变更发布 |
getAcquire | 4 | 状态变更消费 |
storeStoreFence | 4 | 写顺序保证 |
loadLoadFence | 2 | 读顺序保证 |
4. 缓存行对齐:消除伪共享
伪共享(False Sharing)是多核性能的隐形杀手——两个独立变量落在同一缓存行时,一个核的写入会导致另一个核的缓存行失效。Aeron 通过 padding 确保热点字段独占缓存行。
4.1 Java:LHS/RHS Padding 继承层次
class SenderLhsPadding { // 64 bytes padding
byte p000, p001, ..., p063;
}
class SenderHotFields extends SenderLhsPadding { // 热字段
long controlPollDeadlineNs;
long reResolutionDeadlineNs;
int dutyCycleCounter;
int roundRobinIndex;
}
class SenderRhsPadding extends SenderHotFields { // 64 bytes padding
byte p064, p065, ..., p127;
}
public final class Sender extends SenderRhsPadding implements Agent { ... }
SenderHotFields 中的 4 个热字段独占一条缓存行,前后各 64 字节 padding 确保不与任何其他对象字段共享缓存行。
Aeron 中使用了此模式的 15 个类:
| 类 | 模块 | 保护内容 |
|---|---|---|
| Sender.java | driver | 发送热字段 |
| NetworkPublication.java | driver | conductor/sender 字段隔离 |
| PublicationImage.java | driver | volatile 变更字段 |
| ReceiveChannelEndpoint.java | driver | 接收热点字段 |
| SendChannelEndpoint.java | driver | 多目标发送字段 |
| DutyCycleTracker.java | driver | 时间追踪 |
| AbstractMinMulticastFlowControl.java | driver | 流控接收者列表 |
| ImageConnection.java | driver | 连接热点字段 |
| Subscription.java | client | 订阅图像数组 |
| ExclusivePublication.java | client | term 写入缓冲 |
| ClusteredServiceAgent.java | cluster | 活跃回调索引 |
4.2 C:显式 Padding 数组
21 个 C 结构体使用相同的显式 padding 模式。
aeron_network_publication.h:59-81
typedef struct aeron_network_publication_stct {
// conductor_fields_pad: 4 * AERON_CACHE_LINE_LENGTH - sizeof(conductor_fields_stct)
uint8_t conductor_fields_pad[...];
// 热字段(sender 线程写)
uint8_t sender_fields_pad_lhs[AERON_CACHE_LINE_LENGTH]; // 64 bytes
bool has_initial_connection;
bool track_sender_limits;
int64_t time_of_last_data_or_heartbeat_ns;
// ...
uint8_t sender_fields_pad_rhs[AERON_CACHE_LINE_LENGTH]; // 64 bytes
} aeron_network_publication_t;
Ring Buffer 描述符——每个计数器独占 2 条缓存行:
typedef struct aeron_rb_descriptor_stct {
uint8_t tail_pad_before[(2 * AERON_CACHE_LINE_LENGTH)];
volatile int64_t tail_position;
uint8_t tail_pad_after[(2 * AERON_CACHE_LINE_LENGTH) - sizeof(int64_t)];
uint8_t head_cache_pad_before[(2 * AERON_CACHE_LINE_LENGTH)];
volatile int64_t head_cache_position;
// ... 同样模式:head_position, correlation_counter, consumer_heartbeat
} aeron_rb_descriptor_t;
每个 volatile int64_t 计数器前后各有 128 字节 padding,确保任何两个计数器绝对不在同一缓存行。
4.3 C:队列结构 Padding
aeron_mpsc_concurrent_array_queue.h:25-43
typedef struct aeron_mpsc_concurrent_array_queue_stct {
uint8_t padding1[AERON_CACHE_LINE_LENGTH]; // 隔离 producer tail
struct { ... } producer;
uint8_t padding2[AERON_CACHE_LINE_LENGTH]; // 隔离 consumer head
struct { ... } consumer;
uint8_t padding3[AERON_CACHE_LINE_LENGTH]; // 隔离尾部字段
} aeron_mpsc_concurrent_array_queue_t;
Producer 和 Consumer 字段被 64 字节 padding 完全隔离,确保 MPSC 模式下的 producer/consumer 各自写入时不会互相 invalidate 缓存行。
4.4 AERON_DECL_ALIGNED:编译器强制对齐
aeron_atomic64_gcc_x86_64.h:125
#define AERON_DECL_ALIGNED(declaration, amt) declaration __attribute__((aligned(amt)))
确保关键变量在内存中对齐到指定边界(如 64 字节),配合 padding 使用。
5. sendmmsg / recvmmsg:批量系统调用
Aeron 的核心 I/O 优化是使用 Linux 的 sendmmsg 和 recvmmsg,单次系统调用处理多条 UDP 消息。
5.1 recvmmsg:一次收多条消息
aeron_udp_channel_transport.c:430-519
// 编译时自动选择:有 recvmmsg 用批量,否则逐条接收
#ifdef HAVE_RECVMMSG
int result = recvmmsg(transport->fd, msgvec, (unsigned int)vlen, 0, NULL);
if (result >= 0) {
for (int i = 0; i < result; i++) {
recv_func(..., &msgvec[i], ...); // 分发每条消息
}
}
#else
// fallback: 逐条调用 recvmsg
for (int i = 0; i < vlen; i++) {
aeron_udp_channel_transport_recvmsg(transport, &msgvec[i], recv_func, ...);
}
#endif
5.2 sendmmsg:一次发多条消息
aeron_udp_channel_transport.c:574-629
for (size_t i = 0; i < vlen; i++) {
msgvec[i].msg_hdr.msg_iov = &iov[i];
msgvec[i].msg_hdr.msg_iovlen = 1;
}
int result = sendmmsg(transport->fd, msgvec, (unsigned int)vlen, 0);
发送端循环构建 struct mmsghdr 数组,随后一次 sendmmsg() 批量发出。
5.3 预分配 Rece/Send Buffer
避免每次 I/O 分配内存,收发 buffer 在初始化时预分配。
struct aeron_driver_receiver_buffers_stct {
size_t vector_capacity;
uint8_t *buffers[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX]; // 预分配
struct iovec iov[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];
struct sockaddr_storage addrs[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];
};
初始化时分配并缓存行对齐 aeron_driver_receiver.c:55:
aeron_alloc_aligned((void **)&sender->recv_buffers.buffers[i], &offset,
context->mtu_length, AERON_CACHE_LINE_LENGTH);
5.4 epoll 自适应调度
aeron_udp_transport_poller.c:179-251
if (poller->transports.length < AERON_UDP_TRANSPORT_POLLER_ITERATION_THRESHOLD) {
// < 5 个 transport:直接遍历,避免 epoll 开销
for (size_t i = 0; i < poller->transports.length; i++) {
recvmmsg_func(transport, ...);
}
} else {
// >= 5 个 transport:使用 epoll_wait 只处理有数据的 socket
int poll_result = epoll_wait(poller->epoll_fd, ...);
for (int i = 0; i < poll_result; i++) {
recvmmsg_func(transport, ...); // 仅 EPOLIN 事件
}
}
少量 transport 时直接遍历(省去 epoll 系统调用开销),大量时用 epoll 避免空转。
5.5 编译时平台兼容
aeron_udp_channel_transport.c:49-55
#if !defined(HAVE_STRUCT_MMSGHDR)
struct mmsghdr {
struct msghdr msg_hdr;
unsigned int msg_len;
};
#endif
当目标平台缺少 struct mmsghdr 定义时(如旧版 macOS),Aeron 自行提供兼容定义。sendmmsg/recvmmsg 路径通过 #ifdef HAVE_SENDMMSG / #ifdef HAVE_RECVMMSG 条件编译。
6. Flyweight:零反序列化协议编码
Aeron 的网络协议不使用序列化/反序列化,而是通过 Flyweight 模式 直接在堆外内存上按偏移量读写字段。
6.1 协议类直接继承 UnsafeBuffer
public class HeaderFlyweight extends UnsafeBuffer {
static final int FRAME_LENGTH_FIELD_OFFSET = 0;
static final int VERSION_FIELD_OFFSET = 4;
static final int FLAGS_FIELD_OFFSET = 5;
static final int TYPE_FIELD_OFFSET = 6;
public int frameLength() {
return getInt(FRAME_LENGTH_FIELD_OFFSET, LITTLE_ENDIAN);
}
public HeaderFlyweight frameLength(final int length) {
putInt(FRAME_LENGTH_FIELD_OFFSET, length, LITTLE_ENDIAN);
return this;
}
}
所有协议类(9 种帧类型)均继承自 HeaderFlyweight extends UnsafeBuffer。读写字段就是直接在底层 UnsafeBuffer 上调用 getInt(offset) / putInt(offset, val)——没有中间对象,没有序列化步骤。
6.2 DataHeader:32 字节帧头
DataHeaderFlyweight.java:83-108
public static final int HEADER_LENGTH = 32;
public static final int TERM_OFFSET_FIELD_OFFSET = 8;
public static final int SESSION_ID_FIELD_OFFSET = 12;
public static final int STREAM_ID_FIELD_OFFSET = 16;
public static final int TERM_ID_FIELD_OFFSET = 20;
public static final int RESERVED_VALUE_OFFSET = 24;
public static final int DATA_OFFSET = HEADER_LENGTH; // 32
public int termOffset() {
return getInt(TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);
}
Payload 直接从 offset + DATA_OFFSET(即 offset + 32)开始,无额外封装。
6.3 LogBuffer Metadata 偏移布局
LogBufferDescriptor.java:97-177
metadata 区域通过静态初始化计算所有偏移量——每个字段在编译期确定位置:
TERM_TAIL_COUNTERS_OFFSET = 0; // 3 个 int64
LOG_ACTIVE_TERM_COUNT_OFFSET = 0 + SIZE_OF_LONG * 3;
// 中间隔 PADDING_SIZE(64) 字节隔离冷热字段
LOG_END_OF_STREAM_POSITION_OFFSET = PADDING_SIZE * 2; // 偏移 128
LOG_CORRELATION_ID_OFFSET = PADDING_SIZE * 4; // 偏移 256
LOG_DEFAULT_FRAME_HEADER_OFFSET = PADDING_SIZE * 5; // 偏移 320
所有偏移量在 static {} 块中计算,运行时无任何动态偏移查找。
6.4 非对齐访问优化
StatusMessageFlyweight.java:324-388
StatusMessage 中 receiverId 不在 8 字节对齐位置,直接 getLong 会触发对齐异常。Aeron 用逐字节移位读取:
value = (((long)getByte(offset + 7)) << 56) |
(((long)getByte(offset + 6) & 0xFF) << 48) | ...;
6.5 预构建默认帧头
LogBufferDescriptor.java:837-871
默认 DataHeader 在初始化时构建一次,新帧直接 memcpy 拷贝 32 字节:
public static void applyDefaultHeader(
UnsafeBuffer metadataBuffer, UnsafeBuffer termBuffer, int termOffset) {
termBuffer.putBytes(termOffset, metadataBuffer,
LOG_DEFAULT_FRAME_HEADER_OFFSET, HEADER_LENGTH); // 32 字节 bulk copy
}
无需逐字段写入,一条 putBytes 指令完成帧头初始化。
7. 其他优化技巧
7.1 位运算替代乘除
LogBufferDescriptor.java:783-799
// position = termId * termLength + offset — 用移位替代乘法
return (termCount << positionBitsToShift) + termOffset;
// termBeginPosition = termId * termLength
return termCount << positionBitsToShift;
Term 长度必须是 2 的幂(64KB → 1GB),positionBitsToShift 在初始化时计算(如 64KB → shift=16),后续全部用移位运算。
7.2 分支预测提示
#if defined(__GNUC__)
#define AERON_C_COND_EXPECT(exp, c) (__builtin_expect((exp), c))
#else
#define AERON_C_COND_EXPECT(exp, c) (exp)
#endif
在 UDP 收发路径上使用(常见路径是 loss_generator 为空):
aeron_send_channel_endpoint.c:373
if (AERON_C_COND_EXPECT(NULL != endpoint->data_loss_generator, false)) {
// 罕见路径:数据丢失模拟
}
CPU 分支预测器默认预测条件为假,无需额外指令。
7.3 32 字节帧对齐
public static final int FRAME_ALIGNMENT = 32;
每条消息帧对齐到 32 字节,匹配 CPU 缓存行(64 字节)的一半,避免帧跨行。
7.4 无分支对齐宏
#define AERON_ALIGN(value, alignment) (((value) + ((alignment) - 1u)) & ~((alignment) - 1u))
纯位运算实现向上对齐,无分支无除法。
7.5 O(1) 数组快速删除
aeron_udp_transport_poller.c:141-145
aeron_array_fast_unordered_remove(
(uint8_t *)poller->transports.array,
sizeof(aeron_udp_channel_transport_entry_t),
(size_t)index, (size_t)last_index);
删除元素时与最后一个元素交换并缩减长度——O(1) 而非 O(n) 移位。Aeron 中 transport 数组、publication 列表、subscription 列表等高频增删场景均使用此技巧。
7.6 热路径无分配
Aeron 的发送和接收热路径(每条消息都经过的代码)严格禁止堆分配。所有需要的对象和缓冲区均在初始化阶段预分配,热路径上只做指针/引用重绑定。
7.6.1 Java 命令 Flyweight 预创建
DriverProxy 在构造时预创建 12 个命令 flyweight,后续 addPublication() 等方法的每次调用直接使用已创建的对象。
private final PublicationMessageFlyweight publicationMessageFlyweight = new PublicationMessageFlyweight();
private final SubscriptionMessageFlyweight subscriptionMessageFlyweight = new SubscriptionMessageFlyweight();
private final RemovePublicationFlyweight removePublicationFlyweight = new RemovePublicationFlyweight();
private final RemoveSubscriptionFlyweight removeSubscriptionFlyweight = new RemoveSubscriptionFlyweight();
private final CounterMessageFlyweight counterMessageFlyweight = new CounterMessageFlyweight();
// ... 共 12 个 flyweight,每个命令类型一个
// 热路径:wrap() 重绑定,零分配
public boolean addPublication(String channel, int streamId) {
publicationMessageFlyweight.wrap(buffer, offset); // 重用
publicationMessageFlyweight.channel(channel).streamId(streamId);
}
DriverEventsAdapter 对应接收侧,同样预创建 11 个事件 flyweight:
DriverEventsAdapter.java:32-42
private final ErrorResponseFlyweight errorResponse = new ErrorResponseFlyweight();
private final PublicationBuffersReadyFlyweight publicationReady = new PublicationBuffersReadyFlyweight();
private final SubscriptionReadyFlyweight subscriptionReady = new SubscriptionReadyFlyweight();
private final ImageBuffersReadyFlyweight imageReady = new ImageBuffersReadyFlyweight();
// ... 共 11 个
事件分发时只调 flyweight.wrap(buffer, index) 重绑定,不分配新对象。
7.6.2 Java Driver ThreadLocal 预分配
NetworkPublication(发送端)所有辅助帧的 buffer 和 flyweight 在 ThreadLocal 中预分配。
NetworkPublicationThreadLocals.java:37-70
final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(CACHE_LINE_LENGTH * 4, CACHE_LINE_LENGTH);
// 从同一块大 buffer 切出三个缓存行对齐的子区域
heartbeatBuffer = byteBuffer.slice(); // offset 0
setupBuffer = byteBuffer.position(CACHE_LINE_LENGTH).slice(); // offset 64
rttMeasurementBuffer = byteBuffer.position(CACHE_LINE_LENGTH * 2).slice(); // offset 128
// 每个子区域一个 flyweight,构造一次
heartbeatDataHeader = new DataHeaderFlyweight(heartbeatBuffer);
setupHeader = new SetupFlyweight(setupBuffer);
rttMeasurementHeader = new RttMeasurementFlyweight(rttMeasurementBuffer);
每条心跳/Setup/RTT 消息都重用同一个 buffer + flyweight,send 路径零分配。
ReceiveChannelEndpoint(接收端)同样预分配 Status/NAK/RTT/Error 帧 buffer:
ReceiveChannelEndpointThreadLocals.java:55-119
final int bufferLength =
BitUtil.align(StatusMessageFlyweight.HEADER_LENGTH + SIZE_OF_LONG, CACHE_LINE_LENGTH) +
BitUtil.align(NakFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) +
BitUtil.align(RttMeasurementFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) +
BitUtil.align(ResponseSetupFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) +
BitUtil.align(ErrorFlyweight.MAX_ERROR_FRAME_LENGTH, CACHE_LINE_LENGTH);
final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(bufferLength, CACHE_LINE_LENGTH);
// 切片出 SM/NAK/RTTM/ResponseSetup/Error 五个子区域
接收端每次发送 Status Message / NAK 都复用这些预分配 buffer。
7.6.3 BufferClaim:零拷贝 + 可重用
/**
* Try to claim a range in the publication log buffer.
* ...
* @param bufferClaim Can be stored and reused to avoid allocation.
*/
public abstract long tryClaim(int length, BufferClaim bufferClaim);
BufferClaim 内部只有一个预先构造的空 UnsafeBuffer,tryClaim 内调用 wrap() 将其重绑定到 term buffer 中的目标位置:
public class BufferClaim {
private final UnsafeBuffer buffer = new UnsafeBuffer(0, 0); // 空壳,构造一次
public void wrap(final UnsafeBuffer srcBuffer, final int srcOffset, final int srcLength) {
buffer.wrap(srcBuffer, srcOffset, srcLength); // 重绑定,无分配
}
}
用户创建一次 BufferClaim,后续每条消息都通过它直接写入 term buffer。
7.6.4 Singleton 模式避免分配
配置对象、错误处理器、认证器等通过静态 INSTANCE 单例避免每次引用时分配:
RethrowingErrorHandler.java:33-36
/** Singleton instance to avoid allocation. */
public static final RethrowingErrorHandler INSTANCE = new RethrowingErrorHandler();
DefaultNameResolver.java:33-36
/** Singleton instance which can be used to avoid allocation. */
public static final DefaultNameResolver INSTANCE = new DefaultNameResolver();
7.6.5 BufferBuilder:可复用的消息组装缓冲区
BufferBuilder 是 FragmentAssembler 用于重组分片消息的核心工具。构造一次,reset() 清零后复用。
BufferBuilder.java:44-52,176-184
static final int INIT_MIN_CAPACITY = 4096; // 最小 4KB,避免频繁扩容
private final UnsafeBuffer buffer = new UnsafeBuffer(); // 可复用 wrapper
public BufferBuilder reset() {
limit = 0;
nextTermOffset = NULL_VALUE;
completeHeader.context(null).fragmentedFrameLength(NULL_VALUE);
return this; // 不重新分配 buffer
}
7.6.6 DirectBufferVector:可变 flyweight + 流式 reset()
DirectBufferVector.java:34,60-67
public DirectBufferVector() {} // 无参构造,无分配
public DirectBufferVector reset(DirectBuffer buffer, int offset, int length) {
this.buffer = buffer;
this.offset = offset;
this.length = length;
return this; // 同实例复用,不分配
}
用于 scatter/gather I/O,多个 vector 共享同一个 DirectBufferVector[] 数组和其中元素。
7.6.7 C Driver:收发 Buffer 预分配
C 驱动在 init() 中一次性分配所有收发 buffer,每条消息零分配。
Sender 侧 — aeron_driver_sender.c:47-65
sender->recv_buffers.vector_capacity = context->sender_io_vector_capacity;
for (size_t i = 0; i < sender->recv_buffers.vector_capacity; i++) {
size_t offset;
aeron_alloc_aligned((void **)&sender->recv_buffers.buffers[i], &offset,
context->mtu_length, AERON_CACHE_LINE_LENGTH);
sender->recv_buffers.iov[i].iov_base = sender->recv_buffers.buffers[i] + offset;
sender->recv_buffers.iov[i].iov_len = (uint32_t)context->mtu_length;
}
Receiver 侧 — aeron_driver_receiver.c:47-63 — 同样模式。
两者都预分配固定大小的 buffers[]、iov[]、addrs[] 数组。热路径上的 recvmmsg/sendmmsg 直接使用这些预分配 buffer。
7.6.8 C Stack Allocation:栈上分配避免堆
收发循环中使用的临时结构直接栈上分配。
aeron_network_publication.c:571
// 栈上声明,零开销
struct iovec iov[AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND];
struct mmsghdr mmsghdr[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];
固定大小的 iovec/mmsghdr 数组直接声明在栈上,不会触发堆分配。
7.6.9 CongestionControl:打包返回值避免装箱
/**
* Pack values into a long, so they can be returned on the stack without allocation.
*/
static long packOutcome(final int receiverWindowLength, final boolean forceStatusMessage) {
final int flags = forceStatusMessage ? FORCE_STATUS_MESSAGE_BIT : 0x0;
return ((long)flags << 32) | receiverWindowLength;
}
两个返回值(int + boolean)被打包进一个 long,通过寄存器返回,避免分配 Pair/Tuple 对象。
7.6.10 热路径无分配总结
| 模式 | 语言 | 关键文件 | 机制 |
|---|---|---|---|
| Flyweight 预创建 | Java | DriverProxy.java:35-47 | 12 个 flyweight final field,wrap() 重绑定 |
| 事件 Flyweight 预创建 | Java | DriverEventsAdapter.java:32-42 | 11 个 flyweight final field,wrap() 重绑定 |
| ThreadLocal 预分配 | Java | NetworkPublicationThreadLocals.java:37 | 一个大 allocation,切片复用 |
| BufferClaim 复用 | Java | BufferClaim.java:40 | 空 UnsafeBuffer + wrap() |
| Singleton | Java | RethrowingErrorHandler.java:33 | static final INSTANCE |
| BufferBuilder reset | Java | BufferBuilder.java:176 | reset() 清零不重新分配 |
| DirectBufferVector reset | Java | DirectBufferVector.java:60 | reset() 重绑定 |
| 返回值打包 | Java | CongestionControl.java:37 | long 打包 int+boolean |
| C 预分配收发 buffer | C | aeron_driver_sender.c:47 | init() 时 aeron_alloc_aligned,永不释放 |
| C 栈上数组 | C | aeron_network_publication.c:571 | 固定大小 iovec/mmsghdr 数组栈声明 |
7.7 函数指针多态
aeron_udp_channel_transport_bindings.h:113-127
typedef struct aeron_udp_channel_transport_bindings_stct {
aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func;
aeron_udp_channel_transport_send_func_t send_func;
// ...
};
收发函数通过函数指针调用,编译期无法确定目标时用间接调用替代虚函数。生产代码指向原生 sendmmsg/recvmmsg,测试代码可替换为 loss generator 包装器。
7.8 C11 Atomic 与 MSVC Interlocked 双路径
aeron_atomic64_c11.h(ARM/Linux)和 aeron_atomic64_msvc.h(Windows)提供平台最优的原子操作实现。C 代码通过统一宏 AERON_GET_ACQUIRE、AERON_SET_RELEASE、aeron_cas_int64 调用,底层在编译时选择最优指令序列。