2

我想知道在我的程序中是否只有 1 个线程,我可以编写它以便四核或 i7 可以实际使用不同的内核吗?通常当我在四核计算机上编写程序时,CPU 使用率只会达到 25% 左右,而且工作似乎在 4 个核心之间进行,如任务管理器所示。(我写的程序通常是 Ruby、Python 或 PHP,所以它们可能没有那么优化)。

更新:如果我用 C 或 C++ 编写它会怎样,并且

for (i = 0; i < 100000000; i++) {
  a = i * 2;
  b = i + 1;
  if (a == ...  || b == ...) { ... }
}

然后对编译器使用最高级别的优化。编译器能否使乘法发生在一个内核上,而加法发生在另一个内核上,从而使两个内核同时工作?使用 2 个核心不是一个相当容易的优化吗?

4

10 回答 10

7

不,您需要使用线程在多个 CPU(无论是真实的还是虚拟的)上同时执行多个路径......一个线程的执行本质上是绑定到一个 CPU,因为这保持了语句之间的“发生在之前”的关系,这是核心程序如何工作。

于 2009-05-18T09:30:11.467 回答
3

首先,除非在程序中创建了多个线程,否则该程序中只有一个执行线程。

看到 25% 的 CPU 资源用于该程序,这表明四个内核中的一个内核正在以 100% 的速度使用,但所有其他内核都没有被使用。如果使用了所有内核,那么理论上该进程可能会占用 100% 的 CPU 资源。

附带说明一下,Windows 任务管理器中显示的图表是当时运行的所有进程的 CPU 利用率,而不仅仅是一个进程。

其次,您提供的代码可以拆分为可以在两个单独的线程上执行的代码,以便在两个内核上执行。我猜你想证明这一点a并且b彼此独立,它们只依赖于i. 在这种情况下,for像下面这样分离循环内部可以允许多线程操作,从而提高性能:

// Process this in one thread:
for (int i = 0; i < 1000; i++) {
    a = i * 2;
}

// Process this in another thread:
for (int i = 0; i < 1000; i++) {
    b = i + 1;
}

但是,变得棘手的是,是否需要评估来自两个单独线程的结果的时间,例如if后面的​​语句似乎暗示的那样:

for (i = 0; i < 1000; i++) {
  // manipulate "a" and "b"
  if (a == ...  || b == ...) { ... }
}

这将需要查找位于不同线程(在不同处理器上执行)中的aand值,这是一个非常令人头疼的问题。b

没有真正好的保证i两个线程的值同时相同(毕竟,乘法和加法可能会花费不同的时间来执行),这意味着一个线程可能需要等待另一个线程在比较和对应于从属值i之前使值同步。或者,我们是否要创建第三个线程来进行两个线程的值比较和同步?无论哪种情况,复杂性都开始迅速增加,所以我认为我们可以同意我们开始看到出现严重的混乱——线程之间共享状态可能非常棘手。abi

因此,您提供的代码示例只是部分可并行化而不需要太多努力,但是,一旦需要比较两个变量,分离这两个操作很快就会变得非常困难。

并发编程的几个经验法则:

当有些任务可以分解为涉及完全独立于其他数据及其结果(状态)的数据处理的部分时,并行化可以非常容易。

例如,从输入计算值的两个函数(在伪代码中):

f(x) = { return 2x }
g(x) = { return x+1 }

这两个函数不相互依赖,因此它们可以并行执行而没有任何痛苦。此外,由于它们不是在计算之间共享或处理的状态,即使x需要计算多个值,即使这些值也可以进一步拆分:

x = [1, 2, 3, 4]
foreach t in x:
    runInThread(f(t))
foreach t in x:
    runInThread(g(t))

现在,在这个例子中,我们可以有 8 个独立的线程来执行计算。对于并发编程来说,没有副作用可能是一件好事。

但是,一旦依赖于其他计算的数据和结果(这也意味着存在副作用),并行化就变得极其困难。在许多情况下,这些类型的问题必须连续执行,因为它们等待返回其他计算的结果。

也许问题归结为,为什么编译器不能找出可以自动并行化并执行这些优化的部分?我不是编译器方面的专家,所以我不能说,但维基百科上有一篇关于自动并行化的文章可能有一些信息。

于 2009-05-18T15:39:42.360 回答
2

我非常了解英特尔芯片。

根据您的代码,“如果(a == ... || b == ...)”是一个障碍,否则处理器内核将并行执行所有代码,而不管编译器做了什么样的优化。这只要求编译器不是一个非常“愚蠢”的编译器。这意味着硬件本身具有能力,而不是软件。因此,在这种情况下不需要线程编程或 OpenMP,尽管它们有助于改进并行计算。注意这里并不意味着超线程,只是普通的多核处理器功能。

请谷歌“处理器管道多端口并行”以了解更多信息。

这里我想举一个经典的例子,它可以由多核/多通道IMC平台(例如Intel Nehalem家族,如Core i7)并行执行,不需要额外的软件优化。

char buffer0[64];
char buffer1[64];
char buffer2[64];
char buffer[192];

int i;
for (i = 0; i < 64; i++) {
    *(buffer + i) = *(buffer0 + i);
    *(buffer + 64 + i) = *(buffer1 + i);
    *(buffer + 128 + i) = *(buffer2 + i);
}

为什么?3个原因。

1 Core i7有一个三通道IMC,其总线宽度为192位,每通道64位;并且内存地址空间在每个高速缓存行的基础上在通道之间交错。高速缓存行长度为 64 字节。所以基本上buffer0在通道0上,buffer1在通道上,buffer2在通道2上;而对于缓冲区[192],它在 3 个通道之间交错,每个通道 64 个。IMC 支持同时从多个通道加载或存储数据。那是具有最大吞吐量的多通道 MC 突发。在我的以下描述中,我只会说每个通道 64 字节,例如每个通道 w/BL x8(突发长度 8、8 x 8 = 64 字节 = 缓存行)。

2 buffer0..2 和 buffer 在内存空间中是连续的(在虚拟和物理上的特定页面上,堆栈内存)。运行时,缓冲区 0、1、2 和缓冲区被加载/提取到处理器缓存中,总共 6 个缓存行。所以在开始执行上面的“for(){}”代码之后,完全不需要访问内存,因为所有数据都在缓存中,L3缓存,一个非核心部分,由所有核心共享。我们不会在这里讨论 L1/2。在这种情况下,每个内核都可以提取数据然后独立计算它们,唯一的要求是操作系统支持 MP 并且允许窃取任务,例如运行时调度和关联共享。

3 buffer0、1、2和buffer之间没有任何依赖关系,所以没有执行停顿或障碍。例如执行 *(buffer + 64 + i) = *(buffer1 + i) 不需要等待 *(buffer + i) = *(buffer0 + i) 的执行完成。

虽然,最重要和最难的一点是“窃取任务,运行时调度和亲和共享”,这是因为对于一个给定的任务,只有一个任务执行上下文,它应该由所有内核共享以执行并行执行。任何人如果能理解这一点,他/她就是世界顶级专家之一。我正在寻找这样的专家来合作我的开源项目,并负责并行计算和最新的 HPC 架构相关工作。

请注意,在上面的示例代码中,您还可以使用一些 SIMD 指令,例如 movntdq/a,它将绕过处理器缓存并直接写入内存。在进行软件级优化时也是一个非常好的主意,虽然访问内存非常昂贵,例如访问缓存(L1)可能只需要 1 个周期,但在以前的 x86 芯片上访问内存需要 142 个周期。

请访问http://effocore.googlecode.comhttp://effogpled.googlecode.com了解详情。

于 2009-10-17T17:48:52.570 回答
1

The only way to use multiple cores without using multithreading is to use multiple programs.

In your example above, one program could handle 0-2499999, the next 2500000-4999999, and so on. Set all four of them off at the same time, and they will use all four cores.

Usually you would be better off writing a (single) multithreaded program.

于 2009-05-18T10:39:24.013 回答
1

隐式并行性可能是您正在寻找的。

于 2009-05-18T09:30:38.817 回答
1

如果您的应用程序代码是单线程的,则仅在以下情况下才会使用多个处理器/内核:

  • 您使用的库正在使用多个线程(可能将这种用法隐藏在一个简单的界面后面)
  • 您的应用程序会产生其他进程来执行其部分操作

但是,Ruby、Python 和 PHP 应用程序都可以编写为使用多线程。

于 2009-05-18T09:30:39.110 回答
1

单线程程序将只使用一个内核。操作系统很可能会不时地在内核之间切换程序——根据一些规则来平衡负载等。所以你会看到总体上只有 25% 的使用率并且所有四个内核都在工作——但一次只有一个。

于 2009-05-18T09:31:27.473 回答
1

使用 C/C++,您可以使用OpenMP。这是带有编译指示的 C 代码,例如

#pragma omp parallel for
for(..) {
...
}

说这个 for 将并行运行。这是并行化某些东西的一种简单方法,但有时您必须了解并行程序的执行方式,并且会遇到并行编程错误。

于 2009-05-19T18:14:43.217 回答
1

如果您想并行选择评估为“真”您的陈述的“i”,if (a == ... || b == ...)那么您可以使用 PLINQ(在 .NET 4.0 中)执行此操作:

        //note the "AsParallel"; that's it, multicore support.
        var query = from i in Enumerable.Range(0, 100000000).AsParallel()
                    where (i % 2 == 1 && i >= 10) //your condition
                    select i;

        //while iterating, the query is evaluated in parallel! 
        //Result will probably never be in order (eg. 13, 11, 17, 15, 19..)
        foreach (var selected in query)
        {
            //not parallel here!
        }

相反,如果您想并行化操作,您将能够:

Parallel.For(0, 100000000, i =>
{
    if (i > 10)           //your condition here
        DoWork(i);        //Thread-safe operation
});
于 2009-10-17T15:14:53.997 回答
0

由于您在谈论“任务管理器”,因此您似乎在 Windows 上运行。但是,如果您在那里运行一个具有多个进程的网络服务器(对于带有 fcgi 或 Apache 预分叉的 Ruby 或 PHP,在较小程度上是其他 Apache 工作人员),那么它们往往会分散在内核中。

如果只有一个没有线程的程序正在运行,那么,不,不会有任何显着的优势——你一次只会破坏一件事,除了操作系统驱动的后台进程。

于 2009-05-18T09:47:41.230 回答