【问题标题】:why does having more than one thread(parallel processing) in some specific cases degrade performance?为什么在某些特定情况下拥有多个线程(并行处理)会降低性能?
【发布时间】:2019-05-27 17:48:45
【问题描述】:

我注意到为某些代码运行多个线程比运行一个线程要慢得多,我真的很想知道为什么,有人可以帮忙吗?

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

    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);
    }

【问题讨论】:

  • 如果线程不相互干扰(任务完全独立),则检查实际工作负载是否明显大于启动线程的开销。不清楚你到底在做什么,所以很难说到底是什么问题。
  • 出现这种情况的原因有很多。一些更通用的包括凝视比您的硬件更多的线程,或者m_signularity_checker 做一些只能按顺序运行的事情(比如分配内存或受互斥体保护的部分)。如果没有更多代码显示您正在尝试的内容,我们无法判断您的具体问题是什么。
  • “为什么在某些特定情况下拥有多个线程(并行处理)会降低性能?” - 很多很多的原因。有些是; 1)创建和销毁线程的开销超过了每个线程中并行完成的工作量。 2)线程需要大量同步并有效地串行运行,因此线程只会增加开销。 3) 错误共享​​> 和线程严重影响性能时的类似问题。 4)使用比可用硬件线程更多的活动软件线程过度使用系统会导致执行上下文切换的大量开销。 5) 很多,很多更多。
  • 另一个可能的原因是测量不正确。
  • 我编辑了帖子以包含缺少的功能@FrançoisAndrieux 我认为你可能是对的,你知道如何优雅地测量/确保这一点(分别测量处理和上下文切换时间)?

标签: c++ multithreading


【解决方案1】:

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

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

总之:线程不是能自动提高程序性能的灵丹妙药。


关于您的计划,鉴于您展示的摘录,我们无法排除上述任何潜在问题。

避免或发现上述问题的一些技巧:

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

【讨论】:

  • 优秀的理由清单!我建议再提两件事:(a) 对系统资源的并发访问可能比对同一资源的顺序访问慢得多。例如,同时读取两个文件可能比一个接一个地读取要慢一些。 (b) 资源匮乏。同时处理 N 个任务可能会占用 N 倍以上的资源。这可能会导致各种事情:超出缓存容量和缓存未命中,将一些页面交换到磁盘。
  • 关于“发现问题”,您可能需要提及ThreadSanitizer 以及使用分析器(如perf)。
  • 非常感谢您的回答,伙计,我编辑了问题以包含丢失的代码,无论如何,我认为这可能是错误的分享点。你对调试我的情况的工具/方法有什么建议吗(我相信还会有更多)?
【解决方案2】:

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

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

因此,您可能希望自己开始对此进行分析。每行处理完成了多少工作,您总共设置了多少线程?

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 2013-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-11
    • 2016-01-27
    相关资源
    最近更新 更多