性能优化技巧

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 直接返回,无阻塞。

Aeron.java:84-96,278

// 声明
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)) {
        // ... 关闭资源 ...
    }
}

同样模式用于:

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

aeron_mpsc_rb.c:97-100

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

aeron_atomic64_c11.h:29-46

#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 确保帧长度先于其他字段可见。

HeaderWriter.java:85

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 包裹实现零拷贝访问。

LogBuffers.java:84-103

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,实现真正的共享内存——写入立即可见于另一进程。

MappedRawLog.java:78-91

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 显式释放。

LogBuffers.java:293-301

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:缓存行对齐的堆外分配

EventConfiguration.java:64-68

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:应用消息堆外分配

BasicPublisher.java:73

final UnsafeBuffer buffer = new UnsafeBuffer(
    BufferUtil.allocateDirectAligned(256, 64));

应用层消息也直接分配在堆外,Publication.offer() 避免 JNI 拷贝。

2.6 C:mmap 日志文件

aeron_fileutil.c:767-791

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:对齐内存分配

aeron_alloc.c:55-85

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)

PublicationImage.java:129-146

声明 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 继承层次

Sender.java:32-60

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 条缓存行:

aeron_rb.h:28-38

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 的 sendmmsgrecvmmsg,单次系统调用处理多条 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 在初始化时预分配。

aeron_driver_receiver.h:51-58

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

HeaderFlyweight.java:40

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 分支预测提示

aeron_bitutil.h:43-47

#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 字节帧对齐

FrameDescriptor.java:66

public static final int FRAME_ALIGNMENT = 32;

每条消息帧对齐到 32 字节,匹配 CPU 缓存行(64 字节)的一半,避免帧跨行。

7.4 无分支对齐宏

aeron_bitutil.h:35

#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() 等方法的每次调用直接使用已创建的对象。

DriverProxy.java:35-47

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:零拷贝 + 可重用

Publication.java:531

/**
 * 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 内部只有一个预先构造的空 UnsafeBuffertryClaim 内调用 wrap() 将其重绑定到 term buffer 中的目标位置:

BufferClaim.java:40

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];

aeron_driver_receiver.c:125

struct mmsghdr mmsghdr[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];

固定大小的 iovec/mmsghdr 数组直接声明在栈上,不会触发堆分配。

7.6.9 CongestionControl:打包返回值避免装箱

CongestionControl.java:30-42

/**
 * 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_ACQUIREAERON_SET_RELEASEaeron_cas_int64 调用,底层在编译时选择最优指令序列。


8. 优化技巧速查表

技巧 Java 位置 C 位置 效果
CAS 防重入 Aeron.java:278 无锁 one-shot 语义
CAS 多 producer ConcurrentPublication.java:359 aeron_mpsc_rb.c:97 无锁并发写入
内联汇编 CAS aeron_atomic64_gcc_x86_64.h:69 单指令原子操作
x86 编译器屏障 aeron_atomic64_gcc_x86_64.h:23 零开销 Acquire/Release
mmap + UnsafeBuffer LogBuffers.java:84 aeron_fileutil.c:780 零拷贝共享内存
堆外分配 EventConfiguration.java:64 aeron_alloc_aligned 避免 GC + 缓存行对齐
VarHandle seqlock PublicationImage.java:428-429 无锁读写同步
LHS/RHS padding Sender.java:32-60 aeron_network_publication.h:59-81 消除伪共享
sendmmsg/recvmmsg aeron_udp_channel_transport.c:458 批量系统调用,减少上下文切换
epoll 自适应 aeron_udp_transport_poller.c:179 少量 socket 直接轮询
Flyweight on UnsafeBuffer HeaderFlyweight.java:40 aeronc.h:49-60 零反序列化
预构建帧头 LogBufferDescriptor.java:837 32 字节批量拷贝
位运算替代乘除 LogBufferDescriptor.java:783 1 周期 vs 3-30 周期
分支预测提示 aeron_send_channel_endpoint.c:373 减少分支预测失败
32 字节帧对齐 FrameDescriptor.java:66 避免跨缓存行
O(1) 数组删除 aeron_udp_transport_poller.c:141 避免元素搬移
ThreadLocal 预分配 NetworkPublicationThreadLocals.java:39 避免热路径分配
函数指针多态 aeron_udp_channel_transport_bindings.h:118 零开销可插拔 I/O

本站访问量

This site uses Just the Docs, a documentation theme for Jekyll.