如何计算内存访问时间
How do you calculate memory access time?
我创建了一个大型布尔2d数组(5000X5000,共有250亿个元素,23MB)。然后,我循环遍历并用随机的true或false实例化每个元素。然后我循环浏览并阅读每一个元素。所有2500万个元素在约100毫秒内被读取。
23MB太大了,无法放入CPU的缓存,我认为我的程序太简单了,无法从任何类型的编译器优化中获益,所以我得出的结论是,该程序在大约100ms内从RAM读取了2500万个元素,这是对的吗?
#include "stdafx.h"
#include <iostream>
#include <chrono>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
bool **locs;
locs = new bool*[5000];
for(int i = 0; i < 5000; i++)
locs[i] = new bool[5000];
for(int i = 0; i < 5000; i++)
for(int i2 = 0; i2 < 5000; i2++)
locs[i][i2] = rand() % 2 == 0 ? true : false;
int *idx = new int [5000*5000];
for(int i = 0; i < 5000*5000; i++)
*(idx + i) = rand() % 4999;
bool val;
int memAccesses = 0;
auto start = std::chrono::high_resolution_clock::now();
for(int i = 0; i < 5000*5000; i++) {
val = locs[*(idx + i)][*(idx + ++i)];
memAccesses += 2;
}
auto finish = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count() << " nsn";
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(finish-start).count() << " msn";
cout << "TOTAL MEMORY ACCESSES: " << memAccesses << endl;
cout << "The size of the array in memory is " << ((sizeof(bool)*5000*5000)/1048576) << "MB";
int exit; cin >> exit;
return 0;
}
/*
OUTPUT IS:
137013700 ns
137 ms
TOTAL MEMORY ACCESSES: 25000000
The size of the array in memory is 23MB
*/
正如其他答案所提到的,您看到的"速度"(即使CPU正在执行您的代码,并且它没有被编译器剥离)约为250MBps,这对于现代系统来说是非常非常低的数字。
然而,在我看来,你的方法论似乎有缺陷(无可否认,我不是基准测试方面的专家。)
- 对于任何这样的基准测试,即使是最简单的形式,也需要区分随机访问和顺序访问不是一个随机访问设备(尽管它的名字),在这里表现非常糟糕。您的代码似乎是随机访问内存的,所以您将其添加到结论中作为限定符:您"在大约100ms内从RAM的随机位置读取2500万个元素。"
- 这种基准测试的另一个方面是延迟与吞吐量的概念。同样,如果你想从你的数字和时间得出任何结论,你需要知道你到底在测量什么
-
您的内存访问计数不正确。根据编译器生成的确切代码,这一行:
val = locs[*(idx + i)][*(idx + ++i)];
可以实际地访问存储器系统4到9次之间的任何地方。
- 最多,如果
i
、idx
、locs
和val
都在寄存器中,或者对它们的访问被取消,那么您需要读取*(idx + i)
,读取locs[*(idx + i)]
(请记住,locs
是指向数组的指针数组,而不是是2D数组)读取*(idx + ++i)
,最后读取locs[*(idx + i)][*(idx + ++i)]
。其中的一些可能会被缓存,但这不太可能,因为缓存正在发生抖动 - 最坏的情况是,除了上述内容之外,您还需要对
++i
进行两次访问(读取,然后写回),一次用于idx
,一次针对locs
,一次为val
。我不知道,您甚至可能需要对单个i
进行另一次读取和/或对两次出现的idx
进行两次读取(由于指针别名等原因)
- 最多,如果
- 您需要注意,内存永远不会以单个字节甚至单词访问内存总是以缓存行为单位进行读取和写入。缓存行的大小可能因系统而异,尽管目前最常见的大小是64字节。因此,每次读取不在缓存中的内存位置时,都会从RAM加载64个字节(或更多)。如果正在读取的内存位置位于缓存线边界(一条缓存线中的一些字节和下一条缓存线上的一些字节),则从RAM加载两条缓存线。给定一个健全的编译器和内存中正确对齐的变量,这种情况不会经常发生,但可能会发生。因此,您必须至少将计算出的使用带宽乘以缓存线的大小
- 但是,如果您正在访问一个已经在缓存中的内存位置,则不会从RAM加载任何内容。你也需要在结论中考虑这一点
- 在评估缓存和内存的性能时,您还需要考虑缓存线逐出、缓存的关联性、级别的数量、一些缓存级别在指令和数据之间共享,有些没有,有些在核心之间共享,还有一些没有,以及许多其他因素
- DRAM芯片也有许多奇怪而复杂的行为和特性。一些内存位置在其他内存位置之后读取速度更快(由于行和列的排列),一些访问可能会因为刷新周期而延迟很长时间(以CPU速度),其他设备可能正在使用RAM或RAM所在的总线,等等。我对现代存储芯片的操作还很不熟悉,甚至我知道这完全是一团糟
- 必须考虑编译器优化对代码的影响。这意味着你必须在编译器完成代码后,以汇编的形式查看代码。你需要查看生成的汇编,才能知道你的代码实际上在做什么:你的内存访问是否以及哪些被优化了
总而言之,我不认为你能从你的程序中得出很多有用的信息。很抱歉,但记忆很复杂!
部分内存(块)将一次存储在处理器缓存中,这使处理器能够快速访问这些项。然而,这种速度对于现代记忆来说是完全合理的。即使是最慢的DDR3 ram也可以以大约6 GB/s的速度传输数据。
缓存的使用与程序的复杂性无关。每当从RAM读取数据时,数据都会进入缓存。由于缓存有一定的大小,所以总是有那么多的数据可用。如果您访问前一个位置旁边的内存位置,则很有可能该位置已被缓存。在这种情况下,RAM不被访问。
我建议阅读CPU缓存维基百科条目来拓宽你的知识面。
BTW:val = locs[*(idx + i)][*(idx + ++i)];
你确定这是从左到右评估的吗?我不是。这是一种未定义的行为。我建议将++i
放在访问器行下面。
//编辑:
从内存读取的值没有任何作用。很有可能这些指令根本没有执行!检查字节码或添加(void) val;
指令,该指令应强制生成字节码。
编号。读取并不总是一直到RAM。当执行读取(或写入)时,内存块会被拉入缓存。只要正在读取的块已经在缓存中,就会使用缓存。如果您从不在缓存中的块请求数据,则会访问RAM以获取内存块并将其放入缓存。从缓存中读取要比从RAM中读取便宜得多。
编辑
同样,写操作会导致内存中的块被拉入缓存。因为您在读取值之前将其存储在程序中,所以您正在读取的数据很可能在存储时就已经在缓存中了。因此,读取值的循环很可能永远不需要访问RAM。
- C++尝试深度复制唯一指针时出现内存访问冲突
- 如何使用 MPI 的远程内存访问 (RMA) 功能并行化数据聚合?
- CRTP - 危险的内存访问?
- C++ Python 的扩展 - 安全内存访问和内存布局
- 在Visual Studio中查找非法内存访问
- C++内存访问违反内存大块
- 数组中未映射的内存访问从python传递到c++
- 使用加速进程间创建消息队列 - 内存访问冲突
- C 指针转换会导致内存访问冲突
- 为什么代码会抛出非法内存访问错误
- 多线程环境中C++内存访问
- CUDA 中的递归返回非法内存访问
- 为什么创建进程 API 调用会导致内存访问冲突?
- 在 C++ 中遍历链表比在具有类似内存访问的 Go 中慢
- 确定打开进程的内存访问位置
- 存在内存访问异常,但我不确定我的代码中出了什么问题
- 指向结构的指针的 2D 数组.内存访问问题
- GPU 内存访问和使用 (CUDA)
- 在实时程序中是动态内存访问有害的
- 随机 mmaped 内存访问比堆数据访问慢 16%