一般问题
假设您正在编写一个由图形组成的系统,以及可以根据相邻节点的配置激活的图形重写规则.也就是说,您有一个在运行时期间无法预测地增长/收缩的动态图形.如果你天真地使用malloc,新的节点将被分配在内存中的随机位置; 经过足够的时间,你的堆将成为指针spaghetti,给你可怕的缓存效率.是否有任何轻量级的增量技术可以使节点在一起在内存中保持紧密联系?
我尝试了什么
我唯一能想到的是将节点嵌入到笛卡尔空间中,并使用一些物理弹性模拟来排斥/吸引节点.那将有线节点保持在一起,但看起来很傻,我想模拟的开销会比缓存效率加速更大.
坚实的例子
这是我正在尝试实施的系统.这是我试图在C中优化的代码的简短片段.这个 repo是JS中的一个原型,工作实现,具有可怕的缓存效率(以及语言本身).该视频以图形方式显示系统的运行情况.
我在理解参考的地点方面遇到了问题.任何人都可以帮助我理解它的含义和内容,
我正在尝试在两种算法之间做出决定.一个写入8个字节(两个对齐的4字节字)到2个高速缓存行,另一个写入3个整个高速缓存行.
如果CPU只将更改的8个字节写回内存,则第一个算法使用的内存带宽要少得多:8个字节对192个字节.如果CPU写入整个高速缓存行,则128和192字节之间的差异不那么显着.
那么Intel Xeon CPU如何写回内存?你会惊讶地发现在谷歌找到一个应该众所周知的答案是多么困难.
据我了解,写入进入存储缓冲区,然后进入缓存.当脏缓存行从缓存中逐出时,它们可能只被写入内存,但是英特尔是否跟踪缓存行的哪些部分是脏的,或者只是转储整个内容?我更怀疑他们跟踪缓存行粒度以下的事情.如果在高速缓存行被驱逐之前有任何事情进入内存,我也会感到非常惊讶.
从这里:
指令和数据具有不同的访问模式,并访问不同的内存区域.因此,对于指令和数据具有相同的高速缓存可能并不总是有效.
因此,拥有两个缓存是相当普遍的:仅存储指令的指令缓存和仅存储数据的数据缓存.
知道指令和数据之间的区别是直观的,但现在我不确定这种情况下的区别吗?什么构成数据并被放入数据缓存中,什么构成指令并被放入指令缓存?
我知道ARM组装.请问什么需要STR,LDR,LDMF或者STMFD使用数据缓存?但是从技术上来讲STR,LDR,LDMF和STMFD都说明,所以我这就是为什么我很困惑."数据"必须始终存在"指令"吗?是否在该.data部分中考虑了数据?
例如,LDR R1, =myVar那么LDR会进入指令缓存并且myVar的内容会进入数据缓存吗?或者它不是那样的工作?
说明和数据有不同的访问模式有人可以详细说明吗?
我在一篇有用的帖子上发表的评论突显了我难以理解:
"我的想法是,如果一条指令已经从内存中加载,它可能会很快再次使用",但知道下一条指令的唯一方法就是读取它.这意味着内存读取(你不能说它已经在缓存中了,因为新指令是红色的).所以我仍然没有看到这一点?说刚刚发生了LDR指令,所以现在LDR在数据缓存中.可能会发生另一条LDR指令,也许它不会发生,我们无法确定所以我们必须实际读取下一条指令 - 从而破坏了缓存的目的.
我受到这个问题的启发,编写了一个简单的程序来测试我的机器在每个缓存级别的内存带宽:
我的代码使用memset反复写入缓冲区(或缓冲区)并测量速度.它还保存了最后打印的每个缓冲区的地址.这是列表:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#define SIZE_KB {8, 16, 24, 28, 32, 36, 40, 48, 64, 128, 256, 384, 512, 768, 1024, 1025, 2048, 4096, 8192, 16384, 200000}
#define TESTMEM 10000000000 // Approximate, in bytes
#define BUFFERS 1
double timer(void)
{
struct timeval ts;
double ans;
gettimeofday(&ts, NULL);
ans = ts.tv_sec + ts.tv_usec*1.0e-6;
return ans;
}
int main(int argc, char **argv)
{
double *x[BUFFERS];
double t1, t2;
int kbsizes[] = SIZE_KB;
double bandwidth[sizeof(kbsizes)/sizeof(int)];
int …Run Code Online (Sandbox Code Playgroud) 我试图了解CPU缓存是如何运行的.让我们说我们有这个配置(作为一个例子).
1)根据这些配置,标签的长度应为32-5 = 27位,索引大小为5位(2 ^ 5 =高速缓存行中每个字节的32个地址).
如果总缓存大小为1024且有32个缓存行,那么标记+索引存储在哪里?(还有另外4*32 = 128字节.)这是否意味着缓存的实际大小是1024 + 128 = 1152?
2)如果在这个例子中高速缓存行是32字节,这意味着当需要从RAM获取新字节时,32个字节被复制到高速缓存中.我是否正确地假设所请求字节的缓存行位置将由其地址确定?
这就是我的意思是:如果CPU在请求的字节[FF FF 00 08],则可用的高速缓存线将充满了从字节[FF FF 00 00]到[FF FF 00 1F].我们要求的单字节将处于适当位置[08].
3)如果前面的语句是正确的,是否意味着用于索引的5位在技术上是不需要的,因为所有32个字节都在缓存行中?
如果我出错了,请告诉我.谢谢
我喜欢这个例子,所以我在c中写了一些自修改代码...
#include <stdio.h>
#include <sys/mman.h> // linux
int main(void) {
unsigned char *c = mmap(NULL, 7, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|
MAP_ANONYMOUS, -1, 0); // get executable memory
c[0] = 0b11000111; // mov (x86_64), immediate mode, full-sized (32 bits)
c[1] = 0b11000000; // to register rax (000) which holds the return value
// according to linux x86_64 calling convention
c[6] = 0b11000011; // return
for (c[2] = 0; c[2] < 30; c[2]++) { // incr immediate data after every run
// rest of …Run Code Online (Sandbox Code Playgroud) 显然,x86 INVD使高速缓存层次结构无效,而不将内容写回内存.
我很好奇,这样的教学有什么用?鉴于人们对各种缓存级别的数据控制得很少,甚至对异步刷新的内容控制得更少,这似乎只是一种确保你不知道什么数据的方法.再也不在记忆中了.
本周我的任务中有这个问题,我不明白缓存是如何被打败的,或者我如何用汇编程序来展示它.有人能指出我正确的方向吗?
使用汇编程序示例显示如何消除两个不同的缓存(关联和直接映射).解释为什么会发生这种情况以及如何解决这个问题.用于击败缓存的相同程序是否相同?
注意:这是作业.不要只为我回答这个问题,它不会帮助我理解这些材料.
假设我们有一个数据数组和另一个带索引的数组.
data = [1, 2, 3, 4, 5, 7]
index = [5, 1, 4, 0, 2, 3]
Run Code Online (Sandbox Code Playgroud)
我们想要从data位置的元素创建一个新的数组index.结果应该是
[4, 2, 5, 7, 3, 1]
Run Code Online (Sandbox Code Playgroud)
朴素算法适用于O(N),但它执行随机存储器访问.
你能建议具有相同复杂性的CPU缓存友好算法吗?
PS在我的特定情况下,数据数组中的所有元素都是整数.
PPS阵列可能包含数百万个元素.
PPPS我可以使用SSE/AVX或任何其他x64特定的优化
cpu-cache ×10
c ×5
assembly ×4
caching ×3
algorithm ×2
optimization ×2
performance ×2
x86 ×2
arm ×1
c++ ×1
graph ×1
instructions ×1
intel ×1
memory ×1