関数プログラミングのアプローチ (5)

非シングルスレッド化

前回、プログラムを関数的に書き換えましたが、プログラムはシングルスレッド化されていたために、関数プログラミングのメリットを生かせていませんでした。ここでは、これを非シングルスレッド化する方法について検討します。
シングルスレッド化されている原因は、cesaro_test()が乱数状態オブジェクトを受け取り、更新した乱数状態オブジェクトを返しているからでした。そこで、cesaro_test()を次のように書き換えます。

def cesaro_test ((x,y)):
    return 1 == gcd(x, y)

このように、乱数を2つ受け取って、それを元にテストするように書き換えることで、テストそのものが乱数状態オブジェクトを受け取る必要がなくなります。
さらに、乱数を生成するところを、乱数状態オブジェクトから直接生成するのではなく、次のような関数を作って、あらかじめ乱数列を作るようにします。

def pairlist(times, rs):
    ls = []
    for i in xrange(0,times):
        x, rs = rand(rs)
        y, rs = rand(rs)
        ls.append((x,y))
    return ls

pairlist(10000, RandState.new())

プログラムの本体は次のようになります。

def main ():
    print math.sqrt(6.0 / estimate(cesaro_test, pairlist(10000, RandState.new())))

def estimate (test, rands):
    x, y = 0.0, 0.0
    for b in map(test, rands):
        if b: x = x + 1
        y = y + 1
    return x / y

ここで注目すべきは、乱数列をcesaro_test()に適用する部分が「map(test, rands)」という形で表現されているところです。このように、乱数列をあらかじめ作るようにすることで、シングルスレッド化されている部分をpairlist()の部分に局所化し、テストそのものの実行を非シングルスレッド化することに成功しました。
map関数は、リストの要素を関数に逐次的に適用する関数ですが、この並行化版のpmapという関数が存在すると仮定すると、次のように書き換えるだけで、マルチスレッドで実行するプログラムに書き換えることができます。

def estimate (test, rands):
    x, y = 0.0, 0.0
    for b in pmap(test, rands):
        if b: x = x + 1
        y = y + 1
    return x / y

このプログラムとひとつ前の逐次実行版のプログラムとの違いは、たった一文字です。このような書き換えが容易にできることは、関数プログラミングの大きなメリットです。特に、テスト関数の実行のコストが高く、pmap()の実装が分散コンピューティング環境を前提としている場合には、このメリットはさらに大きなものとなります。