我发现了一条来自 的评论crossbeam。
从 Intel 的 Sandy Bridge 开始,空间预取器现在一次提取成对的 64 字节缓存线,因此我们必须对齐到 128 字节而不是 64。
资料来源:
我在英特尔的手册中没有找到这样的说法。但直到最新的提交,folly仍然使用 128 字节填充,这让我很有说服力。所以我开始编写代码来看看是否可以观察到这种行为。这是我的代码。
#include <thread>
int counter[1024]{};
void update(int idx) {
for (int j = 0; j < 100000000; j++) ++counter[idx];
}
int main() {
std::thread t1(update, 0);
std::thread t2(update, 1);
std::thread t3(update, 2);
std::thread t4(update, 3);
t1.join();
t2.join();
t3.join();
t4.join();
}
Run Code Online (Sandbox Code Playgroud)
我的CPU是锐龙3700X。当索引为0、1、2、3时,大约需要 1.2 秒才能完成。当索引为0, 16, 32,时 …
我来到主题缓存和映射以及缓存未命中以及当所有块已满时缓存块如何以什么顺序替换.
有最近最少使用的算法或fifo算法或最不频繁的算法和随机替换,...
但是在实际的cpu缓存上使用了哪些算法?或者你可以使用所有...操作系统决定最佳算法是什么?
编辑:即使我选择了答案,欢迎任何进一步的信息;)
请考虑以下简单代码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <err.h>
int cpu_ms() {
return (int)(clock() * 1000 / CLOCKS_PER_SEC);
}
int main(int argc, char** argv) {
if (argc < 2) errx(EXIT_FAILURE, "provide the array size in KB on the command line");
size_t size = atol(argv[1]) * 1024;
unsigned char *p = malloc(size);
if (!p) errx(EXIT_FAILURE, "malloc of %zu bytes failed", size);
int fill = argv[2] ? argv[2][0] : 'x';
memset(p, fill, size);
int startms = cpu_ms();
printf("allocated %zu bytes …Run Code Online (Sandbox Code Playgroud) c++在某些英特尔至强处理器上运行以下代码时,我偶然发现了一个特殊的性能问题:
// array_a contains permutation of [0, n - 1]
// array_b and inverse are initialized arrays
for (int i = 0; i < n; ++i) {
array_b[i] = array_a[i];
inverse[array_b[i]] = i;
}
Run Code Online (Sandbox Code Playgroud)
循环的第一行按顺序复制array_a到array_b(预期很少有缓存未命中)。第二行计算array_b(许多缓存未命中,因为array_b是随机排列)的倒数。我们也可以将代码分成两个单独的循环:
for (int i = 0; i < n; ++i)
array_b[i] = array_a[i];
for (int i = 0; i < n; ++i)
inverse[array_b[i]] = i;
Run Code Online (Sandbox Code Playgroud)
我原以为这两个版本(单循环与双循环)在相对现代的硬件上的性能几乎相同。但是,在执行单循环版本时,某些 Xeon 处理器似乎非常慢。
您可以在下方看到以纳秒为单位n的挂机时间除以在一系列不同处理器上运行代码段的时间。出于测试目的,代码是使用 GCC 7.5.0 编译的,并-O3 -funroll-loops -march=native …
我正在写一个库,其中:
设计小对象是否有一些经验法则/指南,以便在这种环境中有效利用CPU缓存线?
我对正确调整和构造对象特别感兴趣,因此例如最常访问的字段适合第一个缓存行等.
注意:我完全清楚这是依赖于实现的,我需要进行基准测试,以及过早优化的一般风险.无需浪费任何进一步的带宽指出这一点.:-)
受到最近关于SO的问题和给出的答案的启发,这让我感到非常无知,我决定花一些时间来学习更多有关CPU缓存的知识,并编写了一个小程序来验证我是否正确地完成了这一切(大多数情况下)可能不是,我害怕).我将首先写下构成我期望的假设,所以如果错误的话,你可能会阻止我.基于我所读到的,一般来说:
n三通关联高速缓存被分成s组,每组包含n行,每行具有固定大小L;A可以被映射到任何所述的n的高速缓存行一个集;A映射地址的集合可以通过将地址空间拆分为每个大小为一个高速缓存行A的插槽,然后计算插槽(I = A / L)的索引,最后执行模运算以将索引映射到目标中来找到. set T(T = I % s);我的第一个问题是:这些假设是否正确?
假设它们是,我尝试使用这些概念,所以我实际上可以看到它们对程序产生了具体的影响.我写了一个简单的测试,它分配一个B字节的内存缓冲区,并从缓冲区的开头以固定的给定步长 增量重复访问该缓冲区的位置(意味着如果是14,步骤是3,我只重复访问位置0 ,3,6,9和12 - 如果是13,14或15 ,则同样如此:BB
int index = 0;
for (int i = 0; i < REPS; i++) …Run Code Online (Sandbox Code Playgroud) 在Agner Fog的手册" C++中的优化软件 "第9.10节"大数据结构中的Cahce争论"中,他描述了当矩阵宽度等于称为临界步幅的情况时转置矩阵的问题.在他的测试中,当宽度等于临界步幅时,L1中矩阵的成本增加40%. 如果矩阵更大并且仅适用于L2,则成本为600%! 这在表9.1中的文字中得到了很好的总结.这与在为什么将512x512的矩阵转置比转置513x513的矩阵要慢得多一样是必不可少的 ?
后来他写道:
这种效果对于二级高速缓存争用而言比一级高速缓存争用强得多的原因是二级高速缓存不能一次预取多行.
所以我的问题与预取数据有关.
根据他的评论,我推断L1可以一次预取多个缓存行. 预取了多少?
据我所知,尝试编写代码来预取数据(例如使用_mm_prefetch)很少有用.我读过的唯一例子是Prefetching Examples?它只有O(10%)的改进(在某些机器上).Agner后来解释了这个:
原因是现代处理器由于无序执行和高级预测机制而自动预取数据.现代微处理器能够自动预取包含具有不同步幅的多个流的常规访问模式的数据.因此,如果可以使用固定步幅以常规模式排列数据访问,则不必显式预取数据.
那么CPU如何决定预取哪些数据,以及有哪些方法可以帮助CPU为预取做出更好的选择(例如"具有固定步幅的常规模式")?
编辑:根据Leeor的评论,让我添加我的问题并使其更有趣. 与L1相比,为什么关键步幅对L2的影响要大得多?
编辑:我试图使用代码重现Agner Fog的表格为什么转换512x512的矩阵要比转置513x513的矩阵慢得多? 我在Xeon E5 1620(Ivy Bridge)上以MSVC2013 64位版本模式运行它,它具有L1 32KB 8路,L2 256 KB 8路和L3 10MB 20路.L1的最大矩阵大小约为90x90,L3为256x256,L3为1619.
Matrix Size Average Time
64x64 0.004251 0.004472 0.004412 (three times)
65x65 0.004422 0.004442 0.004632 (three times)
128x128 0.0409
129x129 0.0169
256x256 0.219 //max L2 matrix size
257x257 0.0692
512x512 2.701
513x513 0.649
1024x1024 12.8
1025x1025 10.1
Run Code Online (Sandbox Code Playgroud)
我没有看到L1中的任何性能损失,但是L2明显具有关键的步幅问题,可能是L3.我不确定为什么L1没有出现问题.有可能还有一些其他的背景源(开销)占据了L1时代的主导地位.
我们正在尝试使用Intel CLFLUSH指令在用户空间中刷新Linux中进程的缓存内容.
我们创建了一个非常简单的C程序,它首先访问一个大型数组,然后调用CLFLUSH来刷新整个数组的虚拟地址空间.我们测量CLFLUSH刷新整个阵列所需的延迟.程序中阵列的大小是一个输入,我们将输入从1MB变为40MB,步长为2MB.
根据我们的理解,CLFLUSH应该刷新缓存中的内容.所以我们期望看到整个阵列的刷新延迟首先在阵列大小方面线性增加,然后在阵列大小大于20MB(这是我们程序的LLC的大小)之后延迟应该停止增加.
然而,实验结果非常令人惊讶,如图所示.数组大小超过20MB后,延迟不会停止增加.
我们想知道如果地址不在缓存中,CLFLUSH是否可能在CLFLUSH将地址刷出缓存之前引入地址?我们还试图在英特尔软件开发人员手册中搜索,但没有找到任何解释,如果地址不在缓存中,CLFLUSH会做什么.
以下是我们用于绘制图形的数据.第一列是以KB为单位的数组大小,第二列是以秒为单位刷新整个数组的延迟.
任何建议/建议都不仅仅是值得赞赏的.
[改性]
以前的代码是不必要的.尽管CLFLUSH具有相似的性能,但它可以更容易地在用户空间中完成.所以我删除了凌乱的代码以避免混淆.
SCENARIO=Read Only
1024,.00158601000000000000
3072,.00299244000000000000
5120,.00464945000000000000
7168,.00630479000000000000
9216,.00796194000000000000
11264,.00961576000000000000
13312,.01126760000000000000
15360,.01300500000000000000
17408,.01480760000000000000
19456,.01696180000000000000
21504,.01968410000000000000
23552,.02300760000000000000
25600,.02634970000000000000
27648,.02990350000000000000
29696,.03403090000000000000
31744,.03749210000000000000
33792,.04092470000000000000
35840,.04438390000000000000
37888,.04780050000000000000
39936,.05163220000000000000
SCENARIO=Read and Write
1024,.00200558000000000000
3072,.00488687000000000000
5120,.00775943000000000000
7168,.01064760000000000000
9216,.01352920000000000000
11264,.01641430000000000000
13312,.01929260000000000000
15360,.02217750000000000000
17408,.02516330000000000000
19456,.02837180000000000000
21504,.03183180000000000000
23552,.03509240000000000000
25600,.03845220000000000000
27648,.04178440000000000000
29696,.04519920000000000000
31744,.04858340000000000000
33792,.05197220000000000000
35840,.05526950000000000000
37888,.05865630000000000000
39936,.06202170000000000000
Run Code Online (Sandbox Code Playgroud) x86/x86_64体系结构的每个现代高性能CPU都有一些数据缓存层次结构:L1,L2,有时是L3(在极少数情况下是L4),从/向主RAM加载的数据缓存在其中一些中.
有时程序员可能希望某些数据不会缓存在某些或所有缓存级别中(例如,当想要memset 16 GB的RAM并将某些数据保留在缓存中时):有一些非时间(NT)指令用于这就像MOVNTDQA(/sf/answers/2596471/ http://lwn.net/Articles/255364/)
但有没有一种编程方式(对于某些AMD或Intel CPU系列,如P3,P4,Core,Core i*,......)完全(但暂时)关闭部分或全部级别的缓存,以改变每个内存的方式访问指令(全局或某些应用程序/ RAM区域)使用内存层次结构?例如:关闭L1,关闭L1和L2?或更改每次存储器访问类型CR0 ??? SDM vol3a页的"未缓存的" UC(CD + NW位423 424,425和" 仅适用于基于处理器的三级缓存禁止标志,位在IA32_MISC_ENABLE MSR 6(可用英特尔NetBurst微体系结构) - 允许禁用和启用L3缓存,独立于L1和L2缓存.").
我认为这样的行动将有助于保护数据免受缓存侧通道攻击/泄漏,如窃取AES密钥,隐蔽缓存通道,Meltdown/Spectre.虽然这种禁用会产生巨大的性能成本.
PS:我记得多年前在一些技术新闻网站上发布的这样一个程序,但现在找不到它.将一些神奇的值写入MSR只是一个Windows exe,并使每个Windows程序运行得很慢.缓存关闭直到重新启动或直到使用"撤消"选项启动程序.
在CUDA 2.0设备上有没有办法只为一个特定变量禁用L1缓存?我知道,一个可以在编译时禁用L1缓存添加标志-Xptxas -dlcm=cg,以nvcc对所有内存操作.但是,我想仅对特定全局变量的内存读取禁用缓存,以便所有其余内存读取都通过L1缓存.
基于我在网络上进行的搜索,可能的解决方案是通过PTX汇编代码.