C++二维数组按行遍历和按列遍历的区别

按行遍历的效率更高。(重要前提假设:数组,按行储存;对于clickhouse-client-cpp,由于数据是按列存储的,按列遍历的效率更好)

数组在内存中是按行储存的,按行遍历时可以由指向数组第一个数的指针一直往下走,就可以遍历完整个数组,而按列遍历则要获得指向每一列的第一行的元素的指针,然后每次将指针指下一行,但是指针的寻址很快,所以不会有明显的区别。

按行遍历比按列遍历效率高具体体现在以下几点

1、CPU高速缓存

计算机存在Cache机制,当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在,则不经访问内存直接返回该数据;如果不存在,则要先把内存中的相应数据载入缓存,再将其返回处理器。

缓存从内存中抓取一般都是整个数据块,所以它的物理内存是连续的,几乎都是同行不同列的,而如果内循环以列的方式进行遍历的话,将会使整个缓存块无法被利用,而不得不从内存中读取数据,而从内存读取速度是远远小于从缓存中读取数据的。

2、分页调度

物理内存是以页的方式进行划分的,当一个二维数组很大是如 int[128][1024],假设一页的内存为4096个字节,而每一行正好占据内存的一页(int 存储是4个字节),如果以列的形式进行遍历,就会发生128*1024次的页面调度,而如果以行遍历则只有128次页面调度,而页面调度是有时间消耗的,因而调度次数越多,遍历的时间就越长。

示例:

1.程序:

#include
#include
#include
#include

// 从进行开始执行到完成所经历的墙上时钟时间(wall clock)时间,
// 包括其他进程使用的时间片(time slice)和本进程耗费在阻塞(如等待I/O操作完成)上的时间

// CPU时间是指进程占用CPU的时间,进程阻塞的时间则不会计入CPU时间
void gettime(double *cpu)
{
    struct rusage ru;
    if(cpu != NULL)
    {
        getrusage(RUSAGE_SELF, &ru);
        *cpu = ru.ru_utime.tv_sec + (double)ru.ru_utime.tv_usec * 1e-6;
    }
}

int main(int argc,char* argv[])
{
    int count = 20000;
    double cpu0,cpu1,cpu2;

    int* arr = (int*)malloc(sizeof(int) * count * count);
    int i,j;

    gettime(&cpu0);
    // 按行遍历二维数组
    for(i=0;i

测试机器:
处理器:Apple M1
内存:8g

2.结果:

count=1000 

 

count=5000 

 

count=10000 

 

count=20000 

 

3.说明

第一个for循环按行访问二维数组(第一行第一个、第一行第二个……第二行第一个……),第二个for循环按列访问二维数组(第一行第一个、第二行第一个……第一行第二个……),分别计算两个for循环的时间差
由实验结果可以看出,随着数组数据量的增大,两种访问二维数组的方式的速度相差越来越大

4.原理分析

二维数组的内存地址是连续的,当前行的尾与下一行的头相邻
现代计算机,在CPU与内存之间还有一种存储机制,那就是CPU缓存(cache)。CPU缓存的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。
访问数组元素时,CPU不会每次只从内存中读取一个元素,而是读取一个区域的元素。假设二维数组的大小为(10 x 10),访问第一个元素时,CPU也会读取它的相邻元素,因为这个数组比较小,CPU一次就可以把所有元素缓存,因此无论是按行访问数组还是按列访问数组,CPU访问主存的数量都相同。随着数组元素越来越多,CPU缓存一次只能读取数组不到一行的数据,因此按列访问元素时每访问一个元素都要访问内存,因此速度就会慢很多。

对于clickhouse-client-cpp,由于数据是按列存储的,按列遍历的效率更好:

全市场1分钟行情数据,按行遍历3s,按列遍历则只需要1.8s

你可能感兴趣的:(低延迟,c++,缓存,开发语言)