在Ulrich Drepper的论文中,每个程序员应该了解内存,第三部分:CPU缓存,他显示了一个图表,显示了"工作集"大小与每个操作消耗的cpu周期之间的关系(在这种情况下,顺序读取).并且图中有两个跳转,表示L1缓存和L2缓存的大小.我编写了自己的程序来重现c中的效果.它只是简单地从头到尾顺序读取一个int []数组,我尝试了不同大小的数组(从1KB到1MB).我将数据绘制成图形并且没有跳跃,图形是直线.
我的问题是:
顺便说一句,我在linux中这样做.
感谢Stephen C的建议,这里有一些额外的信息:这是我的代码:
int *arrayInt;
void initInt(long len) {
int i;
arrayInt = (int *)malloc(len * sizeof(int));
memset(arrayInt, 0, len * sizeof(int));
}
long sreadInt(long len) {
int sum = 0;
struct timespec tsStart, tsEnd;
initInt(len);
clock_gettime(CLOCK_REALTIME, &tsStart);
for(i = 0; i < len; i++) {
sum += arrayInt[i];
}
clock_gettime(CLOCK_REALTIME, &tsEnd);
free(arrayInt);
return (tsEnd.tv_nsec - tsStart.tv_nsec) / len;
}
Run Code Online (Sandbox Code Playgroud)
在main()函数中,我尝试过从1KB到100MB的数组大小,仍然相同,每个元素的平均耗时为2纳秒.我认为时间是L1d的访问时间.
我的缓存大小:
L1d == 32k
L2 == 256k
L3 == …
我正在为intel core i7构建缓存模拟器,但很难找到L1,L2和L3缓存(共享)的详细规范.我需要Cacheblock大小,缓存大小,关联性等等......有人能指出我的好方向吗?
我正在尝试学习循环优化.我发现循环平铺有助于使数组循环更快.我尝试使用下面给出的两个代码块,有或没有循环阻塞,并测量两者的时间.我大部分时间都没有发现明显的差异.我测试了不同的块大小,但我不知道如何选择块大小.如果我的方向错了,请帮助我.实际上我发现没有块的循环可以更快地工作.
一个.随着阻止
int max = 1000000;
int B = 100;
for (i = 0; i < max; i += B)
{
for (j = i; j < (i + B); j++)
{
array[j] = 0;
}
}
Run Code Online (Sandbox Code Playgroud)
湾 没有阻止
for (i = 0; i < max; i++)
{
array[i] = 0;
}
Run Code Online (Sandbox Code Playgroud)
所用时间:阻塞:经过时间 - 6997000 Nano Secs
没有阻塞经过的时间 - 6097000 Nano Secs
我需要使用另一个数组(readArray)计算一个数组(writeArray),但问题是数组之间的索引映射是不一样的(writeArray的索引x处的值必须使用readArray的索引y处的值计算)所以它不是很缓存友好.
但是我可以选择循环浏览readArray还是顺序浏览writeArray.
所以这是一个简化的代码:
int *readArray = new int[ARRAY_SIZE]; // Array to read
int *writeArray = new int[ARRAY_SIZE]; // Array to write
int *refArray = new int[ARRAY_SIZE]; // Index mapping between read and write, could be also array of pointers instead indexes
// Code not showed here : Initialization of readArray with values, writeArray with zeroes and refArray with random indexes for mapping between readArray and writeArray (values of indexes between 0 and ARRAY_SIZE - 1)
// Version 1: Random read …Run Code Online (Sandbox Code Playgroud) 当发生高速缓存未命中时,CPU从主存储器中取出整个高速缓存行到高速缓存层次结构中.(通常在x86_64上为64个字节)
这是通过数据总线完成的,在现代64位系统上只有8字节宽.(因为字长是8字节)
编辑: "数据总线"表示在此上下文中CPU芯片和DRAM模块之间的总线.该数据总线宽度不一定与字大小相关.
根据策略,首先获取实际请求的地址,然后按顺序获取高速缓存行的其余部分.
如果有一个64字节宽的总线,它会更快,这将允许一次获取整个高速缓存行.(这将是字长的八倍)
也许可能有两种不同的数据总线宽度,一种用于标准高速缓存线提取,另一种用于外部硬件(DMA),仅适用于字大小存储器访问.
限制数据总线大小的限制是什么?
memory caching cpu-architecture cpu-cache micro-architecture
这个问题专门针对现代x86-64缓存一致性架构 - 我很欣赏其他CPU的答案可能会有所不同.
如果我写入内存,MESI协议要求首先将缓存行读入缓存,然后在缓存中进行修改(将值写入缓存行,然后将其标记为脏).在较旧的写入微架构中,这将触发高速缓存行被刷新,在写回期间,被刷新的高速缓存行可能会延迟一段时间,并且一些写入组合可能在两种机制下发生(更可能是回写) .我知道这与访问相同缓存行数据的其他核心如何交互 - 缓存监听等.
我的问题是,如果商店恰好匹配缓存中已有的值,如果没有单个位被翻转,那么任何英特尔微架构都会注意到这一点并且不将该行标记为脏,从而可能将该行标记为独占,以及在某些时候跟随的回写内存开销?
当我向更多的循环进行矢量化时,我的矢量化操作组合基元不会明确地检查值的变化,并且在CPU/ALU中这样做似乎很浪费,但我想知道底层缓存电路是否可以在没有显式编码的情况下完成(例如,商店微操作或缓存逻辑本身).由于跨多个内核的共享内存带宽变得更加成为资源瓶颈,这似乎是一种越来越有用的优化(例如,重复调整相同的内存缓冲区 - 如果它们已经存在,我们不会重新读取RAM中的值在缓存中,但强制写回相同的值似乎很浪费).回写缓存本身就是对这类问题的承认.
我可以礼貌地要求阻止"在理论上"或"它确实无关紧要"的答案 - 我知道记忆模型是如何工作的,我正在寻找的是关于如何写出相同价值的硬性事实(而不是避免一个商店)将影响内存总线的争用你可以安全地假设是一台运行多个工作负载的机器几乎总是受内存带宽限制.另一方面,解释为什么芯片不这样做的确切原因(我悲观地假设他们没有这样做)将具有启发性......
更新: 这里的预期线路上的一些答案https://softwareengineering.stackexchange.com/questions/302705/are-there-cpus-that-perform-this-possible-l1-cache-write-optimization但仍然很多推测"它必须很难,因为它没有完成",并说如何在主CPU核心中这样做会很昂贵(但我仍然想知道为什么它不能成为实际缓存逻辑本身的一部分).
我已经了解了不同的缓存映射技术,如直接映射,关联映射和集合关联映射技术,还学习了权衡.但我很好奇现在在intel core i7或AMD处理器中使用了什么.以及这些技术是如何演变的.还有哪些事情需要改进?
我的目标是将静态结构加载到 L1D 缓存中。之后使用这些结构成员执行一些操作,并在完成操作后运行invd以丢弃所有修改过的缓存行。所以基本上我想在缓存内创建一个安全的环境,这样在缓存内执行操作时,数据就不会泄漏到 RAM 中。
为此,我有一个内核模块。我在结构的成员上放置了一些固定值。然后我禁用抢占,禁用所有其他 CPU(当前 CPU 除外)的缓存,禁用中断,然后使用__builtin_prefetch()将我的静态结构加载到缓存中。之后,我用新值覆盖之前放置的固定值。之后,我执行invd(清除修改后的缓存行),然后为所有其他 CPU 启用缓存,启用中断并启用抢占。我的理由是,当我在原子模式下这样做时,INVD将删除所有更改。从原子模式回来后,我应该看到我之前放置的原始固定值。然而,这并没有发生。退出原子模式后,我可以看到用于覆盖先前放置的固定值的值。这是我的模块代码,
奇怪的是,重新启动PC后,我的输出发生了变化,我只是不明白为什么。现在,我根本没有看到任何变化。我正在发布完整的代码,包括@Peter Cordes 建议的一些修复,
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("test INVD");
static struct CACHE_ENV{
unsigned char in[128];
unsigned char out[128];
}cacheEnv __attribute__((aligned(64)));
#define cacheEnvSize (sizeof(cacheEnv)/64)
//#define change "Hello"
unsigned char change[]="hello";
void disCache(void *p){
__asm__ __volatile__ (
"wbinvd\n"
"mov %%cr0, %%rax\n\t"
"or $(1<<30), %%eax\n\t"
"mov %%rax, %%cr0\n\t"
"wbinvd\n"
::
:"%rax"
);
printk(KERN_INFO "cpuid %d --> cache disable\n", …Run Code Online (Sandbox Code Playgroud) 这是一个边缘话题。因为我想了解编程、CPU 缓存、读取 CPU 缓存行等,所以我把它贴在这里。
我在 C/C++ 中实现了 AES 算法。由于在没有硬件支持的情况下执行 GF(2 8 ) 乘法在计算上是昂贵的,我已经优化为使用 AES S-box 的查找表。但不幸的是,基于查找表的实现容易受到缓存定时攻击。因此,由于对 CPU 缓存非常天真,我开始学习它的工作原理,以及如何在不增加任何计算成本的情况下规避这种攻击。
我意识到实际上有 AES NI 指令和 Bit Slice AES 实现,但它们远远超出了我目前的理解。
我从 crypto.SE 了解到,最简单的方法是在查找之前读取所有缓存行或读取整个表。(这也影响了我的表现)但是我不知道如何在软件中实现它,或者它背后的复杂概念。
在OpenSSLaes-x86core.c的C 实现参考指南中—— 作者实现了一个功能:
static void prefetch256(const void *table)
{
volatile unsigned long *t=(void *)table,ret;
unsigned long sum;
int i;
/* 32 is common least cache-line size */
for (sum=0,i=0;i<256/sizeof(t[0]);i+=32/sizeof(t[0])) sum ^= t[i];
ret = sum;
}
Run Code Online (Sandbox Code Playgroud)
for循环i中增加8 …