需要关于在Linux上使用C语言进行多线程分析的思路

7
我需要翻译的内容如下:

我的应用场景是这样的:我想评估在四核机器上处理相同数据量时可以获得的性能提升。我有以下两个配置:

i)1进程:一个没有任何线程的程序,从1M到1G处理数据,假设系统只运行其4核中的一个。

ii)4线程进程:一个带有4个线程(所有线程执行相同的操作),但只处理25%的输入数据的程序。

在我的程序中创建4个线程时,我使用了pthread的默认选项(即没有特定的pthread_attr_t)。我认为与1进程配置相比,4线程配置的性能增益应该更接近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身份运行程序时获得结果(即最小化OS运行而没有GUI和网络设置)。


4
很可能是线程之间的缓存交叉污染。你尝试过改变数据块的大小吗?你还应该包括数据加载在你的测量中,因为它可能是瓶颈,即2个核心可能会饱和你的内存总线。(此外,如果你还没有这样做,应该将计时器放在不同的缓存行上。) - Mats
1
@Junaid:你的threadTimer数组元素应该每隔64个字节分开一次。这通常是缓存行的大小。 - Tudor
这个程序涉及到磁盘I/O吗?如果有的话,你正在读取和/或写入哪种磁盘?(例如硬盘、固态硬盘、RAID等)I/O性能可能是一个重要因素,有时甚至比CPU性能更重要... - Jeremy Friesner
@JeremyFriesner:同时也没有将任何数据写入磁盘。 - user1082170
2
@Junaid:先看看这个 - http://en.wikipedia.org/wiki/MESI_protocol 和这个 - http://en.wikipedia.org/wiki/False_sharing。 - user405725
显示剩余10条评论
2个回答

2
由于输入数据的增加可能会增加每个线程的内存需求,因此提前加载所有数据绝对不是可行的选项。为了确保不增加每个线程的内存需求,每个线程读取小块数据进行处理,然后读取下一块数据进行处理,以此类推。但这样做可能会导致速度显著降低。
如果有足够的内存,读取一个大块输入数据总是比读取小块数据快得多,特别是从每个线程读取。任何I/O缓存效果都会消失当你将其分成块。即使分配一个大块内存也比分配多次小块内存便宜得多。
可以运行htop进行检查,确保在运行期间至少所有核心都被充分利用。如果没有,你的瓶颈可能在你的多线程代码之外。
在线程内部,
- 由于许多线程而导致的线程上下文切换可能会导致次优的加速比 - 如其他人所述,由于未连续读取内存而导致的冷缓存可能会导致减速
但是重新阅读您的原始帖子,我怀疑减速与数据输入/内存分配有关。您从哪里读取数据?某种套接字吗?您确定需要在线程中多次分配内存吗?
您的工作线程中的某些算法可能是次优/昂贵的。

0

你的线程是在创建时启动的吗?如果是这样,那么以下情况将发生:

当父线程正在创建线程时,已经创建的线程将开始运行。当你按下timerStop(ThreadCreation定时器)时,四个线程已经运行了一段时间。因此,threadCreationTimethreadTime [i] 重叠。

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

如果可用于你的版本,你可以使用perf工具来获取更多信息。例如:

perf stat -e cache-misses <your_prog>

并查看使用两个线程版本、三个线程版本等会发生什么...


即使我不考虑threadCreationTime,只考虑threadTime[i](现在已经按照上面关于缓存行的建议拆分为单独的变量),这个问题仍然存在。在遵循该建议后,结果有所改善,但是瓶颈现在已经转移。也就是说,在1M数据上性能提升很好。但是在2M上下降,然后在1G上保持不变。我还将尝试您的建议以查看缓存未命中。您认为valgrind可以帮助吗?我还考虑尝试Intel vTune分析器。 - user1082170
@Junaid:cache-misses 只是一个例子,还有很多计数器需要观察。 - shodanex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接