18

执行摘要:如何在他的代码中指定 OpenMP 应该只将线程用于 REAL 内核,即不计算超线程内核?

详细分析:多年来,我在空闲时间编写了一个纯软件的开源渲染器(rasterizer/raytracer)。GPL 代码和 Windows 二进制文件可从此处获得: https ://www.thanassis.space/renderer.html 它在 Windows、Linux、OS/X 和 BSD 下编译和运行良好。

上个月我介绍了一种光线追踪模式 - 生成的图片质量飙升。不幸的是,光线追踪比光栅化慢几个数量级。为了提高速度,就像我对光栅化器所做的那样,我为光线追踪器添加了 OpenMP(和 TBB)支持 - 以轻松利用额外的 CPU 内核。光栅化和光线追踪都可以轻松地进行线程处理(每个三角形的工作量 - 每个像素的工作量)。

在家里,使用我的 Core2Duo,第二个核心帮助了所有模式 - 光栅化和光线追踪模式都获得了 1.85x 和 1.9x 之间的加速。

问题:当然,我很想看到顶级 CPU 性能(我也“玩”过 GPU,初步 CUDA 端口),所以我想要一个坚实的比较基础。我把代码给了我的一个好朋友,他可以使用一台“野兽”机器,它有一个 16 核、1500 美元的英特尔超级处理器。

他以“最重”模式运行它,光线追踪器模式......

...他的速度是我的 Core2Duo 的五分之一(!)

喘息——恐怖。刚才发生了什么?

我们开始尝试不同的修改、补丁……最终我们弄明白了。

通过使用 OMP_NUM_THREADS 环境变量,可以控制产生多少 OpenMP 线程。随着线程数从 1 增加到 8,速度也在增加(接近线性增加)。我们越过 8 的那一刻,速度开始下降,直到它急剧下降到我的 Core2Duo 速度的五分之一,当所有 16 个核心都用完时!

为什么是8?

因为 8 是真正的核心数。其他 8 个是……超线程的!

理论:现在,这对我来说是个新闻——我已经看到超线程在其他算法中的帮助很大(高达 25%),所以这是出乎意料的。显然,即使每个超线程内核都有自己的寄存器(和 SSE 单元?),光线追踪器也无法利用额外的处理能力。这让我想到...

饥饿的可能不是处理能力 - 它是内存带宽。

光线追踪器使用包围体层次结构数据结构来加速光线与三角形的交点。如果使用超线程内核,那么一对中的每个“逻辑内核”都试图从该数据结构中的不同位置(即内存中)读取 - 并且 CPU 缓存(每对本地)被完全颠簸。至少,这是我的理论——欢迎提出任何建议。

所以,问题是: OpenMP 检测“核心”的数量并产生与之匹配的线程——也就是说,它在计算中包含了超线程“核心”。就我而言,这显然会在速度方面导致灾难性的结果。有谁知道如何使用 OpenMP API(如果可能,可移植)只为 REAL 内核生成线程,而不是超线程内核?

PS 代码是开放的 (GPL) 并且可以在上面的链接中找到,请随意在您自己的机器上复制 - 我猜这将发生在所有超线程 CPU 中。

PPS 请原谅帖子的长度,我认为这是一种教育经验并想分享。

4

3 回答 3

6

基本上,您需要一些相当可移植的方式来查询环境以获取相当低级的硬件细节 - 通常,您不能仅通过系统调用来做到这一点(操作系统通常甚至不知道硬件线程和内核之间的差异)。

一个支持多种平台的库是hwloc - 支持 Linux 和 windows(和其他)、intel 和 amd 芯片。Hwloc 将让您了解有关硬件拓扑的所有信息,并了解内核和硬件线程(称为 PU - 处理单元 - 在 hwloc 术语中)之间的区别。所以你会在开始时调用这个库,找到实际内核的数量,然后调用 omp_set_num_threads() (或者只是将该变量作为指令添加到并行部分的开头)。

于 2011-01-27T22:50:41.973 回答
3

不幸的是,您对为什么会发生这种情况的假设很可能是正确的。可以肯定的是,您必须使用配置文件工具 - 但我之前在光线追踪中看到过这一点,所以这并不奇怪。无论如何,目前无法从 OpenMP 确定某些处理器是“真实的”,而某些处理器是超线程的。您可以编写一些代码来确定这一点,然后自己设置数字。但是,仍然存在 OpenMP 不会在处理器本身上调度线程的问题——它允许操作系统这样做。

OpenMP ARB 语言委员会一直在努力为用户定义一种标准方式来确定他的环境并说明如何运行。此时,这个讨论还在激烈进行。许多实现允许您通过使用实现定义的环境变量将线程“绑定”到处理器。但是,用户必须知道处理器编号以及哪些处理器是“真实的”与超线程的。

于 2011-01-27T16:07:12.070 回答
1

问题是 OMP 如何使用 HT。这不是内存带宽!我在我的 2.6GHz HT PIV 上尝试了简单的循环。结果是惊人的......

使用 OMP:

    $ time ./a.out 
    4500000000
    real    0m28.360s
    user    0m52.727s
    sys 0m0.064s

没有 OMP:$ 时间 ./a.out 4500000000

    real0   m25.417s
    user    0m25.398s
    sys 0m0.000s

代码:

    #include <stdio.h>
    #define U64 unsigned long long
    int main() {
      U64 i;
      U64 N = 1000000000ULL; 
      U64 k = 0;
      #pragma omp parallel for reduction(+:k)
      for (i = 0; i < N; i++) 
      {
        k += i%10; // last digit
      }
      printf ("%llu\n", k);
      return 0;
    }
于 2011-06-04T10:17:16.790 回答