为什么在某些特定情况下具有多个线程(并行处理)会降低性能?

时间:2019-05-27 17:48:46

标签: c++ multithreading

我注意到为一个代码运行一个线程要比拥有一个线程慢得多,而且我一直在努力地想知道为什么,有人可以帮忙吗?

代码说明: 我有时有一个非常大的数组,我需要以并行的方式处理部分数组以进行优化,每一行的“一部分”都会在特定线程中循环并进行处理,现在我注意到,如果我只是有一个“部分”,即整个数组和贯穿其中的单个工作线程比将数组划分并作为具有不同线程的单独子数组处理时要快得多。

    bool m_generate_row_worker(ull t_row_start,ull t_row_end)
    {
        for(;t_row_start<t_row_end;t_row_start++)
            {
                m_current_row[t_row_start]=m_singularity_checker(m_previous_row[t_row_start],m_shared_random_row[t_row_start]);

            }
        return true;
    }

    ...
    //code
    ...
    for(unsigned short thread_indx=0;thread_indx<noThreads-1;thread_indx++)
    {
        m_threads_array[thread_indx]=std::thread(
                m_generate_row_worker,this,
                thread_indx*(m_parts_per_thread),(thread_indx+1)*(m_parts_per_thread));
    }
    m_threads_array[noThreads-1]=std::thread(m_generate_row_worker,this,
            (noThreads-1)*(m_parts_per_thread),std::max((noThreads)*(m_parts_per_thread),m_blocks_per_row));
    //join
    for(unsigned short thread_indx=0;thread_indx<noThreads;thread_indx++)
    {
        m_threads_array[thread_indx].join();
    }
//EDIT 
    inline ull m_singularity_checker(ull t_to_be_ckecked_with,ull 
    t_to_be_ckecked)
    {
            return (t_to_be_ckecked & (t_to_be_ckecked_with<<1)
             & (t_to_be_ckecked_with>>1) ) | (t_to_be_ckecked_with & 
    t_to_be_ckecked);
    }

2 个答案:

答案 0 :(得分:3)

  

为什么在某些特定情况下具有多个线程(并行处理)会降低性能?

  • 因为创建线程有开销。如果要执行的任务仅具有很小的计算成本,那么创建多个线程的成本将超过并行处理所节省的时间。当创建的线程远远多于CPU内核时,尤其如此。
  • 因为许多算法不容易分成独立的子任务。对其他线程的依赖关系需要同步,这种同步的开销在某些情况下可能会比并行处理节省的时间更多。
  • 由于在程序设计不良的情况下,同步可能导致所有任务都按顺序处理,即使它们在单独的线程中也是如此。
  • 因为(取决于CPU体系结构)有时可以正确地实现,并且看似独立的任务具有有效的依赖性,因为它们在相同的内存区域上运行。更具体地说,当一个线程写入一块内存时,在同一高速缓存行上运行的所有线程必须同步(CPU会自动为您执行此操作)以保持一致。高速缓存未命中的成本通常比并行处理节省的时间高得多。此问题称为“虚假共享”。
  • 因为有时引入多线程会使程序更加复杂,这使得编译器/优化器更难以利用指令级并行性。
  • ...

结论:线程并不是自动提高程序性能的灵丹妙药。


关于您的程序,鉴于您所摘录的内容,我们无法排除上述任何潜在问题。

避免或发现上述问题的一些提示:

  • 创建的线程数不要超过内核数,从而减少了预期阻塞的线程数(等待输入,磁盘等)。
  • 仅将多线程用于计算量大的问题(或在线程阻塞时进行工作,但这可以使用异步I / O和协程更有效地解决)。
  • 除非经过特别设计来处理一个线程,否则不要将多个线程的I / O(或做得尽可能少)放入单个设备(磁盘,NIC,虚拟终端等)中。
  • li>
  • 最小化线程之间的依赖关系数量。考虑所有对可能导致同步的全局事物的访问,并避免它们。例如,避免内存分配。请记住,对标准容器进行的操作等操作都会分配内存。
  • 使不同线程(彼此不相邻的数组小元素)所接触的内存保持远离。如果处理数组,请将其分成连续的块,而不是在第(线程数)个元素中剥离一个元素。在某些极端情况下,将额外的副本复制到特定于线程的数据结构中,然后再进行最终加入可能是有效的。
  • 如果您已尽力而为,并且多线程措施的速度变慢,请考虑这也许不是解决问题的好方法。

答案 1 :(得分:1)

使用线程并不总是意味着您将完成更多的工作。例如,使用2个线程并不意味着您将在一半时间内完成任务。设置线程有开销,具体取决于线程和操作系统的数量等。线程之间发生了多少上下文切换(保存线程堆栈/ reg并加载下一个线程/ regs-所有这些加起来)。在某个时候添加更多线程将开始减慢您的程序速度,因为将花费更多的时间在线程之间切换/设置线程启动/关闭,然后才能完成工作。所以你可能是这个的受害者。

如果您有100个非常小的项目(如1条指令)要做,那么由于您现在有("many instructions" + 1) x 100的工作要做,因此可以保证100个线程的速度变慢。 “许多指令”是设置线程并最终清除它们并在它们之间切换的工作。

因此,您可能想开始自己分析它。.处理每一行要完成多少工作,总共要设置多少个线程?

一种非常粗略但快速/简单的测量方法是仅花费时间单独处理一行(例如,使用std::chrono函数在处理一行开始时测量时间,然后然后花点时间查看总时间,然后也许对整个表进行相同的测试,以了解总时间。

如果您发现单个行花费的时间很少,那么您可能不会从线程中获得太多好处...您最好将表拆分成等于核心数量的工作块您的CPU拥有,然后开始更改线程数(+/-)来找到最佳位置。仅基于行数创建线程是一个糟糕的选择-您真的想设计它以最大化每个核心(例如)。

因此,如果您有4个内核,则可能首先将工作分成4个线程开始。然后用8进行测试,如果尝试16更好,如果尝试12不好,等等...

另外,您在不同的PC上可能会得到不同的结果...