C++ 向前与向后阵列行走

C++ 向前与向后阵列行走,c++,caching,memory,C++,Caching,Memory,首先,我要说的是,我知道这种微观优化很少具有成本效益。不过我很好奇这些东西是怎么工作的。对于所有缓存线编号等,我都考虑使用x86-64 i5 Intel CPU。对于不同的CPU,数字会明显不同 我经常有这样的印象,向前走一个阵列要比向后走快。我认为,这是因为拉入大量数据是以向前的方式完成的,也就是说,如果我读取字节0x128,那么缓存线(假设长度为64字节)将读取字节0x128-0x191(包括字节0x128-0x191)。因此,如果我想访问的下一个字节是0x129,那么它就已经在缓存中了 然

首先,我要说的是,我知道这种微观优化很少具有成本效益。不过我很好奇这些东西是怎么工作的。对于所有缓存线编号等,我都考虑使用x86-64 i5 Intel CPU。对于不同的CPU,数字会明显不同

我经常有这样的印象,向前走一个阵列要比向后走快。我认为,这是因为拉入大量数据是以向前的方式完成的,也就是说,如果我读取字节0x128,那么缓存线(假设长度为64字节)将读取字节0x128-0x191(包括字节0x128-0x191)。因此,如果我想访问的下一个字节是0x129,那么它就已经在缓存中了

然而,读了一点之后,我现在的印象是,这其实并不重要?因为缓存线对齐将在最近的64可除边界处拾取起始点,因此如果我选取字节0x127作为起始点,我将加载0x64-0x127(包括在内),因此将在缓存中保留数据以供向后行走。当从0x128转换到0x127时,我会遇到cachemiss,但这是我为这个示例选择地址的结果,而不是任何现实世界的考虑

我知道缓存线是以8字节块的形式读入的,因此,如果我们向后走,在第一次操作开始之前必须加载完整的缓存线,但我怀疑这是否会产生巨大的差异

如果我就在这里,而老我错了,有人能澄清一下吗?我搜索了整整一天,仍然没有得到这个问题的最终答案

tl;我们在阵列中行走的方向真的那么重要吗?这真的有区别吗?这在过去有什么不同吗?(大约15年前)

我已经使用以下基本代码进行了测试,前后看到了相同的结果:

#include <windows.h>
#include <iostream>
// Size of dataset
#define SIZE_OF_ARRAY 1024*1024*256
// Are we walking forwards or backwards?
#define FORWARDS 1

int main()
{
    // Timer setup
   LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
   LARGE_INTEGER Frequency;

   int* intArray = new int[SIZE_OF_ARRAY];
    // Memset - shouldn't affect the test because my cache isn't 256MB!
   memset(intArray, 0, SIZE_OF_ARRAY);

    // Arbitrary numbers for break points
   intArray[SIZE_OF_ARRAY - 1] = 55;
   intArray[0] = 15;

   int* backwardsPtr = &intArray[SIZE_OF_ARRAY - 1];

   QueryPerformanceFrequency(&Frequency); 
   QueryPerformanceCounter(&StartingTime);

    // Actual code
   if (FORWARDS)
   {
    while (true)
    {
        if (*(intArray++) == 55)
            break;
    }
   }
   else
   {
    while (true)
    {
        if (*(backwardsPtr--) == 15)
            break;
    }
   }

    // Cleanup
   QueryPerformanceCounter(&EndingTime);
   ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
   ElapsedMicroseconds.QuadPart *= 1000000;
   ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;

   std::cout << ElapsedMicroseconds.QuadPart << std::endl;

    // So I can read the output
   char a;
   std::cin >> a;
   return 0;
}
#包括
#包括
//数据集的大小
#定义数组1024*1024*256的大小
//我们是向前走还是向后走?
#定义转发1
int main()
{
//定时器设置
大整数开始时间、结束时间、经过的微秒;
大整数频率;
int*intArray=新的int[数组的大小];
//Memset-不应该影响测试,因为我的缓存不是256MB!
memset(intArray,0,数组的大小);
//断点的任意数
intArray[数组的大小\u-1]=55;
intArray[0]=15;
int*backardsptr=&intArray[数组的大小-1];
QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&StartingTime);
//实际代码
如果(转发)
{
while(true)
{
如果(*(intArray++)=55)
打破
}
}
其他的
{
while(true)
{
如果(*(backwardsPtr--)=15)
打破
}
}
//清理
QueryPerformanceCounter(&EndingTime);
ElapsedMicroseconds.QuadPart=EndingTime.QuadPart-StartingTime.QuadPart;
ElapsedMicroseconds.QuadPart*=1000000;
ElapsedMicroseconds.QuadPart/=频率.QuadPart;
std::cout a;
返回0;
}
我为A)Windows代码和B)黑客实现道歉。这是为了检验一个假设,但不能证明推理


任何关于行走方向如何产生影响的信息,不仅仅是缓存,还有其他方面,都将不胜感激

正如你的实验所显示的那样,没有区别。与处理器和一级缓存之间的接口不同,内存系统在完整缓存线上进行事务处理,而不是在字节上。正如@user657267所指出的,存在特定于处理器的预取器。这些可能倾向于向前或向后,但我非常怀疑。所有现代预取器都检测方向,而不是假设方向。此外,它们还能检测步幅。它们涉及到难以置信的复杂逻辑,而且像方向这样简单的事情不会让它们垮台


简短回答:朝你想要的任何一个方向走,享受两个方向相同的表演

现代x86处理器在两个方向都可以工作(佩奇是为英特尔设计的,但AMD芯片也有预取器)。大概是在2001年左右的Netburst推出之前,这可能会有所不同。该系统很可能是在假设“向前”更常见的情况下构建的,在向前和向后之间进行的任何权衡都会有利于向前。更大的问题可能不是缓存处理,而是页面错误处理,如果您的应用程序不完全适合RAM,因为磁盘驱动器通常采用向前访问模式。@HotLicks为什么页面错误更喜欢向前?这确实是一个奇怪的内存模型。@CaptainGiraffe-因为代码是在正向执行的,数据往往是在正向访问的,等等。磁盘驱动器知道文件是在正向读取的。