【问题标题】:Need thoughts on profiling of multi-threading in C on Linux需要思考在 Linux 上用 C 语言分析多线程
【发布时间】:2011-12-08 19:59:14
【问题描述】:

我的应用场景是这样的:我想评估在四核机器上处理相同数量的数据可以实现的性能增益。我有以下两种配置:

i) 1-Process:没有任何线程并处理来自 1M .. 1G 的数据的程序,而假设系统仅运行其 4 核中的单个核。

ii) 4-threads-Process:具有 4 个线程(所有线程执行相同操作)但处理 25% 的输入数据的程序。

在创建 4 线程的程序中,我使用了 pthread 的默认选项(即,没有任何特定的 pthread_attr_t)。我相信与 1-Process 配置相比,4-thread 配置的性能提升应该接近 400%(或介于 350% 和 400% 之间)。

我分析了创建线程所花费的时间,如下所示:

timer_start(&threadCreationTimer); 
pthread_create( &thread0, NULL, fun0, NULL );
pthread_create( &thread1, NULL, fun1, NULL );
pthread_create( &thread2, NULL, fun2, NULL );
pthread_create( &thread3, NULL, fun3, NULL );
threadCreationTime = timer_stop(&threadCreationTimer);

pthread_join(&thread0, NULL);
pthread_join(&thread1, NULL);
pthread_join(&thread2, NULL);
pthread_join(&thread3, NULL);    

由于输入数据大小的增加也可能增加每个线程的内存需求,所以提前加载所有数据绝对不是一个可行的选择。因此,为了保证不增加每个线程的内存需求,每个线程以小块读取数据,处理它,然后读取下一个块处理它等等。因此,我的线程运行的函数的代码结构是这样的:

timer_start(&threadTimer[i]);
while(!dataFinished[i])
{
    threadTime[i] += timer_stop(&threadTimer[i]);
    data_source();
    timer_start(&threadTimer[i]);
    process();
}
threadTime[i] += timer_stop(&threadTimer[i]);

变量dataFinished[i] 在接收并处理所有需要的数据时被进程标记为trueProcess() 知道什么时候这样做:-)

在主函数中,我正在计算 4 线程配置所花费的时间,如下所示:

execTime4Thread = max(threadTime[0], threadTime[1], threadTime[2], threadTime[3]) + threadCreationTime.

而性能增益的计算方式很简单

gain = execTime1process / execTime4Thread * 100

问题: 在 1M 到 4M 左右的小数据大小上,性能增益通常很好(在 350% 到 400% 之间)。然而,随着输入大小的增加,性能增益的趋势呈指数下降。它一直在下降,直到一些数据大小达到 50M 左右,然后稳定在 200% 左右。一旦达到这一点,即使是 1GB 的数据,它也几乎保持稳定。

我的问题是任何人都可以提出这种行为的主要原因(即,开始时性能下降,但后来保持稳定)?

以及如何解决这个问题的建议?

为了您的信息,我还调查了每个线程的threadCreationTimethreadTime 的行为,以了解发生了什么。对于 1M 的数据,这些变量的值很小,但是随着数据大小的增加,这两个变量都呈指数增长(但无论数据大小如何,threadCreationTime 应该保持几乎相同,threadTime 应该以对应于数据的速率增加正在处理中)。在继续增加直到 50M 左右之后,threadCreationTime 变得稳定,threadTime(就像性能下降变得稳定一样)和threadCreationTime 以恒定的速率持续增加,对应于要处理的数据的增加(这被认为是可以理解的)。

你认为增加每个线程的堆栈大小、进程优先级的东西或其他参数类型的调度程序的自定义值(使用pthread_attr_init)会有帮助吗?

PS:结果是在 Linux 的故障安全模式下以 root 运行程序时获得的(即,最小的操作系统在没有 GUI 和网络的情况下运行)。

【问题讨论】:

  • 你的CPU是什么型号的?
  • 最有可能在线程之间交叉污染缓存。您是否尝试过改变数据块的大小?您还应该在测量中包含数据加载,因为它可能是一个瓶颈,即 2 个内核可能会使您的内存总线饱和。 (另外,如果你还没有这样做,你应该把你的定时器放在不同的缓存行上。)
  • @Mats:处理器是 Intel(R) Core(TM)2 Quad CPU Q9950 @ 2.83GHz。不,我没有验证数据块的大小。好的,我将尝试更改数据块的大小。但是,我不明白您所说的缓存线是什么意思。如何将定时器缓存起来?
  • @Junaid:您的 threadTimer 数组元素应以 64 个字节分隔。这通常是缓存行的大小。
  • @Junaid:首先阅读这个 - en.wikipedia.org/wiki/MESI_protocol 和这个 - en.wikipedia.org/wiki/False_sharing

标签: linux multithreading performance pthreads multicore


【解决方案1】:

由于输入数据大小的增加也可能在 每个线程的内存需求,然后提前加载所有数据 绝对不是一个可行的选择。因此,为了确保不 为了增加每个线程的内存需求,每个线程读取 小块中的数据,处理它并读取下一个块处理它并 以此类推。

仅此一项就可能导致速度急剧下降

如果有足够的内存,读取一大块输入数据总是比读取小块数据快,尤其是从每个线程读取数据。任何来自分块(缓存效果)的 I/O 好处在您将其分解成小块时都会消失。即使分配一大块内存也比多次分配小块便宜得多。

作为健全性检查,您可以运行 htop 以确保在运行期间至少您的所有内核都已满。如果不是,您的瓶颈可能在您的多线程代码之外。

在线程内,

  • 由于线程过多而导致的线程上下文切换可能会导致次优加速
  • 正如其他人所提到的,由于不连续读取内存而导致的冷缓存可能会导致速度变慢

但是重新阅读您的 OP,我怀疑减速与您的数据输入/内存分配有关。您究竟是从哪里读取数据的?某种插座?您确定需要在线程中多次分配内存吗?

您的工作线程中的某些算法可能不是最佳的/昂贵的。

【讨论】:

    【解决方案2】:

    你的线程是从创建开始的吗?如果是这样,那么会发生以下情况:

    当你的父线程正在创建线程时,已经创建的线程将开始运行。当你点击timerStop(ThreadCreation timer)时,这四个已经运行了 一段时间。所以threadCreationTimethreadTime[i] 重叠

    现在,您不知道自己在测量什么。这不会解决您的问题,因为显然您遇到了问题,因为 threadTime 不会线性增加,但至少您不会添加重叠时间。

    要了解更多信息,您可以使用perf tool(如果您的发行版上提供)。 例如:

    perf stat -e cache-misses <your_prog>
    

    看看两线程版本、三线程版本等会发生什么......

    【讨论】:

    • 这个问题仍然存在,即使我不考虑threadCreationTime 只考虑threadTime[i](现在已按照上面关于缓存行的建议将其拆分为单独的变量)。遵循该建议后,结果有所改善,但现在瓶颈已经转移。也就是说,在 1M 数据上,性能增益很好。但是在 2M 上它会下降,然后即使是 1G 也保持不变。我还将尝试您的建议以查看缓存未命中。您认为 valgrind 可以提供帮助吗?我也在考虑尝试英特尔 vTune 分析器。
    • @Junaid : cache-misses 只是一个例子,还有很多值得关注的地方。
    猜你喜欢
    • 2011-01-30
    • 2011-01-02
    • 2018-10-04
    • 2018-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多