WODIのOCamlにはなぜかParmapがない。LinuxのParmapのソースを見るとCのソースがある。この程度の機能で何故Cを使うのかはわからないが、Windowsに移植できない何かがあるのだろうか。Cソースの中身までは追ってない。
そんなわけで、簡易並行mapを書く。
module Para = struct
exception ERROR_PARA
let map fn ls =
let results = Array.make (List.length ls) None in
(* go *)
let thrs =
List.mapi
(fun i s ->
Thread.create
(fun s ->
results.(i) <- (Some (fn s))
)
s
)
ls
in
(* wait *)
List.mapi
(fun i thr ->
let _ = Thread.join thr in
match results.(i) with
| Some r -> r
| _ -> raise ERROR_PARA
)
thrs
end
OCamlのスレッドは複数コアを使わない。複数コアを使うOCamlの実装はいまだ実験段階のようだ。OCamlのスレッドはOSの機能を使ってないということなのか? 昔、Modula-2は言語レベルでスレッドを実現していたが、そういうものなのか? マニュアルにはOS提供のスレッドって書いてあるんだけどねぇ。APIを使ってわざわざコア1つで動くように設定しているのだろうか。リソースの競合等何か問題があるんだろうか。
現状複数コアを使うには、上記のようなPara.mapに渡す関数(スレッド)にUnix.create_processで別プロセスを起動する位しか方法が思いつかない。実験はしてみたが、有効かつ説明のしやすい応用はあまりない。「プログラミングF#」では並行処理の説明は画像ファイルのサムネールの生成だった。いやまぁ、それでもいいんだろうけど。
複数コア云々は実際のところどうなんだろうと、上記Para.mapをLinuxでpthreadを使ったものにしてみる。
module Para = struct
exception ERROR_PARA
type pthread
external para_create_thread: int -> pthread = "para_create_thread"
external para_thread_join: pthread -> unit = "para_thread_join"
external para_setup_callback: string -> unit = "para_setup_callback"
let map fn ls =
let params = Array.of_list ls in
let results = Array.make (List.length ls) None in
let thread_callback i =
results.(i) <- Some (fn params.(i))
in
let thread_callback_name = "para_thread_callback" in
let _ = Callback.register thread_callback_name thread_callback in
let _ = para_setup_callback thread_callback_name in
(* go *)
let thrs =
List.mapi
(fun i s ->
para_create_thread i
)
ls
in
(* wait *)
List.mapi
(fun i thr ->
let _ = para_thread_join thr in
match results.(i) with
| Some x -> x
| _ -> raise ERROR_PARA
)
thrs
end
Cの記述は以下のようになる。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <caml/mlvalues.h>
static void *threadCallback = NULL;
static void *paraThreadFunc(void *param) {
int number = (int)(long)param;
caml_callback(*((value *)threadCallback), Val_int(number));
return NULL;
}
CAMLprim value para_setup_callback(value s) {
char *name = String_val(s);
threadCallback = (void *)(long)caml_named_value(name);
return Val_unit;
}
CAMLprim value para_create_thread(value n) {
int number = Int_val(n);
pthread_t *thread = malloc(sizeof (pthread_t));
pthread_create(thread, NULL, paraThreadFunc, (void *)(long)number);
return (value)thread;
}
CAMLprim value para_thread_join(value thr) {
pthread_t *thread = (pthread_t *)thr;
void *result;
pthread_join(*thread, &result);
free(thread);
return Val_unit;
}
で、これがどう動くかというと、極めて不安定。シングルコアだと旨く行くのかというと、そんなマシンが近辺に無いので試せない。EeePC 901もHyper-Threadingでマルチコアに見えちゃう。昨年末に壊れたファイルサーバがシングルコアではあったがまぁ、壊れたわけで。Eee PC 901のBIOSでHTを切れるかもしれない。
この方法でpthreadを使ったらシングルでもマルチでもダメなんじゃないかという気はするがともかく不明。不安定なので複数コアを使うかどうかも未確認。
2015.03.17
OSTRACISM CO.
OSTRA / Takeshi Yoneki