为什么并行版本比单线程版本慢。虚假分享?

时间:2021-05-28 08:13:54

标签: c++ multithreading parallel-processing process

所以这里有两个版本的代码:

class VectorCount {
private:
     char               *arr;int                
     size;unsigned long long count;
public:
     VectorCount(char *arr, unsigned int size, unsigned long long count) : arr(arr), size(size), count(count) {}

     void add() {
          while(count--) {
               for (int i = 0; i < size; i++) {
                         arr[i]++;
               }
          }
     }
};

// Single-thread version of the code
void main_st() {
     // initizalize array
     char arr[10];

     // create object
     VectorCount v(arr, 10, 100000000);

     // run add
     v.add();
}

// Parallelized version of the code
void main_mt() {
     // initialize array
     char arrA[10];
     char arrB[10];

     // create objects
     VectorCount v1(arrA, 10, 50000000);
     VectorCount v2(arrB, 10, 50000000);

     // create threads
     thread t1, t2;
     t1.create_thread(v1, v1.add);
     t2.create_thread(v2, v2.add);

     // join threads
     t1.join();
     t2.join();

     // Code to do the final sum of the two VectorCount objects to get the same
     // result as the single-threaded version (assume negligible overhead here)
}

假设程序创建了一个 VectorCount 对象的单个实例,数组大小为 10,计数为 1 亿。单线程版本需要5秒完成求和。并行化版本使用两个线程,每个线程拥有一个单独的 VectorCount 实例,数组大小为 10,因此每个 VectorCount 实例的计数仅为 5000 万,因为每个线程完成了一半的工作。并行化版本需要 8 秒才能完成。为什么它更慢?我在想这是由于错误共享。但我不确定。缓存大小为 64 字节。

我们可以让并行化版本运行得更快吗?我正在考虑更改 VectorCount 数组大小。但是,使用 2 个线程时,什么大小会使并行化版本运行得更快?既然缓存大小是 64 字节,int 是 4 字节,那么在这种情况下 size = 16 会解决错误共享吗? (4 x 16 = 64)。

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

您的代码做的工作很少,只要有足够的上下文,优化器就可以将您的代码基本上变成(假设,在您的情况下,count 可以被 size 整除):

for (int i = 0; i < size; i++) {
  arr[i] = count / size;
}

由于从未使用过 arr 的值,优化器甚至可以消除此代码。

请注意,由于 arr 未初始化,您的代码具有未定义的行为。您应该使用 char arr[10] {0} 将数组初始化为 0

然而,您使优化者的工作越复杂,发现这些机会的可能性就越小。通过 std::thread 构造函数的机制传递您的数组和参数可能会阻止优化器意识到它可以以这种方式优化您的代码。重新排列您的代码:

std::thread t1([] {
    char arrA[10]{0};
    VectorCount v1(arrA, 10, 50'000'000);
    v1.add();
    });
std::thread t2([] {
    char arrA[10]{0};
    VectorCount v1(arrA, 10, 50'000'000);
    v1.add();
    }
);

使它更类似于您的单线程版本,并且优化器能够发挥它的魔力,而并行版本要快得多:https://godbolt.org/z/9j9ocoMTd。如果您查看第一个版本的汇编代码,您会注意到提到了 50000000 而不是 100000000,在第二个版本中您会注意到 50000000 也不存在因为两个版本都没有优化。

请注意,它仍然比单线程版本慢,但由于单线程版本只需要大约 100ns,因此创建和加入 2 个线程的开销可能比并行节省的开销更多。

在我装有 Visual Studio 的机器上,单线程版本的代码需要 85 毫秒,并行版本的代码需要 45 毫秒(visual studio 无法完全删除您的代码)。

在 GCC 上阻止优化器会产生更多相似的结果:https://godbolt.org/z/Mhf5xjx33

相关问题