当前位置:首页 > 68机器人 > 正文内容

68机器人 C++17 alignas 缓存行对齐实战

admin4周前 (09-23)68机器人57

---


一、基本用法


#include <iostream>


// 对齐到 64 字节(典型的缓存行大小)

struct alignas(64) CacheLineAligned {

 int value;

};


int main() {

 std::cout << sizeof(CacheLineAligned) << std::endl; // 64

 std::cout << alignof(CacheLineAligned) << std::endl; // 64

}


alignas(64) 做两件事:

对象的起始地址是 64 的倍数

对象的 sizeof 会被补齐到 64 的倍数(所以上面输出 64,不是 4)


---

二、消除伪共享:两种写法

写法 A:每个成员单独对齐


struct alignas(64) Counter {

 alignas(64) std::atomic<int> counter_a{0}; // 线程 1

 alignas(64) std::atomic<int> counter_b{0}; // 线程 2

};


countera 的地址是 64 的倍数,counterb 的地址是 counter_a + 64 的倍数,两者一定不在同一条缓存行里。

写法 B:结构体内嵌 padding


struct Counter {

 std::atomic<int> counter_a{0};

 char pad_a[60]; // 4 + 60 = 64,凑满一条缓存行

 std::atomic<int> counter_b{0};

 char pad_b[60];

};


写法 A 更好——少一个 magic number,编译器帮你算 padding。


---

三、std::hardwaredestructiveinterference_size


C++17 标准库提供了一个常量,告诉你当前平台实际的缓存行大小:


#include <new>


struct alignas(std::hardwaredestructiveinterference_size) Counter {

 std::atomic<int> counter_a{0};

 std::atomic<int> counter_b{0};

};

std::hardwaredestructiveinterferencesize 在大多数 x8664 平台上就是 64,但用这个常量比硬编码 64 更可移植。ARM 上可能是 32 或 64。


---

四、数组场景(最常用)


多线程直方图之类的高争抢场景:


// ❌ 错误:counter[0] 和 counter[1] 在同一条缓存行

std::atomic<int> counters[8];


// ✅ 正确:每个元素独占缓存行

struct alignas(64) PaddedAtomic {

 std::atomic<int> value{0};

};

PaddedAtomic counters[8];

// counters[0].value 和 counters[1].value 相隔 64 字节


验证:


static_assert(sizeof(PaddedAtomic) == 64, "Must be exactly one cache line");

auto addrdiff = reinterpretcast<char*>(&counters[1])

reinterpret_cast<char*>(&counters[0]);

std::cout << "gap between elements: " << addr_diff << std::endl; // 64


---

五、放在堆上


alignas 对栈上和全局变量直接生效,但 new 出来的对象需要特殊处理:


// ❌ 可能不对齐

auto* p = new PaddedAtomic();


// ✅ C++17 用 std::aligned_alloc 或 over-aligned new

auto* p = new PaddedAtomic(); // C++17 起,over-aligned 类型自动走 aligned new

// 或显式分配

auto* buf = std::aligned_alloc(64, sizeof(PaddedAtomic));

auto* p = new (buf) PaddedAtomic();


C++17 起 new PaddedAtomic() 会自动处理——前提是结构体用了 alignas。


---

六、完整示例


#include <atomic>

#include <thread>

#include <iostream>

#include <new>

#include <vector>


// 缓存行对齐的计数器

struct alignas(std::hardwaredestructiveinterference_size) PaddedCounter {

 std::atomic<long long> value{0};

 // 编译器自动补齐到 64 字节

};


void testfalsesharing() {

 PaddedCounter counters[4];

 std::vector<std::thread> threads;


 // 四个线程各写自己的槽位

 for (int t = 0; t < 4; ++t) {

 threads.emplace_back(&, t {

 for (int i = 0; i < 100'000'000; ++i) {

 counters[t].value.fetchadd(1, std::memoryorder_relaxed);

 }

 });

 }

 for (auto& th : threads) th.join();


 long long total = 0;

 for (auto& c : counters) total += c.value.load();

 std::cout << "total: " << total << std::endl;

}


对比测试:去掉 alignas,同样的代码跑 4 线程,你很可能看到 2~3 倍的速度差。


---

七、避坑清单


| 坑 | 说明 |

|:--|:------|

| alignas 不能加在 using 别名上 | 必须加在 struct/class 定义处 |

| alignas 值必须是 2 的幂 | alignas(48) 编译报错 |

| 不要乱对齐 | 只在 profile 确认伪共享是瓶颈时才加,否则浪费内存 |

| malloc 不保证 64 字节对齐 | 堆上对象用 std::aligned_alloc 或 new |

| alignas 不保证成员本身对齐 | 要在成员上也加 alignas,或者用结构体包裹 |

| ARM 和 x86 的缓存行大小可能不同 | 用 std::hardwaredestructiveinterference_size 而不是硬编码 64 |


---

八、一句话总结

alignas(64) 加在 struct 上让对象对齐到缓存行边界、sizeof 补齐到 64 字节;多个变量各自对齐后就不会落在同一条缓存行里,伪共享自然消失。 数组场景最常用,堆上注意对齐分配器,检查 static_assert(sizeof(T) == 64) 确认 padding 有效。


相关文章

68机器人 最打动人的是那种"能力的可迁移感"

这篇博客园的随笔读完了。作者「阿伦啊」写得很素、很真——没有贩卖焦虑也没有灌鸡汤,就是一个人坐在电脑前翻到自己多年前的技术博客,然后安静地记录了一下自己这些年走过的路。[citation:11.4]它...

68机器人 别再 rm 日志了:Linux 下安全清空日志文件的正确姿势

别再 rm 日志了:Linux 下安全清空日志文件的正确姿势在 Linux 运维中,直接执行 rm -f app.log 删除正在被进程写入的日志文件是一个典型的“高危操作”。虽然文件名从目录中消失了...

68机器人 从缓存层升级为AI原生记忆层

Redis 正式完成AI生态深度接入,凭借其毫秒级响应的原生速度优势,直接解决了AI Agent长期以来“记忆召回慢、上下文断连、状态丢失”的核心痛点,成为AI应用里最适配的实时记忆层。⚡ 从缓存层升...

68机器人 垂直场景需求翻译与价值锚定能力

结合你日常指挥多Agent、带学员做技术落地的实战场景,AI时代最具竞争力的技能全部围绕“驾驭AI、锚定真实业务价值、守住高风险场景底线”展开,完全避开AI能轻松替代的重复性执行环节。🎯 垂直场景需求...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。