在OpenMP并行代码中,memset是否能够并行运行?

我的内存块可能非常大(比L2高速caching大),有时我必须将它们设置为全零。 memset在串行代码中是好的,但是并行代码呢? 如果从并发线程中调用memset实际上可以加快大型数组的运行速度,有人会有经验吗? 甚至使用简单的openmp并行for循环?

HPC中的人们通常认为,一条线程通常不足以使单个内存链接饱和,对于networking链接也是如此。 这是一个快速而脏的OpenMP启用的memsetter,我为你写了两个2GB的内存填充零。 下面是使用GCC 4.7在不同架构上使用不同线程数的结果(报告的几个运行的最大值):

GCC 4.7,用-O3 -mtune=native -fopenmp编译的代码-O3 -mtune=native -fopenmp

四路英特尔至强X7350 – 带有独立内存控制器和前端总线的Nehalem四核前处理器

单个sockets

 threads 1st touch rewrite 1 1452.223 MB/s 3279.745 MB/s 2 1541.130 MB/s 3227.216 MB/s 3 1502.889 MB/s 3215.992 MB/s 4 1468.931 MB/s 3201.481 MB/s 

(由于从头开始创build线程组,并且操作系统将物理页面映射到由malloc(3)保留的虚拟地址空间malloc(3) ,所以第一次触摸很慢)

一个线程已经饱和单个CPU < – > NB链路的内存带宽。 (NB =北桥)

每个插槽1个线程

 threads 1st touch rewrite 1 1455.603 MB/s 3273.959 MB/s 2 2824.883 MB/s 5346.416 MB/s 3 3979.515 MB/s 5301.140 MB/s 4 4128.784 MB/s 5296.082 MB/s 

需要两个线程来饱和NB存储器链路的全部存储器带宽。

OctosocketsIntel Xeon X7550 – 带八核CPU的8路NUMA系统(禁用CMT)

单个sockets

 threads 1st touch rewrite 1 1469.897 MB/s 3435.087 MB/s 2 2801.953 MB/s 6527.076 MB/s 3 3805.691 MB/s 9297.412 MB/s 4 4647.067 MB/s 10816.266 MB/s 5 5159.968 MB/s 11220.991 MB/s 6 5330.690 MB/s 11227.760 MB/s 

为了使一条存储器链路的带宽饱和,至less需要5个线程。

每个插槽1个线程

 threads 1st touch rewrite 1 1460.012 MB/s 3436.950 MB/s 2 2928.678 MB/s 6866.857 MB/s 3 4408.359 MB/s 10301.129 MB/s 4 5859.548 MB/s 13712.755 MB/s 5 7276.209 MB/s 16940.793 MB/s 6 8760.900 MB/s 20252.937 MB/s 

带宽几乎与线程数成线性关系。 基于单插口观察,可以说为了使所有八个存储器链路饱和,至less需要40个线程分配为每个插槽5个线程。

NUMA系统的基本问题是第一次触摸的内存策略 – 在NUMA节点上分配内存,在该节点上线程首先触摸特定页面内的虚拟地址。 线程固定(绑定到特定的CPU核心)在这样的系统中是必不可less的,因为线程迁移导致远程访问,这是较慢的。 Pinnig支持在大多数OpenMP运行时中可用。 GCC及其libgomp具有GOMP_CPU_AFFINITY环境variables,Intel具有KMP_AFFINITY环境variables等。另外,OpenMP 4.0引入了供应商中立的地方概念。

编辑:为了完整起见 ,下面是在Intel Core i5-2557M (具有HT和QPI的双核Sandy Bridge CPU)的MacBook Air上以1 GiBarrays运行代码的结果。 编译器是GCC 4.2.1(Apple LLVM build)

 threads 1st touch rewrite 1 2257.699 MB/s 7659.678 MB/s 2 3282.500 MB/s 8157.528 MB/s 3 4109.371 MB/s 8157.335 MB/s 4 4591.780 MB/s 8141.439 MB/s 

为什么这个高速甚至只有一个线程? 用gdb一点探索,发现memset(buf, 0, len)被OS X编译器转换为bzero(buf, len)并且由bzero$VARIANT$sse42命名的SSE4.2启用的向量化版本由libc.dylib并在运行时使用。 它使用MOVDQA指令一次清零16个字节的内存。 这就是为什么即使有一个线程的内存带宽几乎饱和。 使用VMOVDQA的单线程AVX启用版本可以一次清零32个字节,并可能使内存链接饱和。

这里的重要信息是,有时候vector化和multithreading并不是正交的,因此加速了操作。

那么总是有三级caching

但是,这很可能已经被主存储器带宽所约束了, 增加更多的并行性不太可能改善事情。