为了测量主存储器的带宽,我提出了以下方法。
代码(针对英特尔编译器)
#include <omp.h>
#include <iostream> // std::cout
#include <limits> // std::numeric_limits
#include <cstdlib> // std::free
#include <unistd.h> // sysconf
#include <stdlib.h> // posix_memalign
#include <random> // std::mt19937
int main()
{
// test-parameters
const auto size = std::size_t{150 * 1024 * 1024} / sizeof(double);
const auto experiment_count = std::size_t{500};
//+/////////////////
// access a data-point 'on a whim'
//+/////////////////
// warm-up
for (auto counter = std::size_t{}; counter < experiment_count / 2; ++counter)
{
// garbage data allocation and memory page loading
double* data = nullptr;
posix_memalign(reinterpret_cast<void**>(&data), sysconf(_SC_PAGESIZE), size * sizeof(double));
if (data == nullptr)
{
std::cerr << "Fatal error! Unable to allocate memory." << std::endl;
std::abort();
}
//#pragma omp parallel for simd safelen(8) schedule(static)
for (auto index = std::size_t{}; index < size; ++index)
{
data[index] = -1.0;
}
//#pragma omp parallel for simd safelen(8) schedule(static)
#pragma omp simd safelen(8)
for (auto index = std::size_t{}; index < size; ++index)
{
data[index] = 10.0;
}
// deallocate resources
free(data);
}
// timed run
auto min_duration = std::numeric_limits<double>::max();
for (auto counter = std::size_t{}; counter < experiment_count; ++counter)
{
// garbage data allocation and memory page loading
double* data = nullptr;
posix_memalign(reinterpret_cast<void**>(&data), sysconf(_SC_PAGESIZE), size * sizeof(double));
if (data == nullptr)
{
std::cerr << "Fatal error! Unable to allocate memory." << std::endl;
std::abort();
}
//#pragma omp parallel for simd safelen(8) schedule(static)
for (auto index = std::size_t{}; index < size; ++index)
{
data[index] = -1.0;
}
const auto dur1 = omp_get_wtime() * 1E+6;
//#pragma omp parallel for simd safelen(8) schedule(static)
#pragma omp simd safelen(8)
for (auto index = std::size_t{}; index < size; ++index)
{
data[index] = 10.0;
}
const auto dur2 = omp_get_wtime() * 1E+6;
const auto run_duration = dur2 - dur1;
if (run_duration < min_duration)
{
min_duration = run_duration;
}
// deallocate resources
free(data);
}
// REPORT
const auto traffic = size * sizeof(double) * 2; // 1x load, 1x write
std::cout << "Using " << omp_get_max_threads() << " threads. Minimum duration: " << min_duration << " us;\n"
<< "Maximum bandwidth: " << traffic / min_duration * 1E-3 << " GB/s;" << std::endl;
return 0;
}
代码说明
- 这是一种“天真”的方法,也仅适用于linux操作系统。它仍然可以作为模型性能的大致指标。
- 使用编译器标志
-O3 -ffast-math -march=coffeelake
编译ICC。 - 文件大小为150 MiB,比系统的最低级缓存(Coffee Lake的i5-8400)大得多,此处使用了2个16 GiB DIMM DDR4 3200 MT/s内存。
- 每次迭代的新分配会使上一个迭代的所有缓存行无效(以消除缓存命中)。
- 最小延迟记录旨在抵消中断和操作系统调度的影响:线程短时间脱离核心等。
- 进行预热运行以抵消动态频率缩放的影响(内核功能,也可以通过使用
userspace
管理器关闭)。
代码结果
在我的机器上,我得到了90 GB/s。Intel Advisor运行其自己的基准测试,已经计算或测量出这个带宽实际上是25 GB/s。 (请参阅我的以前的问题:Intel Advisor's bandwidth information,在其中此代码的先前版本在定时区域内出现了页故障。)
汇编: 这是以上代码生成的汇编链接: https://godbolt.org/z/Ma7PY49bE
我无法理解为什么我的带宽会如此不合理地高。任何有助于促进我理解的提示都将非常感激。
wbinvd
!非常难用。在定时运行之间再次循环缓冲区,并在其中使用clflushopt
,或者将其扩大(如1GiB),以使L3命中更加罕见。 - Peter Cordesvmovntpd ymm
NT存储!(其行为与存储到不可缓存的写组合内存相同。) - Peter Cordes