Haskell 启用pthread时C FFI回调的运行时性能下降
我对GHC运行时在C FFI调用Haskell函数时使用Haskell 启用pthread时C FFI回调的运行时性能下降,haskell,concurrency,ffi,Haskell,Concurrency,Ffi,我对GHC运行时在C FFI调用Haskell函数时使用threaded选项的行为感到好奇。我编写代码来测量基本函数回调的开销(见下文)。虽然函数回调开销以前就已经存在,但我很好奇,在C代码中启用多线程时(即使对Haskell的函数调用总数保持不变),我观察到的总时间会急剧增加。在我的测试中,我使用两种场景调用了Haskell函数f5M次(GHC 7.0.4,RHEL,12核盒,代码后面的运行时选项): C中的单线程create_线程函数:调用f5M次-总时间1.32s Ccreate_th
threaded
选项的行为感到好奇。我编写代码来测量基本函数回调的开销(见下文)。虽然函数回调开销以前就已经存在,但我很好奇,在C代码中启用多线程时(即使对Haskell的函数调用总数保持不变),我观察到的总时间会急剧增加。在我的测试中,我使用两种场景调用了Haskell函数f
5M次(GHC 7.0.4,RHEL,12核盒,代码后面的运行时选项):
- C中的单线程
函数:调用create_线程
5M次-总时间1.32sf
- C
function中的5个线程:每个线程调用create_threads
1M次-因此,总数仍然是5M-总时间7.79sf
create_threads
中使用1个线程运行(上面的代码可以做到这一点)-我关闭了并行gc进行测试:
$ ./t +RTS -s -N5 -g1
INIT time 0.00s ( 0.00s elapsed)
MUT time 1.04s ( 1.05s elapsed)
GC time 0.28s ( 0.28s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 1.32s ( 1.34s elapsed)
%GC time 21.1% (21.2% elapsed)
使用5个线程运行(请参阅上文t.hs
的main
函数中关于如何编辑5个线程的第一条注释):
我希望能够深入了解为什么在create_线程中使用多个pthread会降低性能。我首先怀疑是并行GC,但在上面的测试中我关闭了它。给定相同的运行时选项,多个pthread的MUT时间也会急剧增加。所以,这不仅仅是GC
此外,对于这种情况,GHC 7.4.1中是否有任何改进
我不打算经常从FFI调用Haskell,但在设计Haskell/C多线程库交互时,这有助于理解上述问题。我认为这里的关键问题是,GHC运行时如何安排C回调到Haskell?虽然我不确定,但我怀疑所有的C回调都是由最初进行外部调用的Haskell线程处理的,至少在ghc-7.2.1之前(我正在使用它) 这就解释了你(和我)在从1个线程移动到5个线程时所看到的巨大减速。如果这五个线程都回调到同一个Haskell线程中,那么在该Haskell线程上将有大量的争用来完成所有回调 为了测试这一点,我修改了您的代码,以便Haskell在调用
create\u threads
之前分叉一个新线程,并且create\u threads
每次调用只生成一个线程。如果我是正确的,每个操作系统线程将有一个专用的Haskell线程来执行工作,因此争用应该会少很多。尽管这仍然是单线程版本的两倍,但它比最初的多线程版本要快得多,这为这一理论提供了一些证据。如果我使用+RTS-qm
关闭线程迁移,那么差别就会小得多
由于Daniel Fischer报告了ghc-7.2.2的不同结果,我希望版本会改变Haskell安排回调的方式。也许名单上的人可以提供更多的信息;我在7.2.2或7.4.1的发行说明中没有看到任何可能的结果。我在7.2.2中得到了小得多的减速,单线程的总时间为1.42s(1.42s),而四线程的总时间为2.58s(1.86s)(因为我只有两个物理内核和四个线程,我认为要求五个线程是没有意义的)。因此,在7.4.1中可能会更好。@DanielFischer,感谢您对7.2.2性能的介绍。也许我应该在RHEL上下载并编译7.4.1RC,看看它的性能如何。不过,这是相当耗时的工作。我相信他们已经为候选版本预先构建了二进制文件。我想那不会太费时。或者香草二进制文件在RHEL上不起作用吗?@DanielFischer,香草二进制文件在RHEL5上不起作用,因为它的glibc版本比编译二进制文件的版本旧。谢谢您的反馈。你的理论看起来很有道理。似乎有某种争论在进行。我也怀疑回调是单线程的。你所描述的符合观察结果。我昨天还通过电子邮件向ghc用户列表发送了邮件。在我的测试中验证了您的观察结果。如果我将每个pthread映射到一个Haskell线程进行回调(在7.0.4中),那么运行时的伸缩性很好。将您的解决方案标记为答案。
#include <pthread.h>
#include <stdio.h>
typedef void(*FunctionPtr)(int);
/** Struct for passing argument to thread
**
**/
typedef struct threadArgs{
int threadId;
FunctionPtr fn;
int length;
} threadArgs;
/* This is our thread function. It is like main(), but for a thread*/
void *threadFunc(void *arg);
void create_threads(FunctionPtr*,int*,int);
#include "mt.h"
/* This is our thread function. It is like main(), but for a thread*/
void *threadFunc(void *arg)
{
FunctionPtr fn;
threadArgs args = *(threadArgs*) arg;
int id = args.threadId;
int length = args.length;
fn = args.fn;
int i;
for (i=0; i < length;){
fn(i++); //call haskell function
}
}
void create_threads(FunctionPtr* fp, int* length, int numThreads )
{
pthread_t pth[numThreads]; // this is our thread identifier
threadArgs args[numThreads];
int t;
for (t=0; t < numThreads;){
args[t].threadId = t;
args[t].fn = *(fp + t);
args[t].length = *(length + t);
pthread_create(&pth[t],NULL,threadFunc,&args[t]);
t++;
}
for (t=0; t < numThreads;t++){
pthread_join(pth[t],NULL);
}
printf("All threads terminated\n");
}
$ ghc -O2 t.hs mt.c -lpthread -threaded -rtsopts -optc-O2
$ ./t +RTS -s -N5 -g1
INIT time 0.00s ( 0.00s elapsed)
MUT time 1.04s ( 1.05s elapsed)
GC time 0.28s ( 0.28s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 1.32s ( 1.34s elapsed)
%GC time 21.1% (21.2% elapsed)
$ ./t +RTS -s -N5 -g1
INIT time 0.00s ( 0.00s elapsed)
MUT time 7.42s ( 2.27s elapsed)
GC time 0.36s ( 0.37s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 7.79s ( 2.63s elapsed)
%GC time 4.7% (13.9% elapsed)