0

我在两个程序之间做了一些比较,这些程序用来自 mersenne twister 的伪随机整数填充给定向量,关键是 TBB 版本非常慢,当 TBB 需要时,std 版本在大约 0.6 秒内执行任务至少 1.1 秒。

我还注意到,TBB 并没有真正提供与容器一起使用的优化算法,但它只提供通用构造(parallel_for、parallel_for_each 和类似的)来处理通用任务,std::generate在这种情况下,std 提供了更好、更清洁的解决方案。

您可以在此处下载我的带有 2 个小源文件 + 一个用于 gcc 的 Makefile 的小测试http://www.sendspace.com/file/ew73h8

我在这里做错了什么?我增加这个向量的大小越多,TBB 越慢,我在 Ubuntu 13.04 64 位下使用 Intel Q6600。

TBB 版本在某些方面会更好吗?

编辑:2个文件的完整来源


配置文件

#define N 10000000

标准cpp

#include <random>
#include <iostream>
#include <vector>
#include <algorithm>

#include "config.hpp"

int main() {

    std::vector<u_int32_t> v(N);

    std::mt19937 mt;
    std::uniform_int_distribution<u_int32_t> dist(0,499);

    std::generate(v.begin(),v.end(),[&]{return dist(mt);});

    return(0);
}

tbb.cpp

#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>

#include <random>
#include <iostream>

#include "config.hpp"

int main()
{
  tbb::concurrent_vector<u_int32_t> v(N);
  std::mt19937 mt;
  std::uniform_int_distribution<u_int32_t> dist(0, 499);
  tbb::parallel_for_each(v.begin(),v.end(),[&](u_int32_t& e){e = dist(mt); });

  return(0);
}
4

3 回答 3

4

您正在为 ITBB 将控制的所有工作人员共享随机数生成器 (RNG),正如我从您的问题中看到的那样,这将是四个。撇开从多个线程改变 RNG 状态的线程安全问题不谈,我会指出对缓存的影响:RNG 状态使用的相同内存可用于从四个处理器读取和写入,这很可能使缓存无用。

让我们试试这个:

#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>

#include <vector>
#include <functional>

#include <random>
#include <iostream>

#include "config.hpp"

static thread_local std::mt19937 mt;
static thread_local std::uniform_int_distribution<u_int32_t> dist(0, 499);

int main()
{
  std::vector<u_int32_t> v(N);

  auto f = [&v](std::pair<u_int32_t, u_int32_t> const& p) {
     for (size_t i=p.first; i < p.second; i++)
     {
        v[i] = dist( mt );
     }
  };

  std::vector< std::pair< u_int32_t, u_int32_t > > work;
  work.push_back( std::make_pair( 0, N/2) );
  work.push_back( std::make_pair( N/2, N) );

  tbb::parallel_for_each(
    work.begin(),
    work.end(),
    f 
    );

  return(0);
}

现在时间下降到标准版本的近一半(我只有一个双核)。代码所做的是强制 itbb 在连续的内存块中工作,而不是分发数据,而是分发工作分配。我不认为这是使用 ITBB 的最佳方式,但另一方面,parallel_for_each 不能提供块大小(从我在文档中看到的),而使用 *parallel_for* 来做需要一些研究。但这并不难:

#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for.h>

#include <vector>
#include <functional>

#include <random>
#include <iostream>

#include "config.hpp"

static thread_local std::mt19937 mt;
static thread_local std::uniform_int_distribution<u_int32_t> dist(0, 499);

int main()
{
  std::vector<u_int32_t> v(N);

  auto f = [&v](const tbb::blocked_range<u_int32_t>& p) {
     for (auto i=p.begin(); i < p.end(); i++)
     {
        v[i] = dist( mt );
     }
  };

  tbb::parallel_for(
    tbb::blocked_range<u_int32_t>(0,N), 
    f 
    );

  return(0);
}

您可能不想使用 ITBB,而是希望在 OpenMP 中使用一些并行结构,无论如何,它已经与 gcc 捆绑多年(您仍然可以将 ITBB 与 OpenMP 一起使用,但要小心)。

随机数和并行代码呢?他们很乱。如果您想独立地为 RNG 播种并用于时钟,上面的代码可能就足够了。如果您想获得可重现的结果和不相关的 RNG,那么您必须注意每个生成器都由特定于线程的种子初始化,并且您还需要一种让每个种子通过其线程接触确定性部分的方法工作的...

于 2013-06-22T15:54:31.673 回答
4

您的代码实际上并没有做任何计算上昂贵的事情,并且还写入了内核之间共享的一块内存,这将导致缓存行频繁失效。内存访问很有可能在整个运行时中占主导地位,然后并行访问共享数据结构(具有潜在的锁定和类似的开销)只会使开销变得更糟。

此外,正如 dsign 所提到的,您通过在线程之间共享随机数生成器来引入额外的开销,这将进一步增加开销。

最后,您在这里并没有真正将苹果与苹果进行比较。使用任何类型的并行运行时库(如英特尔的 TBB)都是有代价的,即这个运行时引入的开销——你必须启动它,concurrent_vector它将具有访问机制,例如引入额外开销的锁等。这些都不是free 并且您看到的性能差异很可能仅归因于您创建的额外运行时开销。

于 2013-06-22T15:54:32.113 回答
1

这有什么影响...

void f(u_int32_t& e)
{
    thread_local std::mt19937 mt;
    thread_local std::uniform_int_distribution<u_int32_t> dist(0, 499);
    e = dist(mt);
}


int main()
{
  tbb::concurrent_vector<u_int32_t> v(N);
  tbb::parallel_for_each(v.begin(),v.end(),f);

  return(0);
}
于 2013-06-22T15:57:38.667 回答