C ++多线程性能比单线程代码慢

时间:2019-04-18 09:50:02

标签: c++ multithreading

我正在学习在c ++中使用线程
我用整数创建了一个很长的向量,并设置了另一个整数x。我想计算该整数与向量中整数之间的差。

但是,在我的实现中,使用两个线程的函数比使用单个线程的函数要慢。我想知道为什么是原因,以及如何正确实现线程以使其运行得更快。

代码如下:

#include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <math.h>

using namespace std;


vector<int> vector_generator(int size) {
    vector<int> temp;
    for (int i = 0; i < size; i++) {
        temp.push_back(i);
    }
    return temp;
}

vector<int> dist_calculation(int center, vector<int> &input, int start, int end) {
    vector<int> temp;
    for (int i = start; i < end; i++) {
        temp.push_back(abs(center - input[i]));
    }
    return temp;
}


void multi_dist_calculation(int center, vector<int> &input) {
    int mid = input.size() / 2;

    vector<int> temp1(input.begin(), input.begin() + mid);
    vector<int> temp2(input.begin()+mid, input.end());

    auto future1 = async(dist_calculation, center, temp1, 0, mid);
    auto future2 = async(dist_calculation, center, temp2, 0, mid);

    vector<int> result1 = future1.get();
    vector<int> result2 = future2.get();

    return;
}


int main() {

    vector<int> v1 = vector_generator(1000000000);
    vector<int> result;
    multi_dist_calculation(0, v1);
    //dist_calculation(50, v1, 0, v1.size());

    return 0;
}



更新#1

添加了 std :: launch :: async reserve()的建议,它确实使代码更快。但是2线程函数仍然比单线程函数慢。我可以说在这种计算中,单线程更快吗?

#include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <math.h>

using namespace std;


vector<int> vector_generator(int size) {
    vector<int> temp;
    temp.reserve(size);
    for (int i = 0; i < size; i++) {
        temp.push_back(i);
    }
    return temp;
}

vector<int> dist_calculation(int center, vector<int> &input, int start, int end) {
    vector<int> temp;
    temp.reserve(end - start);
    for (int i = start; i < end; i++) {
        temp.push_back(abs(center - input[i]));
    }
    return temp;
}


void multi_dist_calculation(int center, vector<int> &input) {
    int mid = input.size() / 2;

    auto future1 = async(std::launch::async, dist_calculation, center, input,   0, mid);
    auto future2 = async(std::launch::async, dist_calculation, center, input, mid, input.size());

    vector<int> result1 = future1.get();
    vector<int> result2 = future2.get();

    return;
}


int main() {

    vector<int> v1 = vector_generator(1000000000);
    vector<int> result;
    int center = 0;
    multi_dist_calculation(center, v1);
    //dist_calculation(center, v1, 0, v1.size());

    return 0;
}

2 个答案:

答案 0 :(得分:6)

您没有将任何std::launch policy传递给std::async,因此给实现带来了很大的自由度。

  

行为类似于调用(2)且策略为std :: launch :: async | std :: launch ::递延。换句话说,当查询生成的std :: future以获取值时,f可以在另一个线程中执行,或者可以同步运行

但还要注意,更普遍的是,使用更多的线程,尤其是在执行小型任务时,可能不会更快。

  • dist_calculation或您想线程化的任何任务仅是少量工作的情况下,请注意开销。创建新线程的成本相对较高,并且任何内部池std::async的使用,承诺和未来都会产生开销。
  • 另外,按照编写的方式,可能会创建更多的矢量,具有更多的动态内存,并且需要合并结果,这也会带来一定的成本。
  • 在更复杂的情况下,例如同步与std::mutex一起使用,可能会比获得更多线程所花费的性能更高。
  • 在某些情况下,瓶颈将不是CPU。例如,可能是磁盘/存储速度(包括页面/交换文件的速度),网络速度(包括远程服务器),甚至是内存带宽(除了NUMA感知的优化之外,它们比仅使用std::async要复杂得多)。这些中的多线程只会增加开销,但没有好处。

您应该尽可能先利用其他基本优化方法,例如向量reserve的大小,以避免不必要的分配和复制,例如resize,并使用vector[index] = a代替push_back

对于像abs(centre - input[i])这样简单的事情,您可能会从SIMD(单指令多数据)优化中获得更多改进。例如确保您正在使用诸如SSE2之类的任何优化进行编译,并且如果编译器没有适当地优化循环(我认为push_back可能会干扰,进行测试!),请对其稍加更改,以确保确实如此。 ,甚至可以显式使用向量指令(对于x86,请检出_mm_add_epi32等)。

答案 1 :(得分:2)

如果我正确理解这一点,那么您的单线程版本只需在给定的向量上调用dist_calculation,它将对向量进行一次遍历并产生一个新的向量,并将其返回。另一方面,您的多线程版本首先将输入数据的每半复制到两个单独的向量中。之后,它将为每个半启动dist_calculation,可能会在两个线程中启动。 std::async甚至可能没有使用单独的线程运行,因为您未指定启动策略。如果碰巧是使用单独的线程运行的,则由于std::thread构造函数的工作方式,您传递的向量将再次被复制。您必须使用例如std::reference_wrapper才能正确传递引用。想想那里发生了什么。将数据复制到这两个向量意味着您已经运行了整个数据一次,甚至还没有到达要启动一些线程来执行实际计算的地步,如果 if 线程是甚至会被用来作为开始。如果是这样,您将第二次复制数据。两次,复制都发生在单个线程中。因此,为了达到实际的多线程计算开始的目的(如果确实使用多个线程进行了计算),您的“多线程”版本基本上必须完成单线程版本总共要完成的工作的三倍。而且它必须在单个线程中完成所有这些工作……

您的dist_calculation不会修改原始数据。此外,各个线程将访问完全独立的数据部分。因此,根本不需要复制数据。只需给每个线程一个指针/迭代器指向它应处理的原始数据部分即可。另外,您预先知道将要生成多少个元素。因此,与其连续增长两个单独的输出向量,不如预分配一个向量以接收输出,并为每个线程提供另一个指针,该指针应指向输出向量的子范围。

最后,从C ++ 17开始,您可以将std::transform的并行版本与std::execution::par_unseq一起使用,并获得自动并行化(甚至可能是矢量化)的解决方案。 For example

template <typename ExecutionPolicy, typename ForwardIterator, typename OutputIterator>
void multi_dist_calculation(OutputIterator dest, ForwardIterator begin, ForwardIterator end, int center, ExecutionPolicy&& execution_policy)
{
    std::transform(std::forward<ExecutionPolicy>(execution_policy), begin, end, dest, [center](int x)
    {
        return std::abs(center - x);
    });
}

int main()
{
    constexpr int data_size = 1000000000;
    auto data = std::unique_ptr<int[]> { new int[data_size] };
    std::iota(&data[0], &data[0] + data_size, 0);

    auto result = std::unique_ptr<int[]> { new int[data_size] };
    multi_dist_calculation(&result[0], &data[0], &data[0] + data_size, 0, std::execution::par_unseq);  // multithreaded
    // multi_dist_calculation(&result[0], &data[0], &data[0] + data_size, 0, std::execution::seq);  // singlethreaded

    return 0;
}