2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

Ruby 向け並列化機構 Guild の試作○笹田 耕一1,a)

概要：
我々は、プログラミング言語 Ruby の次期メジャーリリースである Ruby 3 において、安全に並行・並列
プログラミングを可能にする機構である Guild を開発している。スレッドプログラミングに代表される、メモリ共有型の並行プログラミングは、間違いの無いプログラムを記述することが困難であるという仮説をもとに、Guild はメモリや資源の共有をある程度制限するモデルを目指している。本発表では、この目標を実現するために、Ruby の言語仕様をどのように拡張するのか、またどのように実装すべきかについて、試作をもとに議論する。

Preliminary implementation of Guild,a parallel execution mechanism for Ruby
Koichi Sasada1,a)Abstract:
We are developing Guild, a mechanism to support making safe concurrent and parallel programs for Ruby
3, the next major release of programing language Ruby. We assume that shared memory type concurrent
programming model like thread programming is diﬃcult to make correct programs. Based in this assumption, Guild will restrict sharing memory or reources. In this presentation, we will discuss how to extend thelanguage specification of Ruby and how to implement Guild, with our preliminary implementation.

1. はじめに理可能にすることであり、我々は過去に試作を行ったこともある [3]。しかし、スレッドプログラミングは難しい。と

本報告では、以前コンセプトを発表した Ruby 3 向けのくに、スレッド安全なプログラムを作成するのは難しい。

並行処理のための抽象化である Guild [2] の試作について

Ruby の良さは「楽しくプログラミングできる」ことであ述べる。

り、並列スレッドはそのスローガンからはほど遠い。

我々は、Ruby を実行する Ruby 処理系（以下、CRuby）

そこで、並行並列処理のための抽象化を別に用意するこを開発している。最新版は Ruby 2.5.1 であり、2018 年末とで、プログラミングしやすい記述を目指している。このに Ruby 2.6.0 のリリースを予定している。現在、Ruby の目標を実現するために、現在検討中の仕組みが Guild であ次のメジャーバージョンである Ruby 3 の仕様についてる。ユーザは Guild を複数作成し、それらは並行並列に実議論しており、静的解析による実行前のエラー検出、JIT行される。しかし、スレッドとは異なり、共有メモリモデ

コンパイルなどによる高速化、よりよい並行並列処理のサ

ルをとらないため、データレースなどの問題が起こらない。

ポートの三つを大きな目標としている。

ただ、完全に非共有とするのではなく、制約をつけること

並行並列処理を Ruby で記述可能にするために単純な方
針は、すでに Ruby がサポートしているスレッドを並列処
1a)

クックパッド株式会社
ko1@cookpad.com

で共有可能なオブジェクトも導入する。

Guild の背景やコンセプトは前報告 [2] で述べているた
め、適宜参照されたい。本報告では読みやすさのため、前報告と多少重複する内容を記述している。本報告では、実12018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

装を進めた上で、概念を整理した点と、いくつかの実験結とが多く、デバッグが困難であると言うこともスレッドプ

果、そして残る多くの課題について述べる。

ログラミングの問題をより深刻にしている。

2 つ目の言語デザイン上の理由は、スレッドプログラミ
1.1 本提案の背景

ングは難しいといっても、並列処理を伴わないスレッドプ

現在、Ruby の安定版バージョンである Ruby 2.5 では、ログラミングは、伴う場合よりも簡単である、という理由

並行処理のために、プリエンプティブに処理を切り替えるである。Ruby 2.5 でのスレッドの実装では、ジャイアント

スレッドと、アプリケーションプログラマが明示的に処理

ロックの受け渡しタイミングが、プログラムの特定の箇所を切り替えるコルーチンをサポートしており、それぞれ（具体的には、メソッドから返るタイミングや、前方ジャン

Thread と Fiber クラスとして利用できる。スレッドは他プを行うタイミング）であると限定している。そのため、の多くのプログラミング言語でのスレッド同様、共有メモ

例えば文字列や配列の中身を破壊的に操作しているタイミ

リモデルで動作し、スレッド間で簡単にオブジェクトを共ングでのスレッド切り替えは考えなくてよく、その点での有することができる。

スレッド安全が保障できている。本来は同期処理を導入す

並列計算機上で複数の処理を同時に実行するために、多る必要がある場合であっても、この理由により、「たまたくのプログラミング言語では複数スレッドを同時実行させ

ま」動いているプログラムは、一定数あるのではないかとることができる。しかし、Ruby 2.5 のスレッドは、イン

筆者は予想している。問題は早期に発見すべきであるため

タプリタに 1 つ存在するジャイアントロックをもつ、たかこれは欠点だ、と言うこともできるが、書き殴りのプログ

だか 1 つのスレッドのみ実行可能となっている。そのたラムにおいて、そこそこまともに動くというのはメリット

め、スレッドを使う限り、通常の方法では並列プログラム

であろう。

を Ruby で記述することはできない。

現在の Ruby 2.5 の方針は、前報告から引用となるが、次ジャイアントロックにより、スレッドを用いた並列処理のようにいうことができる。
「このように、Ruby プログラを書けないようにしているのは、実装上の理由と言語デザ

マおよび Ruby インタプリタ開発者に優しい設計となってイン上の理由の 2 つがある。

おり、並列処理によるプログラムの性能改善を行ないたい

実装上の理由としては、処理系をスレッドセーフに記述者にとっては冷たい設計となっている。」

することの困難さにある。高性能な並列処理を実現するためには、スレッド間で共有するデータについて、適切な単1.2 解決方針と目標

位で細粒度同期を必要とするが、過不足無く細粒度同期を我々は、スレッドプログラミングは一般に共有メモリモ

導入するのは困難である。また、細粒度同期を工夫なく導デルであり、これが多くの問題を抱えていると考えている。

入すると、逐次処理の性能が低下する。

プログラム中に存在する共有データを正しく把握し、適切

言語デザイン上の理由としては、並行処理の書きやすさに同期を行う必要があるからである。とくに、正しく把握の問題である。これについても、2 つ理由がある。スレッすることが難しい。既存研究として、ツールによる発見をドプログラミングはそもそも難しい、という点と、並列実サポートするアプローチや、型により陽に示すアプローチ

行するスレッドプログラミングはもっと難しい、という点があるが、Ruby 処理系に適用するには、実現の困難さや言である。これら 2 つの点から、Ruby においてはスレッド

語仕様との相容れなさといった問題がある。例えば、Rustを、並列処理を可能にするなど、より高機能にする、といのように、引数にその手の情報を記述する、といった変更

う方向にしないようにしている。

は、型を記述しないことによる容易なプログラミングを目

まず、各スレッドは様々なデータを簡単に共有すること指す Ruby には受け入れられない。共有メモリモデルでができるため、適切にプログラミングするのが一般的に困あっても、全てのデータは書き換え不可とする Erlang の難である。共有データについて、アプリケーションプログ

ような仕様とすれば、共有データに関する多くの問題が解

ラマが適切に同期を取る必要があるが、この同期処理につ決するが、Ruby に導入するのは互換性の観点から難しい。

いてプログラミング中に検討することは、スクリプト言語

そこで、共有メモリモデルではなく、並行実行単位で独がもつ「気楽さ」とは相容れないと筆者等は考えている。

立したメモリ空間をもつモデルを目指す。各並行実行単位

何が共有データであるかを意識するのは、規模の小さなプ

は専用のチャンネルを通じ通信することでプログラムを進

ログラムでは問題ないことが多いが、規模が大きくなるとめる。この並行実行単位を Guild とする。

正しく意識するのは難しい。プログラムの修正が進むと、共有メモリモデルを用いないため、基本的には Guild 間

当初は共有しなかったデータが意図せず共有データとなっではオブジェクトは共有しない。ただし、共有が許されるてしまうこともあるだろう。複数人で開発していると、開特殊なオブジェクトを用意することで、プログラミングの発者間の意識の違いから、問題が生じることもあるだろう。

しやすさも実現する。

スレッドによる並行並列処理は、非決定的な挙動になるこ

まとめると、Guild は Ruby が実現してきたプログラム2

2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日の容易性（「気楽なプログラミング」の実現）と、現実的な

つまり、これまで通りの逐次プログラムを記述すると、そインタプリタの開発・維持コストを実現するため、下記をこで生成されるオブジェクトは Guild 間で共有されること目標としている。

はない共有不可オブジェクトである。どのオブジェクトが( 1 ) Ruby 2 との互換性の維持

共有されるのか、精査しながら並行プログラムを記述する( 2 ) 並列処理のサポート必要がなくなり、並行プログラミングの難しさの 1 つが解( 3 ) 共有メモリモデルを用いない安全なプログラミングの

消される。

実現

しかし、Guild 間で共有するのが自然な情報もある。例( 4 ) 高速な Guild 間通信の実現

えば、複数の Guild 間で結果を集約したい場合に、共有の( 5 ) 現実的な処理系開発・維持コストの実現

スコアボードを用いたいような場合である。このようなプ

感覚的に述べると、Guild は「Ruby らしい楽しく簡単
な並行プログラミング」の実現を目指している。
次章では、この目標を達成するための Guild の現在の設計について議論する。

2. Guild の設計
本章では、並行・並列処理を可能にする新しい抽象化である Guild の設計について議論する。

ログラミングを可能にするため、共有可能オブジェクトを用意する。共有可能オブジェクトの操作には排他制御処理を必須とすることで、スレッド安全性を高める。つまり、共有される可能性のあるオブジェクトは、言語仕様として同期処理の記述を強制されることになる。*1
スレッドプログラムの挙動を観察すると、多くの場合、ほとんどのオブジェクトはスレッド間で共有されない*2 。
そのため、スレッド間で共有するオブジェクトは例外的な存在であると考えられる。

2.1 Guild の生成共有するオブジェクトは例外的であるため、出現する場

Ruby 処理系を起動すると、1 つの Guild を作成する（こ所も少ないことが期待できる。共有可能オブジェクトの利

れをメイン Guild とする）。Guild は最低 1 つのスレッド

用は手間がかかるが、そもそも出現箇所も少ないため、現を持つ。同一 Guild 内の複数のスレッドは、Ruby 2 と同実的なコストで Guild を利用することができるのではない

様に並行処理を行うが、ジャイアントロックによって同時かと期待している。

に実行はされない（Ruby 2 と完全に互換となる）。Guild

例えば、逐次プログラムをスレッドで並行処理する、とが異なるスレッドは、ジャイアントロックによって排他制

いったとき、コード中に共有する可能性のあるデータを全御を行わないため、並列に実行することができる。並行処て洗い出し、それらへのアクセスに排他制御等を導入する

理を記述する場合は、複数 Guild を作成することで行う。

必要がある。数十行といった小さい規模のプログラムではGuild の作成は、Thread クラスを用いたスレッドの生成

難しくないが、大規模なプログラムを並行処理しようとすと同様、ブロックを渡すことで実現する。

g1 = Guild.new do
expr1
end

ると、難しい。Guild では、オブジェクトの共有ができな
いため、必要な箇所を共有可能オブジェクトに書き換えなければ実行することができず、フェイルセーフな設計といえる。
共有可能オブジェクトは、他の共有オブジェクトへの参

g2 = Guild.new do
expr2
end
この例では、g1、g2 という 2 つの Guild を生成してい照しか持たないようにする。つまり、共有可能オブジェク
トを通じて共有不可オブジェクトを誤って Guild 間で共有するといった事態は起こらない。

る。Guild を生成すると、暗黙にスレッドも生成されるた共有可能オブジェクトは次の 5 種類である。

め、各コード片はそれぞれ並行に処理される。そして、そ• 共有コンテナオブジェクト

れぞれ別 Guild に所属するスレッドであるため、並列処理• 不変オブジェクトすることができる。

• クラス・モジュールオブジェクト

このとき、Guild.new に渡したブロック（無名関数）は• 隔離 Proc オブジェクト

自由変数などのブロック外の情報にアクセスできない隔離• Guild 制御オブジェクト

ブロックという特殊なブロックとして扱われる。

*1

2.2 共有可能オブジェクトと共有不可オブジェクト
共有メモリモデルでは、スレッド間で共有するオブジェ
クトの発見が困難である、と前章で述べた。そこで、Guild
では、基本的にオブジェクトは共有されないこととする。

*2

Clojure 言語における書き換え可能オブジェクトと同様の考え方である。ただし、Clojure では、すべての（読み書き可能オブジェクトでの）読み書きについて、このような同期プロトコルの利用が必須であるが、Ruby における Guild では、共有可能オブジェクトでの読み書きにのみ適用されるのが異なる。
この特性を利用して、あるオブジェクトがスレッドローカルである、と解析し、その情報を最適化に利用するという研究が多数存在する。

3

2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

これら以外のオブジェクトはすべて共有不可オブジェク
トであり、ある唯一の Guild に所属する。

能に誘導するインターフェースが重要である。すべての共有コンテナオブジェクトを STM にすることで、複数の共実装コストの観点からいうと、共有不可オブジェクトに

有データにまたがったトランザクションを、ロックの順番

ついては、Ruby 2 から実装をあまり変えなくてよい、とによるデッドロックの心配なく実現することができるのいう利点がある。ただし、インタプリタグローバルな情報

で、STM は有力な手法である。

を利用している実装（例えば、C のグローバル変数を用い

しかし、一貫しなければならない処理に対して、複数ト

た実装）は、Guild ローカルにするために変更が必要であ

ランザクションを実行してしまうようなプログラムミスをる。性能的には、細粒度排他制御を行わないため、逐次処検出できない。

理の性能は変らないと期待できる。

# 間違ったコード

2.2.1 共有コンテナオブジェクト

n = s.transaction{ s[:n] }複数の Guild 間で読み書き可能な状態を共有したいこと

s.transaction{ s[:n] = n+1 }もあるため、共有可能なコンテナオブジェクトを用意する。
共有コンテナオブジェクトが格納するのは、すべて共有可能オブジェクトとする。共有可能オブジェクトの読み書
き時には、特定のプロトコルを用いることを必須として、一貫性の維持を強制させる。
特定のプロトコルの、もっとも原始的なアイディアは、
読み書き中にロックを要求するものだろう。

# 正しいコードs.transaction{ s[:n] = s[:n] +1 }上記コードは非常に単純な例なので、間違いようがないと思うが、例えば、これらの取得、設定メソッドを設け、メソッド内でトランザクションを独立に構成している場合、容易に間違いそうなプログラムを記述してしまう。

s = SharedStorage.new# 間違いそうな例

s.lock do # ブロック実行中ロックされる

def get_num(s)s[:key] = vals[:n] += 1

s.transaction{ s[:n] }
end

end

def set_num s, ns.transaction{ s[:n] = n}val = s[:key] # この時、s はロックされていないend# ので、エラーになる
このコードでは、SharedStorage クラスを、Ruby のHash クラス（辞書構造）のデータと似たようなものとして# 間違い
set_num( s, get_num(s) + 1 )いる。lock に渡したブロックでは、ロックを獲得している
ため、s への読み書きを安全に行うことができる。ロック# 正しいコードを獲得しないときにアクセスしようとすると、排他制御を

s.transaction do

行っていないため、例外を発生させる。このように、Guild
間での一貫性の維持をプログラマに強制させる。

set_num( s, get_num(s) + 1 )end

アクセス時にトランザクション制御を用いる、いわゆる、対症療法として、トランザクション内のログを取る、とソフトウェアトランザクショナルメモリ（STM）を用いる

いった手法が考えられる。共有メモリモデルよりも範囲をオブジェクトについても検討している。

絞ることができるので、実現しやすい手法である。今のと上記では、Hash クラスのようなデータ構造を用意した

ころ、この問題を回避しやすい API デザインは思いついが、どのようなデータ構造が必要であるかは課題として残ていないため、今後の課題である。

る。簡単なプログラムでは、キーと値に数値や文字列、シ2.2.2 不変オブジェクト

ンボルといったプリミティブなデータ型しか許さない、制不変オブジェクトは複数の Guild で共有しても、同時に限されたデータ構造でも十分だろう。では、木構造のよう読み書きが行われないため、スレッド安全である。そのたなデータや、配列、オブジェクトとフィールドのような記め、不変オブジェクトは Guild 間で共有が可能である。

述は可能にするべきだろうか。許せばプログラムの自由度

Ruby には、あるオブジェクトの書き換えを禁止するを増すが、複雑性を増すため、ミスが混入する可能性が増

freeze というメソッドがあるが、書き換え禁止にしただえる。

けでは不変オブジェクトとはならない。書き換え禁止オブ

例えば、共有データが複数あるとき、データをまたがっジェクト a が書き換え可能オブジェクト b への参照を持って一貫性が求められるような場合、トランザクションを適ていた場合、a を共有したとき b も同時に共有されうるた切に制御することが必要になるが、それを誤らずに制御可

めである。

4

2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

b = [1]# 書き換え可能な配列オブジェクト

複数 Guild で利用する場合は、例えば上記の例では単なるa = [b]配列ではなく、それに相当する共有可能オブジェクトを利a.freeze # a は書き換え禁止だが、用するようにプログラムを変更しなければならない。既存# a が参照する b は書き換え可能なのでのプログラムは動かなくなるが、気づかずにオブジェクト# a は不変オブジェクトではないを共有してしまい、同期漏れを起こす、といった事故を防ぐことができる。

c = []
c.freeze # c は不変オブジェクト
不変オブジェクトであるとは、そのオブジェクトが書きクラス変数は、読み書き可能な情報であるため、定数と同様の仕組みが必要である。

換え不可オブジェクトであり、そのオブジェクトから参照

先ほど、「メソッド定義や定数については、基本的には初されるオブジェクトが共有可能オブジェクトのみであると期化時に設定されるため、共有しても比較的問題がない」

き、不変オブジェクトと呼ぶ。数値リテラルやシンボルオと述べたが、初期化時以外に行われれば、一貫性の問題がブジェクト、freeze された文字列オブジェクトなどは不生じる可能性がある。

変オブジェクトである。例えば、ary=[1, 2].freeze とこの問題を回避するため、クラス定義は Guild 生成自にした配列 ary は不変オブジェクトである。

コピーし、変更は各 Guild で閉じる、といった仕様も検討

2.2.3 クラス・モジュールオブジェクト

したが、例えば Guild 間で共有オブジェクトを共有してい全てのオブジェクトは、オブジェクトの継承関係を管理

るとき、あるメソッドは特定の Guild でしか定義されていするためのクラスやモジュールオブジェクトへの参照を保ない、もしくはあるメソッド定義が Guild 間で異なる、と持している。共有可能オブジェクトも同様である。そのたいった問題を生じる（なお、これは分散 Ruby（dRuby）をめ、クラス・モジュールオブジェクトも共有可能オブジェ利用したときに生じる問題と同様である）。

クトとする。

利点欠点を検討した結果、現在は Guild 間で定義を共有

ただ、クラス・モジュールオブジェクトは書き換え可能

し、変更が他の Guild に影響することを許容するモデルをであるため、特別な対処が必要である。クラスやモジュー採用している*4 。ただし、クラス・モジュールへの変更を

ルオブジェクトがもつ主な書き換え可能な情報は、メソッ

すべてトレースする機構を導入することで、問題発見を可ド定義、定数、クラス変数である。

能にする。開発のプラクティスとしては、複数 Guild 実行

メソッド定義や定数については、基本的には初期化時に時は、定義を変更しないようにすることだろう（トレース設定されるため、共有しても比較的問題がない。ただし、機構によって、これを検出できる）。

定数が共有不可オブジェクトを参照することがあるため、

2.2.4 隔離 Proc オブジェクト

特殊な取り決めが必要である。

class C
Const = [] # 配列は共有不可オブジェクト

Ruby でクロージャを表現する Proc オブジェクトは、ブロックという文法で与えたコード片と、その時のローカル変数等の環境をまとめたものである。環境は自由変数（ブロックの外側にある変数）の参照をもつため、Proc オブend

ジェクトをそのまま共有可能オブジェクトとすると、自由

Guild.new do

変数経由で共有不可オブジェクトを誤って共有してしまう

C::Const

可能性がある。しかし、Guild 間で Proc を用いて手続き#=> 共有不可オブジェクト（配列）を取り出そを手軽に共有したい、というニーズは大きい。

#

うとするが、セットした Guild と違うた#

め実行時例外end

そこで、自由変数などの環境を共有しない隔離 Proc オ
ブジェクトをサポートする。隔離 Proc オブジェクトは、

Proc#isolate メソッドを用いて作成される。自由変数などへの参照がある場合、例外を発生させ、アプリケーショ

C::Const

ンプログラマにこの自由変数などへのアクセスは許されて#=> 共有不可オブジェクト（配列）を取り出す

いないことを示す。
「自由変数など」とあるのは、自由変数

そこで、Ruby 2 との互換性のため、共有不可オブジェ
クトを定数に代入したときは、セットした Guild 以外でア以外にも環境は情報をもっており、それらへのアクセスをすべて禁止するためである。

クセスしようとすると実行時例外をあげることにする*3 。

現在の実装では、ローカル変数アクセス時、自由変数へ

つまり、単一 Guild で実行する分には問題ないが、複数のアクセスであれば例外を発生させるという、簡単な手法

Guild で利用しようとすると、実行時例外により停止するを用いている。しかし、Proc#isolate 実行時に与えられ*3

ただし、この仕様はまだ検討中であり、実際に利用しながら仕様を固めていく。

*4

別案として、メソッド定義にトランザクション制御を設ける、ということも検討している。

5

2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

たコード片が自由変数アクセスを行うかどうか、判別可能

ルボックスでは送信時に異常検知ができるが、単純にであるので、Proc#isolate 呼び出し時に例外を発生させ、双方向通信を行うチャンネルオブジェクトを用いる問題を早期発見できるようにする予定である。

と、これはできない。片方向通信チャンネルを用いる

Guild.new に渡したブロックは、暗黙的に Proc#isolate

パイプやソケットは SIGPIPE によって送信先の不在

メソッドにより生成された隔離 Proc オブジェクトが利用を知ることができるが、入出力の端点を Guild 間で適される。

切に管理しようとすると、API が煩雑になる。

なお、Proc#isolate の呼び出しではなく、文法レベル• 生産者消費者モデルにおいて、それぞれに対応する

で隔離 Proc オブジェクトを生成するということも検討しGuild が複数あるとき、手の空いている消費者がタスているが、まだ良い文法を発見できていない。

クを受け取りたい、という場合において、チャンネ

2.2.5 Guild 制御オブジェクト

ルでは、生産者と消費者が 1 つのチャンネルへ送信、Guild を制御するための Guild オブジェクト等は、Guild

受信を行うことで実現できる。メールボックスでは、間で共有できるようにする。Guild 間通信などに用いるが、Guild 間の仲介を行うハブ的な仕組みを別途用意する

それについては次節で説明する。

必要がある。

2.2.6 共有不可オブジェクト
上述した共有可能オブジェクト以外は、すべて共有不可
オブジェクトである。

• ある Guild がシステムからの通知を非同期で受け取る場合（例えば、監視している Guild が異常終了した、など。Erlang でいう link(pid))）、メールボックス

Ruby プログラムを記述すると、文字列や配列といったであれば通知先がメールボックス 1 つで済むが、チャ

オブジェクトが大半を占める。それらは共有不可オブジェ

ンネルの場合、通知用チャンネルを用意しなければな

クトであるため、これらの操作にスレッドの並列処理に必
要な細粒度同期が不要となり、不必要な細粒度同期による逐次実行の速度低下をふせぐことができる。

らない。
他にも様々な点があるだろう（指摘頂きたい）。チャン
ネルは柔軟で、メールボックスは単純でミスがしづらい、という印象をもっているため、現状ではメールボックスを

2.3 Guild 間通信
共有メモリモデルをとらないため、Guild 間で協調する
ためには、特別な Guild 間をつなぐ通信路が必要になる。
通信路によって、情報の伝達および同期（待ち合わせ）がサポートしている。具体的には、Guild#send によって、その Guild へメッセージを送信する。Guild.receive によって、自 Guild へ届いたメッセージを受信する。

# ping-pong の例

可能となる。そこで、Guild 間でオブジェクトを伝達する手法を提供する。

2.3.1 通信路の抽象化：メールボックスとチャンネル
通信路の抽象化の方法として、Guild ごとにメールボッ
クスを持たせる方法と、Guild 間で共有したチャンネルを

child_g = Guild.new(Guild.current) do |parent_g|
obj = Guild.receive
parent_g.send obj
end用いる方法が考えられる。前者は Erlang 言語などが利用し、後者は Go 言語などがサポートしている。また、パイ

child_g.send 1

プやソケットなども後者の例である。

p Guild.receive #=> 1

どちらも、同期キューとしてモデル化できる。利用方法

この例では、親から子 Guild へ、オブジェクト（‘1‘）をに応じて、得意不得意がある。いくつかの点について議論送り、それを親へ送り返している。‘Guild.new‘ に渡したする。

パラメータは、通信路を経由してブロックパラメータとし• Guild 間で 2 種類以上の通信路が必要になった場合て新しく生成した Guild に渡される。

（例えば、データ通信路と制御通信路が欲しい場合）、

まだ、フィルタして受信する機能を設計、搭載していな

チャンネルの場合は、単に 2 つのチャンネルを用意す

いため、複数の通信路をシミュレートするには、アプリればよい。メールボックスではユーザが、送るデータ

ケーション開発者側で記述しなければならない。

を工夫して区別可能にする必要がある（もしくは、区

2.3.2 メッセージの送信：複製と移籍

別可能なように、送信時に付加情報を必須とする、という選択肢もある）。また、受信するメッセージのフィルタ機能が必要である。通信路ごとに固定の名前を付ける場合、名前の衝突が起こる可能性がある。

• 送信先不在の検出がメールボックスでは容易である。
送信先の Guild がエラーなどで終了している場合、メー共有可能オブジェクトを送るのは、参照を送るのみでよ
く、軽量である。
共有不可オブジェクトは、共有不可であるため、参照を送ってはならない。Guild では、共有不可オブジェクトの送信のために、複製と移籍の 2 つをサポートする。
複製は、送信するオブジェクトの複製を生成し、別の6

2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

Guild へ渡す。送信元と送信先で別のオブジェクトになるクラスやモジュールは特殊共有可能変数であるため、メ

ため、状態を共有しない。ただし、送信するオブジェクト

ソッドテーブルなど、これらが管理するデータは同期処理だけでなく、そのオブジェクトから辿ることができるすべが必要になる。そのため、テーブルアクセス時にはロックての共有不可オブジェクトを複製する必要がある。オブなどを導入する。同様に、シンボルテーブルなどの、インジェクト間の参照は循環構造になる可能性があるため注意

タプリタでグローバルに管理しなければならないテーブルが必要である。

も、同期処理を挿入する必要がある。

移籍は、送信するオブジェクトの所属を、送信先 Guild

その他、内部的に Guild 間で同時に読み書きされるデーへ移すものであり、送信元からはアクセスを禁止する。複タについても、すべて適切な同期処理を挟む必要がある。

製のように、すべてをコピーする必要は無いため軽量であ

例えば、インラインメソッドキャッシュがそれにあたるが、る。例えば、タスクを送る、結果を返すといった場合、送これも [3] で提案したように、更新時は新しいエントリを信元では送信するオブジェクトは、以降不要になることが作るようにすると良いと思われる。

多く、利用するシーンはあると考える。とくに、大きな文C 拡張には、並列に動作することができない、マルチス

字列データ、ソケットオブジェクトなどの移籍を想定してレッドセーフでないものが含まれていることが当然考えらいる。なお、移籍の前後で、オブジェクト ID の同一性はれる。そこで、既存の C 拡張はメイン Guild 以外では動維持しない。

作しないようにし、マルチスレッドセーフであると宣言し

なお、複製と同じように、当該オブジェクトから辿ることができるすべての共有不可オブジェクトに対して移籍をた C 拡張のみ、その他の Guild で実行できるとする。これは、[4] で提案した手法である。

行う必要がある。そのため、意図せず、参照されているオ

各オブジェクトには、freeze されたオブジェクトであブジェクトが送信元 Guild で参照不可となり、アクセスしるかどうかを示すフラグがあるが、参照しているオブジェて例外になる、という場合があり得るが、フェイルセーフ

クトが不変オブジェクト（もしくは特殊共有オブジェクト）を実現しているとも言える。

である、不変オブジェクトを示すフラグがないため、これ移籍の実装は、オブジェクトのヘッダのコピーのみ生成
し、オブジェクトの実体への参照を付け替える、という手法で実現できる。

を付与する必要がある。

4. 実験
試作を用いていくつかの実験を行った。実験は、Intel(R)

2.4 その他
Ruby にはグローバル変数など、インタプリタ内で情報を共有する仕組みがある。共有不可オブジェクトをグロー

Xeon(R) CPU E5-2630 v4 @ 2.20GHz（10 コア、HT により 20 CPU スレッド）を 2 つ詰む、合計 40 CPU スレッドをもつマシン上で実行させた。

バル変数などでどの箇所からも参照されるとまずいため、

まず、生成時間について調査した。比較対象として、Proc、

言語仕様に色々と手を加える必要がある。仕様変更においFiber（コルーチン）、スレッド、Unix プロセス（fork）を用ては、単一 Guild で実行する場合、Ruby 2 と完全に互換いた。それぞれ、生成し、値を渡し、その値を呼び出し元へであることを目指す。

返す、という一連の処理を、10 万回繰り返した時間を計測

グローバル変数は、Guild ローカルの変数とする。ただし、$LOAD_PATH といった特殊なグローバル変数については判断を保留する。

3. 実装

した（fork の場合は、1 万回繰り返した時間を 10 倍した）。

fork の場合は、パイプを用いて値のやりとりを行った。
結果は、Proc: 0.09 秒、Fiber: 0.24 秒、スレッド: 3.23
秒、Guild: 6.48 秒、fork: 63.40 秒となった。スレッドとGuild はほぼ同一となるかと思ったが、約 2 倍遅い。Guild

本章では Guild の実装の方針について述べる。

の生成と解放時、スレッド＋αの生成と解放が必要になる

インタプリタプロセスに唯一であったグローバルロック

ため、遅くなったのではないかと思われる。プロセス生成

を、Guild ごとに持たせる。すでにスレッドはネイティブが必要な fork に比べ、Guild は 10 倍程度高速である。

スレッドで実装しているため、並列計算機上では並列処理が可能である。

次に、ある数値 n に対するフィボナッチ数を求めるワー
カー Guild を複数生成し、マスター Guild からリクエスト

ガーベージコレクションは、全 Guild を停止し、マークを送る、単純なプログラムを作成する実験を行った。マスを行う。スイープは各 Guild で遅延スイープを行う。今ターは負荷分散用 Hub へリクエストを送信し、ワーカー

後、Guild ローカルな GC を行うことも検討しているが、はハブからリクエストを取得し、結果は逆の順序で送り返共有可能オブジェクトをどのように扱うかが問題になる。

す、というものである。

おそらく、分散 GC の仕組みの導入が必要になる。
以降の段落は前報告からの引用である。

結果を図 1 に示す。X 軸がワーカー Guild の数（並列度）で、Y 軸は逐次実行と比較しての速度向上率である。

7

2018-2-(3): 情報処理学会プログラミング研究会 発表資料 2018 年 8 月 1 日

Speedup ratio(compare with serial execution)の設計（C 言語レベル）では、最大限の性能を引き出すことができないため、コンテキストへのポインタを第一引数20に必ず指定するような方法が有効である。そのためには、

15
10

MRI のソースコードの設計を大幅に改め、また C 拡張を

5
0
0

20

図 1

40

60

80

100

記述するための Ruby C API の仕様を刷新する必要がある# of Guilds（ただし、互換背のために、現在利用している C API は、

fib(23) を 10 万回求めた結果

互換レイヤとして残す必要がある）。この変更は、決めてしまえば機械的に行なうことができる。しかし、C API の並列度が上がると、最大 15 倍ほどの性能向上を達成できprefix である rb_ を、別の prefix を与えねばならず、そのている。

名前付けがとくに困難である*5 。

現状、GC の実装が不完全で、多くの Ruby プログラムが実行できないが、例えば複数ファイルから文字列を検索

7. おわりにするプログラムを実装すると、逐次実行と比べ 2〜6 倍遅

本報告では、Ruby 3 に向けて開発している並行処理のくなった。これは、オブジェクト生成時にグローバルロッ

抽象化である Guild について、その進捗を述べた。報告にクを取得するナイーブな実装を用いているためであり、オあるとおり、未だ検討段階である部分が多いため、多くのブジェクト生成をある程度 Guild ローカルで行うことにすご意見を頂けるとありがたい。今後、実装を進め、また報れば徐々に改善するのではないかと思われる。

告できればと思っている。

5. 関連研究

なお、実装は https://github.com/ko1/ruby/tree/guildから取得できる。

関連研究については、前報告を参照されたい。

1 点補足する。メールボックスを用いた Guild をアクターモデルと称して良いか、という点である。Koster 等の分類 [1] では、アクターモデルを 4 つに分類したうちの「プロセス」に含まれると思われるため、称しても良いと思われる。ただし、彼らの分類では、アクターモデルが共有メ
モリモデルを用いていても良い（アクターモデルと称する必須事項ではない）としているため、本報告ではアクター
モデルという用語を用いなかった。

6. 今後の課題
課題は多く残っているが、少なくとも下記が未解決である。

Guild の終了時やエラー時の挙動の意味が不十分である。
Guild がエラーによる異常終了を行ったとき、何をどのように通知するか検討が必要である。Ruby のスレッドでは、

参考文献
[1]

De Koster, J., Van Cutsem, T. and De Meuter, W.:
43 Years of Actors: A Taxonomy of Actor Models
and Their Key Properties, Proceedings of the 6th International Workshop on Programming Based on Actors, Agents, and Decentralized Control, AGERE 2016,
New York, NY, USA, ACM, pp. 31–40 (online), DOI:
10.1145/3001886.3001890 (2016).
[2] 笹 田 耕 一 ，松 本 行 弘： Ruby 3 に 向 け た 新 し い 並 行 実
行モデルの提案，情報処理学会論文誌プログラミング（PRO），Vol. 10, No. 3, pp. 16–16（オンライン），入手先⟨https://ci.nii.ac.jp/naid/170000148644/⟩ (2017).
[3] 笹田耕一，松本行弘，前田敦司，並木美太郎： Ruby 用仮想マシン YARV における並列実行スレッドの実装，情報処理学会論文誌 (PRO)，Vol. 48, No. SIG 10(PRO33), pp.
1–16 (2007).
[4] 笹田耕一，卜部昌平，松本行弘， 平木敬： Ruby 用マル
チ仮想マシンによる並列処理の実現，情報処理学会論文誌
プログラミング（PRO），Vol. 5, No. 2, pp. 25–42 (2012).

異常終了しても単に無視する、もしくは例外情報を出力する、というのがデフォルトの挙動である。他の選択肢としては、例外を親（もしくはメイン Guild）の Guild に伝搬させる、という手法がある（Go 言語の goroutine での例外の伝搬）。異常終了のキャッチを迅速に行うデザインである。Erlang では、監視しているプロセスが異常終了したとき、その通知をメールボックスに受け取る。
共有可能オブジェクトの設計が十分に進んでいない。

STM を用いると述べたが、具体的にどのようなインター
フェースとなるか、まだ検討していない。また、不変オブジェクトが共有不可オブジェクトを参照していないことを確認する手法について、未解決である。
（前報告からの引用）[4] で示したとおり、現在の MRI*5

Ruby コミュニティでは、メソッドやデータ構造の名前付けに多くの苦労が払われている。Guild という名前も、数年越しで検討した結果提案した名前であるが、すでに多くの異論を頂いている。

8