18

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

4

2 回答 2

24

HPC 中的人通常说一个线程通常不足以使单个内存链接饱和,网络链接通常也是如此。是我为您编写的一个快速而肮脏的启用 OpenMP 的 memsetter,它用 2 GiB 的内存填充了两次零。以下是使用 GCC 4.7 在不同架构上具有不同线程数的结果(报告了多次运行的最大值):

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

四路 Intel Xeon X7350 - 具有独立内存控制器和前端总线的前 Nehalem 四核 CPU

单插座

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

(第一次触摸很慢,因为线程组是从头开始创建的,并且操作系统正在将物理页面映射到由 保留的虚拟地址空间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 <-> 内存链接的全部内存带宽饱和,需要两个线程。

Octo-socket Intel Xeon X7550 - 带有八核 CPU 的 8 路 NUMA 系统(禁用 CMT)

单插座

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

为了使一个内存链接的带宽饱和,至少需要 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

带宽几乎与线程数成线性关系。根据对单插槽的观察,可以说至少需要 40 个线程,每个插槽分布为 5 个线程,才能使所有 8 个内存链路饱和。

NUMA 系统上的基本问题是首次接触内存策略 - 内存分配在 NUMA 节点上,在该节点上执行第一个接触特定页面中虚拟地址的线程。线程固定(绑定到特定的 CPU 内核)在这样的系统上是必不可少的,因为线程迁移导致远程访问速度较慢。大多数 OpenMP 运行时都支持 pinnig。GCC 具有libgomp环境GOMP_CPU_AFFINITY变量,Intel 具有KMP_AFFINITY环境变量等。此外,OpenMP 4.0 引入了供应商中立的place概念。

编辑:为完整起见,以下是在配备Intel Core i5-2557M(具有 HT 和 QPI 的双核 Sandy Bridge CPU)的MacBook Air 上使用 1 GiB 阵列运行代码的结果。编译器是 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

为什么即使是单线程也有这么高的速度?对由 OS X 编译器翻译成的gdb节目进行了一些探索,并且在运行时提供并使用了名为 的启用 SSE4.2 的矢量化版本。它使用该指令一次将 16 个字节的内存归零。这就是为什么即使只有一个线程,内存带宽也几乎饱和的原因。启用单线程 AVX 的版本可以一次将 32 个字节归零,并且可能会使内存链接饱和。memset(buf, 0, len)bzero(buf, len)bzero$VARIANT$sse42libc.dylibMOVDQAVMOVDQA

这里的重要信息是,有时矢量化和多线程在加速操作方面并不正交。

于 2012-07-20T13:04:57.917 回答
1

好吧,总是有 L3 缓存...

但是,这很可能已经受到主内存带宽的限制;添加更多并行性不太可能改善事情。

于 2012-07-20T10:06:03.157 回答