L1,L2和L3缓存之间的性能差异:
Cache Line 是 Cache 和主存储器之间的数据传输单位。
通常,Cache Line 为 64 字节。当读取或写入64字节区域中的任何位置时,处理器将读取或写入整个缓存行。处理器还尝试通过分析线程的内存访问模式来预取高速缓存行。
如果对象中包含标志位来判断该数据段是否有效:
如果一个缓存行大小内的数据被多个CPU Core读取,为了一致性,需要额外的手段保证 Cache 上的数据被刷新(volatile),但通常这是一个非常昂贵的操作,会显著的降低内存带宽。
使用缓冲行填充,可以避免一个缓冲行大小内的数据被多个CPU Core读取,能够避免上述问题。
#include <iostream>
#include <chrono>
#include <thread>
#include <vector>
#include <atomic>
enum
{
ITERATIONS = 500 * 1000 * 1000
};
struct Long
{
#ifdef PADDING
int64_t p1, p2, p3, p4, p5, p6, p7;
std::atomic<int64_t> value;
//volatile int64_t value;
int64_t p8, p9, p10, p11, p12, p13, p14;
#else
std::atomic<int64_t> value;
//volatile int64_t value;
#endif
};
Long* longs;
void run(size_t index)
{
for (int i = 0; i < ITERATIONS; ++i)
{
longs[index].value++;
}
}
int main(int argc, char** args)
{
size_t threadNum = args[1][0] - '0';
longs = new Long[threadNum];
std::vector<std::thread*> trs;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < threadNum; ++i)
{
std::thread* tr = new std::thread(run, i);
trs.push_back(tr);
}
for (int i = 0; i < threadNum; ++i)
{
trs[i]->join();
delete trs[i];
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "thread number: " << threadNum << " duration: " << duration << " us" << std::endl;
}
伪共享造成的性能损失极大
因为 volatile 只是进行了内存写入,避免 Cache 在其他线程中不可见,并没有复杂的同步保证,所以测试结果差距不大