gpt4 book ai didi

c++ - 使用 INTEL TBB 的可扩展内存分配

转载 作者:太空宇宙 更新时间:2023-11-04 16:00:04 24 4
gpt4 key购买 nike

我想在 RAM 上分配大约 40 GB。我的第一次尝试是:

#include <iostream>
#include <ctime>

int main(int argc, char** argv)
{
unsigned long long ARRAYSIZE = 20ULL * 1024ULL * 1024ULL * 1024ULL;
unsigned __int16 *myBuff = new unsigned __int16[ARRAYSIZE]; // 3GB/s 40GB / 13.7 s
unsigned long long i = 0;
const clock_t begintime = clock();
for (i = 0; i < ARRAYSIZE; ++i){
myBuff[i] = 0;
}
std::cout << "finish: " << float(clock() - begintime) / CLOCKS_PER_SEC << std::endl;
std::cin.get();
delete [] myBuff;
return 0;
}

内存写入速度约为 3 GB/s,这对我的高性能系统来说并不令人满意。

所以我尝试了英特尔 Cilk Plus,如下所示:

    /*
nworkers = 5; 8.5 s ==> 4.7 GB/s
nworkers = 8; 8.2 s ==> 4.8 GB/s
nworkers = 10; 9 s ==> 4.5 GB/s
nworkers = 32; 15 s ==> 2.6 GB/s
*/

#include "cilk\cilk.h"
#include "cilk\cilk_api.h"
#include <iostream>
#include <ctime>

int main(int argc, char** argv)
{
unsigned long long ARRAYSIZE = 20ULL * 1024ULL * 1024ULL * 1024ULL;
unsigned __int16 *myBuff = new unsigned __int16[ARRAYSIZE];
if (0 != __cilkrts_set_param("nworkers", "32")){
std::cout << "Error" << std::endl;
}
const clock_t begintime = clock();
cilk_for(long long j = 0; j < ARRAYSIZE; ++j){
myBuff[j] = 0;
}
std::cout << "finish: " << float(clock() - begintime) / CLOCKS_PER_SEC << std::endl;
std::cin.get();
delete [] myBuff;
return 0;
}

结果注释在代码上方。可以看出,当 nworkers = 8 时速度会加快。但是 nworkers 越大,分配越慢。我想可能是由于线程锁定。所以我尝试了英特尔 TBB 提供的可扩展分配器:

#include "tbb\task_scheduler_init.h"
#include "tbb\blocked_range.h"
#include "tbb\parallel_for.h"
#include "tbb\scalable_allocator.h"
#include "cilk\cilk.h"
#include "cilk\cilk_api.h"
#include <iostream>
#include <ctime>
// No retry loop because we assume that scalable_malloc does
// all it takes to allocate the memory, so calling it repeatedly
// will not improve the situation at all
//
// No use of std::new_handler because it cannot be done in portable
// and thread-safe way (see sidebar)
//
// We throw std::bad_alloc() when scalable_malloc returns NULL
//(we return NULL if it is a no-throw implementation)

void* operator new (size_t size) throw (std::bad_alloc)
{
if (size == 0) size = 1;
if (void* ptr = scalable_malloc(size))
return ptr;
throw std::bad_alloc();
}

void* operator new[](size_t size) throw (std::bad_alloc)
{
return operator new (size);
}

void* operator new (size_t size, const std::nothrow_t&) throw ()
{
if (size == 0) size = 1;
if (void* ptr = scalable_malloc(size))
return ptr;
return NULL;
}

void* operator new[](size_t size, const std::nothrow_t&) throw ()
{
return operator new (size, std::nothrow);
}

void operator delete (void* ptr) throw ()
{
if (ptr != 0) scalable_free(ptr);
}

void operator delete[](void* ptr) throw ()
{
operator delete (ptr);
}

void operator delete (void* ptr, const std::nothrow_t&) throw ()
{
if (ptr != 0) scalable_free(ptr);
}

void operator delete[](void* ptr, const std::nothrow_t&) throw ()
{
operator delete (ptr, std::nothrow);
}



int main(int argc, char** argv)
{
unsigned long long ARRAYSIZE = 20ULL * 1024ULL * 1024ULL * 1024ULL;
tbb::task_scheduler_init tbb_init;
unsigned __int16 *myBuff = new unsigned __int16[ARRAYSIZE];
if (0 != __cilkrts_set_param("nworkers", "10")){
std::cout << "Error" << std::endl;
}
const clock_t begintime = clock();
cilk_for(long long j = 0; j < ARRAYSIZE; ++j){
myBuff[j] = 0;
}
std::cout << "finish: " << float(clock() - begintime) / CLOCKS_PER_SEC << std::endl;

std::cin.get();
delete [] myBuff;
return 0;
}

(以上代码改编自 James Reinders,O'REILLY 的 Intel TBB 书籍)但结果几乎与之前的尝试相同。我设置了 TBB_VERSION 环境变量,看看我是否真的使用Scalable_malloc 和得到的信息在这张图中(nworkers = 32):

https://www.dropbox.com/s/y1vril3f19mkf66/TBB_Info.png?dl=0

我愿意知道我的代码有什么问题。我预计内存写入速度至少约为 40 GB/s。
我应该如何正确使用可伸缩分配器?
有人可以提供一个使用 INTEL TBB 的可扩展分配器的简单验证示例吗?

环境:英特尔至强 CPU E5-2690 0 @ 2.90 GHz(2 个处理器),224 GB 内存(2 * 7 * 16 GB)DDR3 1600 MHz,Windows 服务器 2008 R2 数据中心,Microsoft Visual Studio 2013 和 Intel C++ 编译器 2017。

最佳答案

期待什么

来自 wikipedia : "DDR3-xxx 表示数据传输速率,描述 DDR 芯片,而 PC3-xxxx 表示理论带宽(截断最后两位数字),用于描述组装的 DIMM。带宽是通过每秒传输数乘以计算得出的八。这是因为 DDR3 内存模块在 64 数据位宽的总线上传输数据,并且由于一个字节包含 8 位,这相当于每次传输 8 个字节的数据。”

因此单个模块 DDR3-1600 最大能够达到 1600*8 = 12800 MB/s让您的系统有 4 个 channel (每个处理器),您应该能够达到:

12800 * 4 = 51200 MB/s - 51.2 GB/s,这就是 CPU specifications 中的说明方式

你总共有两个 CPU 和 8 个 channel :你应该能够达到它的两倍,并行工作。但是,您的系统是 NUMA 系统 - 在这种情况下内存放置很重要......

但是

每个 channel 可以放置多个内存库。当在 channel 中放置更多模块时,您会减少可用时序 - 例如,PC-1600 可能表现为 PC-1333 或更少 - 这通常在主板规范中报告。示例 here .

您有七个 模块——您的 channel 不是平均分配的……您的带宽受到最慢 channel 的限制。建议 channel 彼此相等。

如果您降频到 1333,您可以期待:1333 * 8 = 10666 MB/s 每 channel :

每个 CPU 42 GB/s

然而

channel 在寻址空间中交错分布,您在对内存块进行清零时会使用所有 channel 。只有在使用 strip 访问访问内存时,您才会遇到性能问题。

内存分配不是内存访问

TBB 可扩展分配让许多线程优化内存分配。也就是说,分配时没有全局锁,内存分配不会被其他线程事件限制。这就是操作系统分配器中经常发生的事情。

在您的示例中,您根本没有使用很多分配,只有一个主线程。你正试图获得最大内存带宽。使用不同的分配器时,内存访问不会改变。

阅读评论我看到你想优化内存访问。

优化内存访问

用对 memset() 的单个调用替换归零循环,并让编译器对其进行优化/内联。 -/O2 应该足够了。

理由

英特尔编译器用优化的内部函数/内联调用替换了许多库调用(memset、memcpy 等)。在这种情况下——即,将一大块 ram 归零——内联并不重要,但使用优化的内在函数非常重要:它将使用流指令的优化版本:SSE4.2/AVX

然而,基本的 libc memset 将胜过任何手写循环。至少在 Linux 上。

关于c++ - 使用 INTEL TBB 的可扩展内存分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46523375/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com