在 ccNUMA 系统上测量带宽
Measuring bandwidth on a ccNUMA system
我正在尝试使用 2x Intel(R) Xeon(R) Platinum 8168 对 ccNUMA 系统的内存带宽进行基准测试:
- 24 核 @ 2.70 GHz,
- 一级缓存 32 kB,二级缓存 1 MB 和三级缓存 33 MB。
作为参考,我使用了 Intel Advisor 的 Roofline 图,它描述了每个 CPU 可用数据路径的带宽。据此,带宽为230GB/s.
为了对此进行基准测试,我使用了我自己的小基准测试助手工具,它在循环中执行定时实验。 API 提供了一个名为 experiment_functor
的摘要 class
看起来像这样:
class experiment_functor
{
public:
//+/////////////////
// main functionality
//+/////////////////
virtual void init() = 0;
virtual void* data(const std::size_t&) = 0;
virtual void perform_experiment() = 0;
virtual void finish() = 0;
};
然后用户(我自己)可以定义数据初始化、要计时的工作,即实验和清理例程,以便新分配的数据可以用于每个实验。派生的 class 的实例可以提供给 API 函数:
perf_stats perform_experiments(experiment_functor& exp_fn, const std::size_t& data_size_in_byte, const std::size_t& exp_count)
这是 Schönauer 向量三元组 class 的实现:
class exp_fn : public experiment_functor
{
//+/////////////////
// members
//+/////////////////
const std::size_t data_size_;
double* vec_a_ = nullptr;
double* vec_b_ = nullptr;
double* vec_c_ = nullptr;
double* vec_d_ = nullptr;
public:
//+/////////////////
// lifecycle
//+/////////////////
exp_fn(const std::size_t& data_size)
: data_size_(data_size) {}
//+/////////////////
// main functionality
//+/////////////////
void init() final
{
// allocate
const auto page_size = sysconf(_SC_PAGESIZE) / sizeof(double);
posix_memalign(reinterpret_cast<void**>(&vec_a_), page_size, data_size_ * sizeof(double));
posix_memalign(reinterpret_cast<void**>(&vec_b_), page_size, data_size_ * sizeof(double));
posix_memalign(reinterpret_cast<void**>(&vec_c_), page_size, data_size_ * sizeof(double));
posix_memalign(reinterpret_cast<void**>(&vec_d_), page_size, data_size_ * sizeof(double));
if (vec_a_ == nullptr || vec_b_ == nullptr || vec_c_ == nullptr || vec_d_ == nullptr)
{
std::cerr << "Fatal error, failed to allocate memory." << std::endl;
std::abort();
}
// apply first-touch
#pragma omp parallel for schedule(static)
for (auto index = std::size_t{}; index < data_size_; index += page_size)
{
vec_a_[index] = 0.0;
vec_b_[index] = 0.0;
vec_c_[index] = 0.0;
vec_d_[index] = 0.0;
}
}
void* data(const std::size_t&) final
{
return reinterpret_cast<void*>(vec_d_);
}
void perform_experiment() final
{
#pragma omp parallel for simd safelen(8) schedule(static)
for (auto index = std::size_t{}; index < data_size_; ++index)
{
vec_d_[index] = vec_a_[index] + vec_b_[index] * vec_c_[index]; // fp_count: 2, traffic: 4+1
}
}
void finish() final
{
std::free(vec_a_);
std::free(vec_b_);
std::free(vec_c_);
std::free(vec_d_);
}
};
注意: 函数 data
有一个特殊用途,它试图抵消 NUMA 平衡的影响。通常,在随机迭代中,函数 perform_experiments
使用所有线程以随机方式写入此函数提供的数据。
问题: 使用这个我一直得到最大值。 201 GB/s 的带宽。为什么我无法达到规定的 230 GB/s?
如果需要,我很乐意提供任何额外信息。非常感谢您的回答。
更新:
根据@VictorEijkhout 的建议,我现在对只读带宽进行了强大的扩展实验。
如您所见,峰值带宽确实是平均 217 GB/s,最大 225 GB/s。仍然很费解的是,在某一点上,增加CPUs实际上减少了有效带宽。
带宽性能取决于您执行的操作类型。对于混合读写,您确实不会获得峰值;如果你只做阅读,你会离得更近。
我建议您阅读“Stream 基准测试”的文档,并查看发布的数字。
进一步说明:我希望你把你的线程与 OMP_PROC_BIND
联系起来?此外,您的架构在用完内核之前就用完了带宽。您的最佳带宽性能可能会在少于内核总数的情况下发生。
我正在尝试使用 2x Intel(R) Xeon(R) Platinum 8168 对 ccNUMA 系统的内存带宽进行基准测试:
- 24 核 @ 2.70 GHz,
- 一级缓存 32 kB,二级缓存 1 MB 和三级缓存 33 MB。
作为参考,我使用了 Intel Advisor 的 Roofline 图,它描述了每个 CPU 可用数据路径的带宽。据此,带宽为230GB/s.
为了对此进行基准测试,我使用了我自己的小基准测试助手工具,它在循环中执行定时实验。 API 提供了一个名为 experiment_functor
的摘要 class
看起来像这样:
class experiment_functor
{
public:
//+/////////////////
// main functionality
//+/////////////////
virtual void init() = 0;
virtual void* data(const std::size_t&) = 0;
virtual void perform_experiment() = 0;
virtual void finish() = 0;
};
然后用户(我自己)可以定义数据初始化、要计时的工作,即实验和清理例程,以便新分配的数据可以用于每个实验。派生的 class 的实例可以提供给 API 函数:
perf_stats perform_experiments(experiment_functor& exp_fn, const std::size_t& data_size_in_byte, const std::size_t& exp_count)
这是 Schönauer 向量三元组 class 的实现:
class exp_fn : public experiment_functor
{
//+/////////////////
// members
//+/////////////////
const std::size_t data_size_;
double* vec_a_ = nullptr;
double* vec_b_ = nullptr;
double* vec_c_ = nullptr;
double* vec_d_ = nullptr;
public:
//+/////////////////
// lifecycle
//+/////////////////
exp_fn(const std::size_t& data_size)
: data_size_(data_size) {}
//+/////////////////
// main functionality
//+/////////////////
void init() final
{
// allocate
const auto page_size = sysconf(_SC_PAGESIZE) / sizeof(double);
posix_memalign(reinterpret_cast<void**>(&vec_a_), page_size, data_size_ * sizeof(double));
posix_memalign(reinterpret_cast<void**>(&vec_b_), page_size, data_size_ * sizeof(double));
posix_memalign(reinterpret_cast<void**>(&vec_c_), page_size, data_size_ * sizeof(double));
posix_memalign(reinterpret_cast<void**>(&vec_d_), page_size, data_size_ * sizeof(double));
if (vec_a_ == nullptr || vec_b_ == nullptr || vec_c_ == nullptr || vec_d_ == nullptr)
{
std::cerr << "Fatal error, failed to allocate memory." << std::endl;
std::abort();
}
// apply first-touch
#pragma omp parallel for schedule(static)
for (auto index = std::size_t{}; index < data_size_; index += page_size)
{
vec_a_[index] = 0.0;
vec_b_[index] = 0.0;
vec_c_[index] = 0.0;
vec_d_[index] = 0.0;
}
}
void* data(const std::size_t&) final
{
return reinterpret_cast<void*>(vec_d_);
}
void perform_experiment() final
{
#pragma omp parallel for simd safelen(8) schedule(static)
for (auto index = std::size_t{}; index < data_size_; ++index)
{
vec_d_[index] = vec_a_[index] + vec_b_[index] * vec_c_[index]; // fp_count: 2, traffic: 4+1
}
}
void finish() final
{
std::free(vec_a_);
std::free(vec_b_);
std::free(vec_c_);
std::free(vec_d_);
}
};
注意: 函数 data
有一个特殊用途,它试图抵消 NUMA 平衡的影响。通常,在随机迭代中,函数 perform_experiments
使用所有线程以随机方式写入此函数提供的数据。
问题: 使用这个我一直得到最大值。 201 GB/s 的带宽。为什么我无法达到规定的 230 GB/s?
如果需要,我很乐意提供任何额外信息。非常感谢您的回答。
更新:
根据@VictorEijkhout 的建议,我现在对只读带宽进行了强大的扩展实验。
如您所见,峰值带宽确实是平均 217 GB/s,最大 225 GB/s。仍然很费解的是,在某一点上,增加CPUs实际上减少了有效带宽。
带宽性能取决于您执行的操作类型。对于混合读写,您确实不会获得峰值;如果你只做阅读,你会离得更近。
我建议您阅读“Stream 基准测试”的文档,并查看发布的数字。
进一步说明:我希望你把你的线程与 OMP_PROC_BIND
联系起来?此外,您的架构在用完内核之前就用完了带宽。您的最佳带宽性能可能会在少于内核总数的情况下发生。