C FFIがHaskell関数をコールバックした場合に、threaded
オプションを使用してGHCランタイムの動作が不思議です。私は基本的な関数コールバックのオーバーヘッドを測定するコードを書いています(下記)。関数コールバックのオーバーヘッドはすでにdiscussedでしたが、Haskellへの関数呼び出しの総数が同じであっても、Cコードでマルチスレッドが有効になったときに観測された合計時間が急激に増加しているのが不思議です。私のテストでは、私はHaskellの関数と呼ばれる2つのシナリオ(GHC 7.0.4、RHEL、12コアボックス、コードの後に以下のランタイム・オプション)を使用してf
5M回:C create_threads
機能でpthreadが有効になっているときにC FFIコールバックの実行時パフォーマンスが低下する
シングルスレッド:コールを
f
5M時間 - 合計時間Ccreate_threads
関数で1.32s5スレッド:各スレッドの呼び出し
f
1M時間 - ので、合計は依然として5Mである - 7.79s
コード以下合計時間 - ハスケル以下のコードは、シングルスレッドCコールバックのためのものである - コメントは5スレッド試験のためにそれを更新する方法について説明:
t.hs:
{-# LANGUAGE BangPatterns #-}
import qualified Data.Vector.Storable as SV
import Control.Monad (mapM, mapM_)
import Foreign.Ptr (Ptr, FunPtr, freeHaskellFunPtr)
import Foreign.C.Types (CInt)
f :: CInt ->()
f x =()
-- "wrapper" import is a converter for converting a Haskell function to a foreign function pointer
foreign import ccall "wrapper"
wrap :: (CInt ->()) -> IO (FunPtr (CInt ->()))
foreign import ccall safe "mt.h create_threads"
createThreads :: Ptr (FunPtr (CInt ->())) -> Ptr CInt -> CInt -> IO()
main = do
-- set threads=[1..5], l=1000000 for multi-threaded FFI callback testing
let threads = [1..1]
l = 5000000
vl = SV.replicate (length threads) (fromIntegral l) -- make a vector of l
lf <- mapM (\x -> wrap f) threads -- wrap f into a funPtr and create a list
let vf = SV.fromList lf -- create vector of FunPtr to f
-- pass vector of function pointer to f, and vector of l to create_threads
-- create_threads will spawn threads (equal to length of threads list)
-- each pthread will call back f l times - then we can check the overhead
SV.unsafeWith vf $ \x ->
SV.unsafeWith vl $ \y -> createThreads x y (fromIntegral $ SV.length vl)
SV.mapM_ freeHaskellFunPtr vf
mt.h:
#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);
MT。 C:
#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 7.0.4、それはGHCで使用する場合には、GCC 4.4.3):
つのスレッドを実行$ ./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.hs
のmain
関数で最初のコメントを参照してください。私はテストのために、並列GCをオフ - (上記のコードは、それを行います)create_threads
1つのスレッドで実行
$ ghc -O2 t.hs mt.c -lpthread -threaded -rtsopts -optc-O2
5つのスレッド)のためにそれを編集する方法について:
$ ./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)
私はパフォーマンスがcreate_threadsで複数のpthreadに低下する理由についての洞察に感謝します。最初に並列GCの疑いがありましたが、上記のテストではオフにしました。同じランタイムオプションを指定すると、複数のpthreadに対してMUT時間も急激に増加します。したがって、GCだけではありません。
また、このようなシナリオではGHC 7.4.1の改良点はありますか?
HaskellをFFIから頻繁に呼び戻すつもりはありませんが、Haskell/Cのマルチスレッドライブラリの相互作用を設計する際に、上記の問題を理解するのに役立ちます。
シングルスレッドの合計時間が1.42秒(1.42秒経過)、スレッドが4つの2.58秒(1.86秒経過)の合計時間が7.2.2と非常に小さくなりました(4つのスレッドで2つの物理コアしかないため、私は5つのスレッドを求めるのは無意味だと思った)。だから、おそらく7.4.1で良いでしょう。 –
@DanielFischer、7.2.2のパフォーマンスのポインタに感謝します。 RHELで7.4.1RCをダウンロードしてコンパイルして、それがどのように動作するかを確認する必要があるかもしれません。それはかなり時間のかかる作業です。 – Sal
私は、リリース候補者にもバイナリをあらかじめビルドしていると思います。それはあまり時間がかかりませんと思います。あるいは、バニラバイナリはRHEL上で動作しませんか? –