0

我正面临一个“也许”奇怪的问题。假设我有一个可执行文件。当我在具有两个内核的计算机上运行它时,该过程运行时间为 t1。然后,如果我运行进程的两个实例(相同的可执行文件但在不同的目录上,手动或使用 gnu 并行启动),每个进程的运行时间并不接近 t1,但实际上更大,有时接近 1.9t1。我必须注意这两个核心是物理的(macbook pro mid 2009,Mountain Lion)。我还在具有 8 个内核的 linux 机器上测试了这种行为。如果我运行 1、2、3 和 4 个实例,每个实例的运行时间大约为 t1。但是,在 5、6、7 和 8 个实例之后,每个实例的运行时间越来越大于 t1。

我在运行模拟时检测到了这种行为。我能够将测试用例简化为下面介绍的简单测试。我想在几个编译级别检查静态std::vectorstd::array动态数组。测试代码如下:

#include <iostream>
#include <vector>
#include <array>
#include <cstdlib>

struct Particle {
private:
  int nc;
public:
  void reset(void) { nc = 0; };
  void set(const int & val) { nc = val; };
};

#define N 10000 // number of particles
#define M 200000 // number of steps

#define STDVECTOR 0
#define STDARRAY  0
#define ARRAY     1
#define DYNARRAY  0

int main (void)
{
#if STDVECTOR
  std::vector<Particle> particles(N);
#elif STDARRAY
  std::array<Particle, N> particles;
#elif ARRAY
  Particle particles[N];
#elif DYNARRAY
  Particle *particles; particles = new Particle [N];
#endif

  int jj = 0;
  for (int ii = 0; ii < M; ++ii) {
    //for (auto & body : particles) body.reset();
    for (int idx = 0; idx < N; ++idx) particles[idx].reset();
    jj = ii;
  }
  particles[0].set(jj*drand48());

  return 0;
}

编译测试完成为

for a in 0 1 2 3; do printf "\n\nOPT=$a\n\n"; g++-4.8 -O${a} -o tmp.x tmp.cpp; cp tmp.x simul01/ ; cp tmp.x simul02/; time simul01/tmp.x ; parallel 'time {}/tmp.x' ::: simul01 simul02 ; done

对于两核机,我得到如下数据: 运行时间或一两个进程

其中时间以秒为单位,例如vector-1或vector-2分别表示使用astd::vector和运行一个或两个进程时的运行时间。对于这两个过程,我在两者之间花费的时间最长。

我的期望:我希望两个进程的运行时间与单个进程的时间相似。但是,当运行多个实例时,时间会系统性地增加,即使核心数量足够。正如我所说,这也发生在八核机器上,当进程数大于 4 时。

我如何测量时间:我使用了time命令,并选择了用户时间。系统时间如此之小,不足以解释运行一两个进程时的差异。

我检查了 gcc 4.6、4.7、4.8 和 4.9。

因此,我的问题是:为什么会发生这种情况?也许与操作系统的某些内在特性以及进程从核心到核心的迁移有关。我真的不知道。我非常感谢有人能对此有所了解,因为这会影响我的模拟运行时间。我需要同时运行多个进程,但运行时间正在增加。相比之下,另一个具有不同方法的模拟代码在一个和两个进程中运行的时间几乎相同。所以我想放弃或确保这是我自己的程序的问题。我也不知道如何以可移植的方式(在 mac 和 linux 之间)设置处理器亲和力。

提前致谢

4

3 回答 3

0

您的编译器CPU 可能会自动对代码进行线程化。因此,您将获得 1 个实例的最大性能......因此运行 2 个实例将花费大约两倍的时间。

于 2013-09-13T01:02:15.070 回答
0

可能有很多东西:

  1. 核心争用——尤其是英特尔的超线程往往会减慢不包含不可预测跳转的 CPU 密集型代码。但也可能是,如果您只有两个内核,则与系统中的其他进程(网络数据包、唤醒以检查新邮件等)的其他进程有更多的争用。
  2. 缓存争用:如果代码和数据不能全部放入 L1 缓存中,它很可能最终会进入共享缓存,并且两个实例争夺谁来存储什么以及存储在哪里。此外,如果两个线程被安排在同一个核心上,因为必须运行其他一些进程(例如您的电子邮件客户端、Web 浏览器等),那么 L1 缓存内容将从进程 A 中被丢弃以使进程 B 受益。它不会不需要那么多微秒来完全填充L1缓存中的替换内容,所以即使很短的时间也会影响这一点。第二个过程也有可能转移到另一个过程。
  3. 内存带宽限制 - 两个实例使用两倍的内存带宽,导致内存负载增加。

上面的列表远不是一个完整的可能性列表,只是一些更常见的。

您可以通过使用分析工具(例如oprofileor )来判断这些选项中的哪一个(或其他选项) perf,并检查机器上的各种性能计数器,比较 1 进程与 2 进程场景。

于 2013-09-13T01:08:06.140 回答
0

请记住,与您的应用程序(内核线程、后台进程等)一起运行的其他任务(内核线程、后台进程等)在 2 个内核上引入另一个 CPU 密集型任务意味着可能存在跨内核的线程迁移,并且本质上它们正在争夺资源。对于具有更多内核的 Linux 场景也是如此。一旦达到一定的限制,那么您将与系统上的其他进程竞争,无论是您生成的进程还是后台进程和内核线程等......

于 2013-09-13T01:06:33.213 回答