IT・技術研修ならCTC教育サービス

サイト内検索 企業情報 サイトマップ

研修コース検索

コラム

スーパーエンジニアの独り言

CTC 教育サービス

 [IT研修]注目キーワード   Python  UiPath(RPA)  最新技術動向  Microsoft Azure  Docker  Kubernetes 

第61回 カネゴンの繭 2016年10月

「特撮の神様」円谷英二が率いる円谷プロ製作の空想特撮シリーズ「ウルトラQ」"Ultra Q" 第十五話「カネゴンの繭」"Cocoon of Kanegon" の放映をご覧になった事がありますでしょうか?

もし番組を観た事がない方でも「カネゴン」というチャックの歯付いた蝦蟇口(がまぐち)が頭になった奇妙な造形の怪獣はどこかでご覧になった事があるかと思います。

実はこの怪獣元々は少年なのですが、まるで「カフカ」(Franz Kafka) の「変身」"Die Verwandlung" や「安部公房」(Kōbō Abe) の「赤い繭」"The Red Cocoon" に肖る(あやかる)ように「カネゴン」になってしまったのです。何故このような容姿に変身したのか? の理由ですが、それはお金を貯める事に夢中な「加根田金男」(かねだかねお)少年が振るとコインの音が鳴る「繭」を拾うところから物語が始まります。「人の落としたお金を黙って拾ってはいけません」という親の忠告を聴かずにお金の音がする繭に執着していると部屋の中で巨大化したその繭の中に吸い込まれてしまいます。翌朝、目覚めると金男はご存知「カネゴン」になってしまったのです(拙作コラム「第26回 赤い繭」、「第38回 はらぺこあおむし」も併せてご覧ください)。

今回のお話は落としたコインを見つけては貪る(むさぼる)「カネゴン」の如く、不要なメモリ領域を見つけては回収する「ガベージコレクション」"Garbage Collection, GC" について散策したいと思います。

 

『オペレーティングシステムのプロセス』:

「 Ruby のメモリ管理はどうなっているのですか?」という質問を受けることが稀にあります。

この質問に対する一つ目の回答として思いつくのは、Ruby インタプリタはオペレーティングシステム "Operating System, OS" から見ると単に一つのプロセスに過ぎないのですから該当のオペレーティングシステムが行っている「プロセス管理」"Process Management" に従うことになりましょう、というのが妥当なご回答になると考えています。

お使いのオペレーティングシステムが人間との仲介してくれる事により目視で確認できる四角い機械である物理機器(現実世界)をイメージとして丸くて触りやすい論理構造(仮想空間)に対応付けてくれるのです。この作用によって箱の中に綺麗に納まったシリコンや金属とプラスチックの固まりが凝縮した配線で繋がったハードウェアで複数のプログラムを任意に実行できる環境を我々に提供してくれています。
オペレーティングシステムを詳しく知るというのは、つまり機械の箱をどのように制御しているのかという「仕組み」を理解することで非常に大切な事になります。

オペレーティングシステムへの知見を得るためには、プロセッサとメモリ、ストレージ、ネットワークインターフェースと無数のハードウェアを緊密に連動して動作を可能とするための制御を行うのが役割で且つ存在理由であるために、その複雑怪奇なロジックの闇間へと潜って深遠に達すると「ディラックの海」"Dirac Sea" という真空状態でマイナスエネルギーの電子に満ちた虚無空間にあると噂されるコアに触れることで総体として合意形成された真意を見極めるというのは難題であろうことは想像に難くないのです。ですが少しでも造詣を深めようと「場の量子論」"Quantum Field Theory" を唱えて倫理的に解明しようと励む行為は極めて尊いと言えましょう。

ディラックの海へと跳び込む無謀なダイビングは機会を見て取り上げたいと思いますが、今回はこれを切欠に「 Ruby のインタプリタが行うメモリ管理」について違う角度からもう少しだけ調査してみます。

 

『Ruby インタプリタのメモリ管理』:

受けた質問をもう少し狭義の意味で為された意図として解釈するなら「実行されているプロセス内でのメモリ管理はどのようになっているのか?」と受け取ることが出来るでしょう。

例えばですが、Java VM での起動オプションには色々な指定可能な項目が存在しており "GC" (Garbage Collection) に関してヒープの最大サイズ指定するなどが行えるのですが、 Ruby インタプリタでも同様に関係する起動オプションは無いのか? という意図も付帯していたかもしれません。プロセス個々に於ける内部でのメモリの割り当ての話です。ここでは、Ruby インタプリタに於ける関連情報の記載を試みます。

1. Ruby インタプリタ起動オプション

Rubyインタプリタの起動オプションでは、メモリに関連するオプションは存在しません。Rubyの公式実装である MRI (Matz's Ruby Interpreter, CRuby) をご利用の場合にインタプリタ起動オプションでは、GC の挙動に何らかの指示をすることができないのです。ですが、Rubyはオープンソースであり他の Ruby 処理系が複数存在しますのでそれら代替実装ではご指定可能であるかもしれません。

2. プロファイリング

プログラムがどの行がどのくらいメモリを消費しているか ObjectSpace モジュールを使って調べることが出来ます。例えば、each_objectメソッドで存在するオブジェクトの個数などを知ることができます。
但し、(CRuby固有の機能になりますが) Cレベルのオブジェクトもカウントするには count_objects メソッドで正確に測定出来ます。

irb(main):011:0> ObjectSpace.count_objects
=> {:TOTAL=>53795, :FREE=>2748, :T_OBJECT=>374, :T_CLASS=>983, :T_MODULE=>44, :T
_FLOAT=>4, :T_STRING=>32394, :T_REGEXP=>241, :T_ARRAY=>4023, :T_HASH=>186, :T_ST
RUCT=>1964, :T_BIGNUM=>58, :T_FILE=>6, :T_DATA=>1349, :T_MATCH=>13, :T_COMPLEX=>
1, :T_SYMBOL=>8, :T_IMEMO=>9331, :T_NODE=>15, :T_ICLASS=>53}

また個数ではなく、オブジェクトのメモリ使用量を知りたい場合は、(これもCRuby固有の機能ですが) memsize_ofメソッドが使えます( objspace ライブラリは、ObjectSpace の MRI 拡張機能で memsize_ofメソッドなどが搭載されています)。

irb(main):015:0> require 'objspace'
irb(main):016:0> ObjectSpace.memsize_of("hello, world")
=> 40
irb(main):017:0> ObjectSpace.memsize_of(123)
=> 0

ここでの実行結果は「数値」のサイズが「0byte」になっていますが、「Rubyオブジェクト」"RVALUE" のサイズが出力に含まれていないためです(拙作の「第46回 深海マリア」号を併せてお読み下さい)。これはRubyインタプリタの後継バージョンでは挙動が異なる様ですので、ご自身の環境で是非お試し下さいませ。

ついでの Tips ですが、この "RVALUE" のサイズは(プラットフォームに因りますが)通常 40 byte (5 word) になっている場合が多いのですが、これを 64 byte (8 word) にすると CPU の動作に優しくなってキャッシュ効率が大幅に改善するらしいです(2 の倍数である 8 にすることでキャッシュラインに沿うことになってCPUキャッシュミスヒットが削減されるらしいのです)。ですが、パッチを当てる必要がありますので自らは試しておりません。その点ご容赦下さい。

更には、memsize_of_all メソッドで生存しているオブジェクトが消費しているメモリ使用量を確認することができそうです。これをモニタすることで、もし「オブリヴィオン・ダスト」"Oblivion Dust" つまりは「ガベージコレクションされていないオブジェクト」を発見したとすると、(場合によっては)明示的に廃棄することも出来るようになるのかもしれません。ですが、手動でメモリ管理を補助することになるとするならばそのオーバーヘッドも考慮しなければならないでしょうし、何よりもガベージコレクションの存在意義自体が薄れてしまうことになりかねません。その点は熟慮が必要になると思います。

ここまで来たら自分が書いたコードがどんな風に動いているのか知りたくなってきましたが、「プログラムのプロファイリング」"Profiling" の手段そして「性能解析」"Performance Analysis" という話に移行していくのでこれはまた別の機会にチャレンジするとしましょう。

3. チューニング

Ruby 2.0 以降では GC のアルゴリズムが変更されている様子です。更に Ruby 2.1 ではパフォーマンスの改善も図られています。 2.1からは「世代別ガベージコレクション」 "Generational Garbage Collection" に移行が為されているとの事です。これらGCの改善に関する情報はオフィシャルサイトに掲載中の 「るびま」"Rubyist Magazine" をご参照下さい。
これらの改善は Ruby が採用されている大規模サービスに於いて著しいパフォーマンスへの影響が発生している状況下で GC の改良を望む声に呼応する形であった様子です。
現行ではパフォーマンスに影響が出ないような改善が十二分に行われていると考えて宜しいのかとも思います。

ここで更に一歩進んで自分が創ったアプリケーション専用に GC でのパラメータを修正することでチューニングを行うことも出来そうに思えます。
該当の GC パラメータは CRuby (MRI) のソースコード "ruby/gc.c" に記述されています。最初にこのソースコードを読んでパラメータの意味を理解する必要が先ずあります。続いてソースコードを読むと判明しますが、パラメータの設定には環境変数を使うことで値を外部から設定が出来ます(実際に筆者も試してみましたが設定内容は環境変数経由で反映されました)。パラメータ修正の度にソースコードにパッチを当てなくても済みます。

以下のようなパラメータと初期値がソースコードにありました。一部を付記します。

#define GC_HEAP_FREE_SLOTS 4096                         #=> 最初にヒープに確保するスロット数
#define GC_HEAP_INIT_SLOTS 10000                        #=> 最初に確保するスロット数
#define GC_HEAP_GROWTH_FACTOR 1.8                       #=> スロットを確保する増加率
#define GC_HEAP_GROWTH_MAX_SLOTS 0 /* 0 is disable */   #=> 一度に確保するスロット数の上限
#define GC_HEAP_OLDOBJECT_LIMIT_FACTOR 2.0              #=> Oldオブジェクトが指定した数を超えるとメジャーGC発動
                                                        #=> 初期値 2.0 の意味はOldオブジェクトが前回GC発動時の2倍になったら発動という意味。

このチューニング方法はパラメータの意味をちゃんと理解して設定しなければ、意図せず極端に処理が遅くなるという逆転現象も起こり兼ねないという危険を伴いますが、走らせるアプリケーションのメモリ使用の特性を理解した上でパラメータを調整することでチューニングが出来るとは思います。
パラメータをデフォルト値から変更することは危険であり諸刃の剣と成り得ますが、意味を知った上で値を徐々に調整して速度を測定するという行為を繰り返し行い、F1マシンのメカニックの様に特化したアプリケーションに最適なコンディションにチューンアップするということは可能でありましょう。

MRI にもユーザに調整を行う余地が与えられていることが理解できました。

『ガベージコレクションのアルゴリズム』:

Ruby インタプリタに於けるプロセス内のメモリ管理について記載してきましたが、そもそも「ガベージコレクション」"Garbage Collection, GC" とは何者なのでしょうか?

「ガベージコレクション」は言語処理系(ランタイムや外部ライブラリ)などが提供している機能でプログラムが必要とするメモリ管理を自動でしてくれるものです。お陰でプログラマが面倒なメモリ管理を手動で行う必要がないという便利な代物なのです。

「ガベージコレクション」はその名前の通り「ゴミの収集」が役割です。「ゴミ」とは不要になったオブジェクトを指しており二つの事を行います。一つ目が「メモリ領域上のゴミを見つける」事、二つ目が「見つけたゴミを回収してその領域を再利用できるようにする」事です。「ガベージコレクタ」"Garbage Collector" という「ゴミの車(ゴミ収集車)」を所有していてこの役目を担います。つまり「ゴミの車」が走っていらなくなったゴミを拾い集めてくれるのです。

この「ゴミの車」(ガベージコレクタ)ですが、幾つかの「アルゴリズム」"algorithm" で「ゴミ集め」をしています。下記はその種類を大まかに列記したものです。

1. マーク・アンド・スイープ "Mark and Sweep"
2. リファレンスカウント "Reference Count"
3. コピーGC "Copying GC"
4. マーク・コンパクト "Mark Compact GC"
5. 保守的GC "Conservative GC"
6. 世代別GC "Generational GC"
7. インクリメンタルGC "Incremental GC"

最初に米国科学者の「ジョン・マッカーシー」(John McCarthy) 等によって「マーク・アンド・スイープ」"Mark and Sweep" というアルゴリズムが1960年に登場しました。これは、生きているオブジェクトにマークして死んでいるオブジェクトを回収するスイープ という二つのフェーズを繰り返すものです。これがすべての始まりです。
その後に「リファレンスカウント」"Reference Count" が効率を求めてそれを改良する目的で登場しましたが、循環参照という問題が発覚しました。これによりメモリリークを引き起こすのです。そして「コピーGC」"Copying GC" 登場へという推移した歴史なのだそうです。上記以外にも掲載したものを改良した多岐に及ぶアルゴリズムが多数存在するようです。
ここにご興味があればですが「ガベージコレクションのアルゴリズムと実装」(著者:中村成洋、相川光、監修:竹内郁雄)という書籍が上梓されておりますので是非ご覧ください。

実際のガベージコレクションは、搭載されている言語処理系の特性や各々の実装形態によって様々です。

では、肝心の Ruby (MRI, CRuby)ではどんな実装をされていたかと言えば、当初は「マーク・アンド・スイープ」"Mark and Sweep" が採用されていました。
Ruby 1.9.0 では「笹田耕一」(Koichi Sasada, ko1) さんが開発した「YARV」 "Yet Another Ruby VM" が MRI にマージされて実装自体が大きく変更されました。
Ruby 2.0.0 では「ビットマップマーキング」"Bitmap Marking" 機能によってGCが効率よくマークできる方策に改善されました。
Ruby 2.1.0 では「世代別GC」"Generational GC" が採用されてスループットが大幅に改善されました。
Ruby 2.2.0 では 世代別GC をベースにしながら「インクリメンタルGC」"Incremental GC" が合わせて採用されることでGCによる長時間停止を回避することが出来るようになったのです。
Ruby 2.2.0 では、更なるメモリ管理の改善として「シンボルGC」"Symbol GC" という「シンボル」に対しての新たなGC実装が投入されています。これによりシンボルがガベージコレクションされるようになりました。これが理由で Ruby on Rails 5 からは、Ruby 2.2 以上を要求とするということに至ります。 Rails での外部から受け付ける文字列操作に関して大幅に負担を軽減できることが見込まれるからです。

前述した書籍の著者であり株式会社ネットワーク応用通信研究所 (NaCl) の「中村成洋」(Narihiro Nakamura, nari3) さんが MRI (CRuby) の GC 実装を中心に行っている様子です。以前のインタビュー記事では、MRI の 歴史的な理由から GC の大きな変更は難しいものの、"Mark and Sweep" に細かい修正を加えて将来的には並列マーキングを採用したいという展望を話されていました。現行では大幅に改良が進んでおり地道な尽力が続けられているのが確認できました。

Ruby (MRI, CRuby) の GC は着実に進化しています。

『未来のガベージコレクション』:

因みにですが、半導体メモリを永続的外部記憶装置として利用する「ソリッドステートドライブ」"Solid State Drive, SSD" が普及し「フラッシュメモリ」"Flash Memory" が採用された高速なディスクドライブとして Flash SSD を筆者も利用していますが、 Flash SSD がデータ更新する際に元データを削除して空き領域を用意することも同様にガベージコレクション(ゴミ集め)と呼ばれるそうです。これも言語系での実装と同様に動作特性に適合した GC アルゴリズムが必要とされるでしょう。

GC界隈では Java VM での実装が先行していたのは間違いないのでしょう。
例えば、特異点であった Azul Systems が J2EEのサーバーマシンとしての Java 専用計算機 "Network Attached Processing, NAP" を開発しました。専科したハードウェアでは "VEGA"(ヴェガ)という独自プロセッサを開発しており、その内部に JVM の GC 実装として "Pauseless GC"(無停止GC)という特殊なアルゴリズムを使用するものが存在しました。「クリフ・クリック」(Cliff Click) が設計した代物で彼は Sun 在職中に HotSpot の "Just-In-Time Compiler" (JIT コンパイラ) を設計したその人でした。その先進性は間違いないものだったと思われます。
また OpenJDK などで採用されている HotSpot VM では、複数のGCの中からアルゴリズムを選択出来るようになっています。中でも「ガベージファーストGC」"G1GC" は、ヒープを小さい領域に分割して管理する手法で GC 中にもアプリケーションを続行できるなどの魅力があるそうです。これはマルチコアに対応するための一つの方策でもありましょう。

中村氏曰く、これらの多岐にわたる試みを俯瞰した上で将来に向けて一つ可能性として提言されているのは「ハードウェアサポート」があるそうです。サーバ仮想化支援のためのハードウェアサポートがあるように「ガベージコレクションを援護するハードウェアサポート」があっても良いのではという問いかけです。これが採用されれば俄然(がぜん)高速になるであろうことは予想に違わないのでしょうが、ハードウェアとしての汎用性は著しく落ちることになります。ですが、電源投入時に最新のバイトコードをロードするような Ruby 或いは Python 専科のマシンとすることで不可能な訳ではありません。もしそうであれば Azul Systems の再来となるのかもしれません。

そしてガベージコレクションの展望としては、如何にGCが実行される影響を少なくするのか?そして先進的なハードウェアを十二分に活用できるのかに焦点が当りそうです。
課題とされるのは「CPUコアの並列化」、「言語処理系の並列化」、「GCの並列化」などが挙げられています。更にその先の近未来にはプログラムを実行する事でオブジェクトの寿命を学習していくようなインタプリタであり、機械学習が組み込まれることで各々の寿命にあったガベージコレクションが為されるような実装が予想されています。

「ゴミの車」を快適に走らすためには、まだまだ果てしなく続く道程がありそうです。

『 RHG: Ruby Hacking Guide 』:

今回記事を書くために主に参考にさせて頂いたのは、前述の「ガベージコレクションのアルゴリズムと実装」と供に「Rubyソースコード完全解説」(青木峰郎 著、まつもとゆきひろ 監修)の書籍です。「Rubyソースコード完全解説」はRuby Hacking Guide "RHG" と呼ばれており、Rubyインタプリタ実装を詳細に解説した貴重な書籍として非常に重要なものです。今回は「第5章 ガ-ベージコレクション」の項目を読んで勉強させて頂きました。RHG は書籍としては既に廃版なのですが、著者の青木峰郎氏が全文を公開されています。Ruby を扱う方々にとっては心強い味方です。一見の価値はありますので未読であれば是非ご覧になって下さい。
加えて、RHG については、Ruby オフィシャルサイト内に掲載されている「るびま」(Ruby magazine 0006号) の記事として "YARV Maniacs【第 1 回】『Ruby ソースコード完全解説』不完全解説"(書いた人: ささだこういち)がありますので併読なさる事をお薦めさせて頂きます。筆者のRubyの先生である直人氏も改めてお薦めしておりました。

『北の国から '95 秘密』:

インタプリタに於いてガベージコレクションという「ゴミの車」が掃除をして下さるという仕組みが行われているというプロセス内でのメモリ管理の様子が少しだけ垣間見えたように思えます。 Python ではどんな「ゴミの車」が走っているのかは次の課題とさせて頂きます。

「ゴミの車」で思い出したのは「北の国から '95 秘密」です。

北海道旭川出身の筆者は「北の国から」のテレビシリーズ放映時に地元で五郎さん(田中邦衛)を見かけた事がありました。富良野と旭川はすぐ近くなのです。テレビシリーズとして放映されていた「北の国から」は雪国が辺鄙な田舎とされていてあまり好きではなかったので当時番組を見ることは無かったのですけれど、1995年と言えば筆者は既に上京して年月が過ぎ就職もしていたその際にはどういう心境の変化だったのかは想い出せませんが、何故かこのスペシャル番組は視聴していたのです。観たのはテレビ放送されたその一回だけですがよく覚えています。

富良野に帰郷した純(吉岡秀隆)が市役所の臨時職員となって「ゴミの車」に乗っている冴えない様子を想い出します。そして富良野で出逢った小沼シュウ(宮沢りえ)と物語の糸を紡ぎはじめるのです。

シュウは優しく聡明で可憐なその立ち姿に見惚れてしまいます。
シュウの存在は富良野の風景が透けて見える程に儚げ(はかなげ)に映ります。
シュウが呟く言葉も涙を誘発します。「消しゴム」の台詞(せりふ)です。
五郎さんが純を戒める言葉が胸に突き刺さります。「石鹸」の件(くだり)です。
純が意気地無し(いくじなし)なのは生来(せいらい)のものですが、思い遣りに欠けた不寛容さは許しがたいです。
筆者のみならず視聴者の皆が駄目な純くんに自分自身の姿を投影していた事でしょう。

人間長く生きていれば汚れも付きます。
ですが付いた汚れは洗えば綺麗になりましょう。
いつでもやり直せないことはないと信じたいものです。
物事をどう捉えるのかというのは自身の心の持ち様である筈です。

ちっぽけな存在に過ぎませんが、懐の深さと寛容さを滲み出せるように心掛けたいです。

次回もお楽しみに。

 


 

 [IT研修]注目キーワード   Python  UiPath(RPA)  最新技術動向  Microsoft Azure  Docker  Kubernetes