C にクロージャのようなものを追加する Grand Central Dispatch

2009-09-21 23:34

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 に詳しくないのであまりうなづけなかった。

次回予告

息切れしたのでここで一度おわり。次回はコンパイラ側のはなしをしたいです。

Leave a Reply