6

我目前正在尝试使用tbb::concurrent_vector<T>. 这个二维数组将被许多不同的线程访问,这就是为什么我希望它能够最有效地处理并行访问。

我想出了2个解决方案:

  • 使用 atbb::concurrent_vector<tbb::concurrent_vector<T> >来存储它。

  • 将所有内容存储在 a 中tbb::concurrent_vector<T>并使用 w/ 访问元素x * width + y

我偏爱第二个,因为我不想锁定一整行来访问一个元素(因为我假设要访问元素array[x][y],tbb 实现将锁定第xth 行然后第yth 元素)。

我想知道哪种解决方案对您来说更好。

4

2 回答 2

5

首先,我认为关于tbb::concurrent_vector. 此容器类似于std::vector但具有线程安全的大小调整,但由于内部存储布局,元素访问速度较慢。

你可以在这里阅读更多关于它的信息。

在您的情况下,由于您提出的第二种解决方案(带x * width + y索引的一维数组),我假设您的算法不涉及密集的多线程数组大小调整。因此,tbb::concurrent_vector与单线程容器(如std::vector.

我猜你假设tbb::concurrent_vector保证线程安全的元素访问,但它没有 - 引用来自doc:tbb::concurrent_vector::operator[]

获取给定索引处元素的引用。

此方法对于并发读取是线程安全的,并且在增长向量时也是如此,只要调用线程检查了该索引

如果你不调整数组的大小,你只对第一部分感兴趣:线程安全的并发读取。但是 std::vector 甚至原始 C 数组都可以为您提供相同的功能。另一方面,两者都不提供线程安全的任意访问(读/写元素)。

您必须使用锁来实现它,或者找到另一个为您执行此操作的库(可能std::vector来自 STLPort,但我不确定)。虽然这会非常低效,因为每次访问 2D 数组中的元素都会涉及线程同步开销。虽然我不知道您到底要实现什么,但同步很可能需要比您的实际计算更长的时间。

现在回答您的问题,即使在单线程设置中,使用一维数组表示 ND 数组总是更好,因为计算索引 (x * width + y) 比真正的 ND 数组所需的额外内存访问更快. 对于并发向量,这更是如此,因为在最佳情况下(没有冲突的行访问),您将拥有两倍的锁开销,并且在发生冲突的情况下更多。

因此,在您提出的两种解决方案中,我会毫不犹豫地选择第二种:一维数组(不是必需的tbb::concurrent_vector),具有足够的锁定元素访问权限。

根据您的算法和不同线程的访问模式,另一种方法 - 用于图像编辑软件(gimp,photoshop ...) - 基于图块:

template<typename T> struct Tile {
    int offsetX, int offsetY;
    int width, height;
    Not_ThreadSafe_Vector<T> data;
};
ThreadSafe_Vector< Tile<T> > data

哪里Not_ThreadSafe_Vector可以是任何容器而不锁定元素访问,例如std::vector;并且ThreadSafe_Vector是一个具有线程安全读/写元素访问权限的容器(不是tbb::concurrent_vector!)。这样,如果您的访问模式中有一些局部性(一个线程更有可能访问靠近其先前访问的元素而不是远处的元素),那么每个线程都可以处理来自单个图块的大部分数据时间,并且您只有在切换到另一个图块时才会有同步开销。

于 2011-11-10T10:15:43.940 回答
-2

tbb::concurrent_vector对于访问元素(读取、写入、获取地址)和增长向量是完全线程安全的。在此处查看英特尔员工 Arch D. Robison 的回复。

但是,清除或销毁向量的操作不是线程安全的(请参阅 $6.2.1 英特尔 TBB 教程 pdf 文档)。也就是说,clear()如果tbb::concurrent_vector.

正如 Antoine 所提到的,由于效率和性能,始终首选将 ND 阵列作为一维阵列处理。

于 2018-11-03T06:31:48.100 回答