有没有什么方法可以确保java刷新在CyclicBarrier或CountDownLatch允许我们继续(就像synchronized关键字一样)之前已经完成的写入的缓存,而不使用synchronized关键字?
x86_64 上是否有针对 32 位和 64 位值的非临时加载(即直接从 DRAM 加载,无需缓存)的 C/C++ 内在函数?
我的编译器是MSVC++2017工具集v141。但其他编译器的内在函数以及对底层汇编指令的引用也是受欢迎的。
我有一个问题需要了解是否有更好的解决方案。我编写了以下代码,将一些变量从编写器线程传递到读取器线程。这些线程固定到共享相同 L2 缓存的不同 CPU(禁用超线程)。
writer_thread.h
struct a_few_vars {
uint32_t x1;
uint32_t x2;
uint64_t x3;
uint64_t x4;
} __attribute__((aligned(64)));
volatile uint32_t head;
struct a_few_vars xxx[UINT16_MAX] __attribute__((aligned(64)));
Run Code Online (Sandbox Code Playgroud)
reader_thread.h
uint32_t tail;
struct a_few_vars *p_xxx;
Run Code Online (Sandbox Code Playgroud)
写入线程增加头变量,读取线程检查头变量和尾变量是否相等。如果它们不相等,则按如下方式读取新数据
while (true) {
if (tail != head) {
.. process xxx[head] ..
.. update tail ..
}
}
Run Code Online (Sandbox Code Playgroud)
性能是迄今为止最重要的问题。我使用的是 Intel Xeon 处理器,读取器线程每次都会从内存中获取 head 值和 xxx[head] 数据。我使用对齐数组来实现无锁
就我而言,是否有任何方法可以尽快将变量刷新到读取器CPU缓存中。我可以从写入器 CPU 触发读取器 CPU 的预取吗?如果存在的话,我可以使用 __asm__ 来使用特殊的英特尔指令。总之,在固定到不同 CPU 的线程之间传递结构中的变量的最快方法是什么?
提前致谢
为了测量程序中缓存未命中的影响,我想要计算缓存未命中对用于实际计算的周期造成的延迟。\n我用它来perf stat测量周期、L1 负载、L1 未命中、LLC 负载和 LLC - 我的程序中遗漏了。这是一个示例输出:
467\xe2\x80\xaf769,70 msec task-clock # 1,000 CPUs utilized \n 1\xe2\x80\xaf234\xe2\x80\xaf063\xe2\x80\xaf672\xe2\x80\xaf432 cycles # 2,638 GHz (62,50%)\n 572\xe2\x80\xaf761\xe2\x80\xaf379\xe2\x80\xaf098 instructions # 0,46 insn per cycle (75,00%)\n 129\xe2\x80\xaf143\xe2\x80\xaf035\xe2\x80\xaf219 branches # 276,083 M/sec (75,00%)\n 6\xe2\x80\xaf457\xe2\x80\xaf141\xe2\x80\xaf079 branch-misses # 5,00% of all branches (75,00%)\n 195\xe2\x80\xaf360\xe2\x80\xaf583\xe2\x80\xaf052 L1-dcache-loads # 417,643 M/sec (75,00%)\n 33\xe2\x80\xaf224\xe2\x80\xaf066\xe2\x80\xaf301 L1-dcache-load-misses # 17,01% of all L1-dcache hits (75,00%)\n 20\xe2\x80\xaf620\xe2\x80\xaf655\xe2\x80\xaf322 LLC-loads # 44,083 M/sec (50,00%)\n 6\xe2\x80\xaf030\xe2\x80\xaf530\xe2\x80\xaf728 LLC-load-misses # 29,25% of all LL-cache hits (50,00%)\nRun Code Online (Sandbox Code Playgroud)\n那么我的问题是:\n如何将缓存未命中数转换为“丢失”时钟周期数? …
5 级流水线 CPU 具有以下阶段顺序:
\nIF \xe2\x80\x93 从指令存储器中获取指令。
\nRD \xe2\x80\x93 指令译码和寄存器读取。
\nEX \xe2\x80\x93 执行:ALU 运算,用于数据和地址计算。
\nMA \xe2\x80\x93 数据存储器访问 \xe2\x80\x93 用于写访问,使用 RD 状态下的寄存器读取。
\nWB \xe2\x80\x93 寄存器写回。
\n现在我知道,例如,指令提取是从内存中获取的,这可能需要 4 个周期(L1 缓存)或最多约 150 个周期(RAM)。但是,在每个流水线图中,我都会看到类似的内容,其中每个阶段都分配一个周期。
\n现在,我当然知道真正的处理器具有超过 19 个阶段的复杂管道,并且每种架构都是不同的。但是,我在这里错过了什么吗?如果在 IF 和 MA 中进行内存访问,那么这个 5 级流水线是否需要几十个周期?
\n我开始使用_mm_clflush、_mm_clflushopt和 等函数_mm_clwb。
现在说我已经定义了一个结构体名称 mystruct ,它的大小是 256 字节。我的缓存行大小是 64 字节。现在我想刷新包含 mystruct 变量的缓存行。以下哪种方法是正确的方法?
_mm_clflush(&mystruct)
Run Code Online (Sandbox Code Playgroud)
或者
for (int i = 0; i < sizeof(mystruct)/64; i++) {
_mm_clflush( ((char *)&mystruct) + i*64)
}
Run Code Online (Sandbox Code Playgroud) 在sparc架构中,有一个ASI(地址空间指示符)被传递给加载、存储指令,因此如果ASI为0x20,则像IO访问一样绕过缓存。即使内存范围在页表中设置为可缓存,缓存也会被绕过。这有时非常方便,例如使用变量在核心之间进行同步等
。aarch64 架构中有类似的东西吗?我查看了指令内容,但在加载/存储指令列表中找不到任何内容。
我在Linux中发现,它显示我的CPU的缓存行大小是64字节,我意识到16/32/128字节是存在的,但大多数CPU现在都设计为64字节缓存行大小。为什么不更大或更小?
鉴于我目前正在学习的这本教科书中的这段代码片段。Randal E. Bryant, David R. O'Hallaron - 计算机系统。A Programmer's Perspective [3rd ed.] (2016, Pearson) (全球版,所以书中的练习可能是错误的。)
for (i = 31; i >= 0; i--) {
for (j = 31; j >= 0; j--) {
total_x += grid[i][j].x;
}
}
for (i = 31; i >= 0; i--) {
for (j = 31; j >= 0; j--) {
total_y += grid[i][j].y;
}
}
Run Code Online (Sandbox Code Playgroud)
这是给出的信息
最近热门游戏 SimAquarium 的核心是一个计算 512 藻类平均位置的紧密循环。您正在一台具有 2,048 字节直接映射数据缓存和 32 字节块 (B = 32) 的机器上评估其缓存性能。
Run Code Online (Sandbox Code Playgroud)struct algae_position { int …
我正在学习AMD处理器L1缓存的访问过程。但我反复看了AMD的手册,还是看不懂。
我对Intel的L1数据缓存的理解是:
L1缓存是虚拟索引和物理标记的。因此,利用虚拟地址的索引位来查找对应的缓存集合,最终根据标记确定缓存集合中的哪条缓存行。
(Intel 使他们的 L1d 缓存足够关联且足够小,索引位仅来自页内偏移,与物理地址相同。因此,他们获得了 VIPT 的速度,没有任何别名问题,表现得像 PIPT .)
但AMD使用了一种新方法。在 Zen 1 中,它们有一个 32 KB、8 路组关联 L1d 缓存,该缓存(与 64KB 4 路 L1i 不同)足够小,可以在没有微标签的情况下避免别名问题。
来自AMD 2017年软件优化手册,第2.6.2.2节“AMD系列17h处理器的微架构”(Zen 1):
L1 数据缓存标签包含一个基于线性地址的微标签 (utag),它使用最初用于访问缓存行的线性地址来标记每个缓存行。负载使用此 utag 来确定使用其线性地址读取缓存的哪条路,该线性地址在通过 TLB 确定负载的物理地址之前可用。utag 是负载线性地址的哈希值。这种基于线性地址的查找使得能够在读取高速缓存数据之前非常准确地预测高速缓存行的定位方式。这允许负载仅读取单个缓存方式,而不是全部 8 个。这可以节省电量并减少存储体冲突。
utag 有可能在两个方向上都是错误的:它可以在访问将丢失时预测命中,并且在访问可能命中时预测丢失。无论哪种情况,都会发起对 L2 缓存的填充请求,并且当 L2 响应填充请求时更新 utag。
当两个不同的线性地址映射到同一物理地址时,就会发生线性别名。这可能会导致加载和存储别名缓存行的性能下降。对在 L1 DC 中有效但在不同线性别名下的地址的加载将看到 L1 DC 未命中,这需要发出 L2 缓存请求。延迟通常不会大于 L2 缓存命中的延迟。然而,如果多个别名加载或存储同时进行,则它们每个都可能会遇到 L1 DC 缺失,因为它们使用特定的线性地址更新 utag 并删除另一个能够访问高速缓存行的线性地址。
如果两个不同的线性地址没有别名到同一物理地址,那么它们也可能在 utag 中发生冲突(如果它们具有相同的线性哈希)。在给定的 L1 DC 索引 (11:6) 下,任何时候都只能访问具有给定线性哈希的一个缓存行;任何具有匹配线性散列的缓存行在 utag 中都被标记为无效并且不可访问。
第二段这句话的具体场景是什么?什么情况下命中预测为未命中,未命中预测为命中?当CPU从内存访问数据到cache时,会根据utag计算出一条cache way。然后就把它放在这里?即使其他缓存路为空?
不同的线性地址如何映射到同一个物理地址?