C にクロージャのようなものを追加する Grand Central Dispatch
Snow Leopard には Grand Central Dispatch (以下 GCD) という機能がある。これは C と当然 C++, Objective-C, Objective-C++ にクロージャのようなものを追加し、マルチコアな CPU における計算の並列実行を簡単に書けるようにするらしい。
私は並列化には全然詳しくないのだけど、クロージャのようなものは気になるので調べてみた。
Block
Apple Developer Connection にある Introducing Blocks and Grand Central Dispatch は GCD の全体像をコードを交えつつわかりやすく説明している。この “blocks” というのがクロージャのようなものだ。
Blocks are similar to — but far more powerful than — traditional function pointers. The key differences are:
- Blocks can be defined inline, as “anonymous functions.”
- Blocks capture read-only copies of local variables, similar to “closures” in other languages
上記文章中ではこの一カ所でしか “closures” という語は使われていなく、あとは “blocks” と呼ばれている。これを使って 技術野郎の復讐 (書籍『ハッカーと画家』に「オタク野郎の復習」として収録されている) にあるアキュムレータを書いてみた。
#include <stdio.h> #include <Block.h> typedef int (^IntAccumulator)(int); IntAccumulator foo(int n) { __block int s = n; int (^bar)(int) = ^(int i){ return s += i; }; return Block_copy(bar); } int main(int argc, char* argv[]) { IntAccumulator f = foo(1); printf("%dn", f(1)); printf("%dn", f(1)); printf("%dn", f(2)); Block_release(f); return 0; }
このコードのうち、いままでの C になかった部分を簡単に説明する:
- block は関数ポインタと環境をあらわすデータで実現され、文法も関数ポインタを模し
*
を^
に代えたものになっている。 - 外のローカル変数は参照できるが変更は通常できない。グローバル変数と static 変数、さらに新たに導入された __block 修飾子のつけられたローカル変数は変更できる。
- block は効率上の理由でスタックに確保される。もとのスコープから脱したい場合は明示的に Block_copy して複製を作り Block_release で解放する必要がある。
実行結果は当然こうなる。
% cc -Wextra 1.c && ./a.out 2 3 5 %
まだ並列化は関係がなく、個々の関数呼び出しは順に実行されている。
並列化抜きの block はこれから使われていくんだろうか。callback として、スコープのより狭い関数ポインタとしては便利だ。実際、Apple は qsort_b, atexit_b といった block を引数にとる関数をいくつか提供している。一方、関数が関数を返すような使い方は微妙に思う。ただ Block_copy/Block_release の「だささ」は C では日常茶飯事だし、Objective-C 上なら NSAutoreleasePool やガベージコレクタの恩恵にもあずかれるらしい。
ちなみに、Apple は block の標準化を目指しているらしく、ISO の C のワーキンググループには 今年3月 に Apple’s extensions to C として提出されている。実装も、GCC では Apple の独自拡張あつかいだけど、LLVM のフロントエンドである Clang には とりこまれて いる。
並列化
さて、この block を並列に実行するために、GCD は dispatch queue というキューを提供する。キューの先から取り出された block はスレッドプールのなかのスレッド上で実行される。スレッドプールがキューを提供するのは 一般的 なようだ。
While initially inspired by the challenge of multicore computing, these actually solve a more general problem: how to efficiently schedule multiple independent chunks of work. GCD does this using four primary abstractions:
- block objects
- dispatch queues
- synchronization
- event sources
block と dispatch queue と、その同期のためのいくつかの方法、さらに非同期 IO などに block を callback として使う event とが、GCD の提供する主な抽象とされている。
GCD の特長は、スレッドが完全に隠蔽されることと、block の導入で、分割した処理を記述量もふくめ簡単に書けることのように思う。Apple は OS との密な連携もあげているけど、私は OS に詳しくないのであまりうなづけなかった。
次回予告
息切れしたのでここで一度おわり。次回はコンパイラ側のはなしをしたいです。