最近のruby-core (2016年7月)

こんにちは。卜部です。

ruby-coreというRuby本体の開発の議論がされているメーリングリストがあります。

新機能やバグ報告などがだいたいここに集約されてくるので購読しておくとRubyの動きが分かります。

最近興味深かったトピックを紹介します。過去分はこちら

[#12217] Introducing Enumerable#sum for precision compensated summation and revert r54237

たしか何回か前のこの連載でも紹介したこの機能は、2.4に入りました。Enumerable#sum というメソッドが追加されており、特定の場合(浮動小数点数の配列とか)には誤差が累積しないアルゴリズムが採用されています。 

[#12142] Hash tables with open addressing

以前から提案されているハッシュの内部構造をドラスティックに変更する提案ですが、従来指摘されていた懸念点がほぼ払拭されてきていて、いよいよ入りそうかなという雰囲気です。添付されているベンチマーク結果によるとRailsアプリ(redmine)で1req/s程度の性能改善になってるぽいので、マージされるとみなさんの日々の生活への影響は大きそうですね。

[#12025] Reduce minimum string buffer size from 128 to 127

これは新機能ではないですが、文字列の初期長を128から127に削る、という変更が入りました。ここでいう長さというのはstrlen的な意味の長さなわけですが、strlenが128ということはメモリ領域としては'\0'が追加で必要なので129バイト確保されていて、これがキリが悪いのでmalloc実装によっては無駄なメモリ確保につながっていました。128バイトはjemalloc含む多くの実装でうまくハンドリングできる長さとのことで、メモリ使用量削減などが見込まれます。

[#12039] Fixnum#infinite?/Bignum#infinite or Numeric#infinte, consistent with Float#infinite? and BigDecimal#infinite?

この機能は2.4に入りました。各種数値系クラスでfinite?メソッドが追加になっています。もともとFloatにある同名メソッドと同じように振る舞い(というか整数にinfiniteは存在しないが)、したがってクラスによる場合分けをしなくて済むようになります。

コメントにも書きましたけどそういう「finite限定」という指定はたとえばJSONをエンコードするときなどに便利です。

[#12300] Allow Object#clone to take freeze: false keyword argument to not freeze the clone

この機能は2.4に入りました。オブジェクトをcloneするときにfreeze: falseとすることでfreezeされているオブジェクトのfreezeされてないコピーを得ることができます。

どういう用途かという話ですが、イミュータブルなデータ構造の場合が想定されているようです。普段はだいたいfreezeされているわけですが、ちょっとだけ変更された新しいオブジェクトを生成したいというニーズはそういう場合には頻出します(オブジェクトへの代入の代わりにコピーを作るようなイメージ)。そのようなパターンを実現するために、一旦freezeを解除したコピーを作ってから、変更を入れて、再度freezeするような流れが想定されているとのことです。

[#12275] String unescape

Stringにはdumpというメソッドがあり、危なそうな文字をエスケープしてくれたりとか、様々に便利なのですが、その逆つまりエスケープ済みの文字列を元に戻す方法が、現在evalしかありません。

しかしながらevalは文字列のエスケープを解くだけではないもっと色々な機能があるので、なんも考えずにeval使うというのは「覚悟」が必要です。そうじゃなくて、必要なのはすでにあるdumpメソッドの逆でしかないわけで、そのようなものが別に用意されているべきではないでしょうか。

という話をしたら採用されました。実装待ちです。ただ実装ちょっと面倒なので悩ましいところ。

[#11813] Extend safe navigation operator for [] and []= with syntax sugar

最近導入された&.ですが、インデクサ系のメソッドとあまりうまくフィットしないという問題があり(という主張で)、そのあたりをfoo[10]&.[12]とか書けるようにしてほしい、との提案でした。しかしながらそういうことをするArray#digというメソッドがすでにあって、言語レベルではなくAPIから解決されているので、採用されませんでした。

もし言語でなんとかすべきと思う人は、その理由をもう少し深掘りするとよさそうです。

[#12297] Ruby stdlib date can parse non-existent date with year 0

西暦0年などという年は存在しない。というバグ報告ですが、実際には0年というのは天文学では割と使われる紀年法で、Rubyで実装しているアルゴリズムは天文学の方面で利用されているもので、0年は意図的なのでした。詳細

[#12515] Create “Boolean” superclass of TrueClass / FalseClass

TrueClassとFalseClassの上位にBooleanというクラスがほしいという(失礼ながらおそらくさほど深く考えてない)リクエストなのですが、いくつかの理由で拒否されました。

まずtrueとfalseですが、振る舞いが違います。多少でも同じならスーパークラスに実装をまとめる意味もあろうかというものですが、全然違うので、OOPとして意味がない。

それから、Rubyではif文とかの中にtrueとfalse以外の任意の値をもってこれるし、しかもtrueとfalse以外にも真偽それぞれになるそれぞれの値があるので、ようするにtrueとfalseだけ特別扱いしてもあまり使い道がないです。

最後に、すでにBooleanという俺クラスを作ってる人がたくさんいて、githubで検索するだけで何千件もヒットする現状ではすでにオフィシャルに追加するには時すでに遅し。

[#12577] Is ‘$’ punctuation or not? Inconsistency between us-ascii and UTF-8

はい今月のUnicode案件ですね、今回は$という文字が、ASCIIで解釈するとpunctuation(句読点)にカテゴライズされているのに、Unicodeで解釈するとpunctuationに含まれてなくて、なんでですか? というもの。

しかしこれはASCIIのほうはPOSIXの定義する文字クラスに従っている一方でUnicodeは当然Unicodeの定義する文字クラスに従っているわけでして、べつにRubyの問題じゃなくて、元規格の時点ですでにおかしい。

なんで後発のUnicodeのほうでそんなことにしちゃったのか。POSIXにあわせておけばいいのに。

[#12578] Instance Variables Assigned In parameters ( ala Crystal? )

def initialize @thing_one , @thing_two などと書けても良いのではないかという提案がありました。個人的にはいいんじゃないのと思うんですがこれはrejectで、ようするにそういう文法をゆるしても結局そんなのinitialize以外で使うとも思えず、それなら文法をどうにかするよりもたとえばdefine_attr_initialize(:foo, :bar)みたいののほうが良い、という話でした。

[#12543] explicit tail call syntax: foo() then return

foo() then return という新たな構文を用意して、末尾呼び出しを明示するのはどうだろうかという提案です。

ただ議論になったのは、末尾呼び出しについては、世の中他の言語を見渡してみると、最適化する言語だと黙って最適化するし、しないやつは全然しないわけで、こういう「これはします」と宣言するというのは違うのではないか? 最適化を意図するなら、そもそもこんなの書かなくてもそのように動くべきでは? ということ。

それから、技術的な観点では、Cで書かれた関数呼び出しを末尾呼び出し最適化できるかどうかはABIに依存していて、必ずしも実現可能とは限らないのですが、そのような関数がコールグラフに入り混じっているような場合にも、必ず末尾呼び出し最適化される前提でコードが書かれると困る。期待された振る舞いが実現できないわけです。

他、バックトレースが壊れるとか、様々な理由が指摘されて、この提案は却下になりました。

[#7361] Adding Pathname#touch

Pathnameというのはファイルのパスを表現するクラス(URIみたいに)なわけですけれども、これにtouchというメソッドを追加できないかという提案です。機能は、touch(1)のそれを想定しています。

で、それは便利なのかもしれないが、ちょっとインターフェースとしてよくなくて、2つのことを同時にやろうとしているんですよね。普段のオペレーションでtouchしようと思う時、ファイルを作りたいという用途とタイムスタンプを更新したいという用途の二つがあり、コマンドラインでは両方touchで実現されていることかもしれないけれど、プログラムのAPIとしてはどうなのという疑問があるわけです。実際Cレベルではtouchに相当するようなPOSIX関数というようなものはない。

というわけでtouchというのは不採用になりました。

[#3187] Allow dynamic Fiber stack size

Fiberというのはスレッドみたいなやつで、実際ここでは両者は一緒くたに議論されているので、だいたいスレッドのことと思ってくれていいんですが、ようするにスレッド(等)には付随するスタック的な資源があるわけですよ。で、メモリ上に連続した領域として配置されている。

しかしメモリは無限ではないので、確保するメモリ領域をめちゃ長くするとメモリ上にあんまりたくさんスレッド(等)作れないという事態になってくるわけです。今でこそかなり緩和されてきてますが、スレッド(等)が実装された当初はみな32bit環境だったので本当に厳しいものがあり、しかし逆にそのためにスタック長を制限すると、これはこれで今度は深い関数呼び出しができなくなってくるわけで、用法に制限が出てくる。

そこで「こいつは長いやつが必要」とか「こいつはどうでもいい」とかをプログラマの側からメタ情報として与えてやるといいのでは? という発想になるわけです。

ただ難しいのはスレッド(等)はRubyのAPIでいうと作った瞬間にもう動き始めているわけで、動いてるスレッドのスタックを後から動的に伸ばしたり縮めたりするのはメチャむずいわけですよ(いわゆるスパゲッティ・スタック)。そこで取りうる対策としては

  • 作った瞬間に動き出すのを諦める
  • 動き出してもいいけど、作る時にコンストラクタにサイズを渡せる

のどっちかという話になってきて、ただ、どちらにせよ非互換は不可避なので、どうしたものですかねという感じですね。結論は出ていない。

[#10208] Passing block to Enumerable#to_h

なんかようするに配列とかそういうのをなんかの変換して最終的にHashを作りたいというニーズがある。あります。そのようなニーズを実現する「なんかブロックの評価結果でHash生成する」的なメソッドがないことがかねてから指摘されていて、この提案はto_hという既存のメソッドにブロックが渡せればいいのではという提案です。

しかし他のto_なんとかのメソッドでブロックをとるものがないので、新たなコンセプトを導入することになってしまい、これだとやりたいことに対して新機軸が大きすぎるという判断になりました。

[#12512] Import Hash#transform_values and its destructive version from ActiveSupport

まあ、上記と同じようなニーズは色々と指摘されていて、なのでActiveSupportにはすでに実装されていて、それなりに広く使われているので、もうこれでよくないか? という提案です。

この提案には「名前が気にくわない」以外の障害がとくにないので、どれかが入るとすればこれなんじゃないかなという気がします。

[#11090] Enumerable#each_uniq and #each_uniq_by

この機能は2.4に入りました。Array#uniqと同じように動くuniqが他のEnumerableにも実装されています。そこで、無限列に対して適用すると無限に処理が終わらないわけですが、Lazyにも同様に実装されていますので、そこはLazyに変換しておくことでよさげな動きになるという趣向です。

[#12589] VM performance improvement proposal

急に来た。いまのVMの内部構造というか、インストラクション・セット・アーキテクチャを変更していく提案です。

なお提案者はGCCの内部に詳しく、提案されているのもGCCで使われている低レベルIRであるRTLを使うという提案です。 RTLはレジスタベース言語なので、現在のRubyがスタックベースであるのと比べてかなりドラスチックに変えてくるわけですが、RTLの利点としてメモリトラフィックが削減できる(特にRubyの場合メソッド呼び出し時に引数をスタックに積むところが最適化できる)ということが指摘されていますし、書かれていないがおそらくGCCで使われる他の多くのテクニック、たとえば最適化とかを援用できるかもというのもあるでしょう。

提案者の興味の方向性としてはRTLベースでいくつかのインストラクションを融合してVMディスパッチを減らすことが可能なのと、RTLからバイナリへのAOTコンパイルをやっていきたいというふうに書いています。

いずれにせよ大きな話なので、今後どういう風になっていくのか。というかこれが実装されるなら卜部の仕事は不要では…

追記: GCCで使われているRTLそのものではないのでは、との指摘を受けました。つつしんで訂正させていただきます。ただレジスタベースのVMにしていこうという方向性ではあるようです。

[#12628] change block/env structs

急に来た。いまのVMの内部構造のうち、ブロックの取り回し(スタックの使い方など)を変更したという提案です。

ちょっとこれはかなりパッチがでかくて読みきれてないうちにbig bang commitされてしまったためぜんぜん読みきれてなくて厳しさを感じているわけですが、基本的には struct rb_blockというのが追加されてるぽく、これに対応してBindingや環境に手が入っているようです。

で、ごく端的に言うとスタックフレームが2word減ってて、メソッド/ブロック呼び出しごとに2wordのメモリ書き込みが削減できるので、ごく大量のブロックがやりとりされるようなケースで高速化が見込まれるということかな。と理解しました。

が、せめて1コミットで全部突っ込むんじゃなくて分割してほしかったな…

[#12574] Remove TRUE, FALSE, and NIL

TRUEっていう定数があってtrueが代入されてるんですが、さすがに誰も使ってないだろ、という話で、まあそうでしょうね。とりあえず非推奨になる流れのようです。

[#12599] For CLang, increase inline-threshold to get 7% speedup of optcarrot

clangの場合ですが、関数のインライン化閾値をコマンドラインオプションで変更することができて、いろいろと試してみるとすごく大きい閾値にしたあたりで7%くらい高速化されるケースがあるという話です。

基本的にはあまり大きい関数をインライン化しても関数呼び出しコストに比べて関数本体の実行のほうが大きいから急速に意味がなくなっていくはずなのですが、そうはなってないというあたりが興味深いですね。何が起きているのでしょうか。すごく大きい関数の突入直後にいきなりreturnするのが頻発してるとかかな?

このパッチそのものをいきなり取り込むというよりは、何がインライン化された結果が効いているのかをもう少し調査して、関数の切りだし方を変えるといいかもしれませんね。

最後に

マネーフォワードでは、ruby-coreが気になって夜も眠れないエンジニアを募集しています。
ご応募お待ちしています。

【採用サイト】
マネーフォワード採用サイト
Wantedly | マネーフォワード

【プロダクト一覧】
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad
家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Android
クラウド型会計ソフト『MFクラウド会計』
クラウド型請求書管理ソフト『MFクラウド請求書』
クラウド型給与計算ソフト『MFクラウド給与』
経費精算システム『MFクラウド経費』
消込ソフト・システム『MFクラウド消込』
マイナンバー対応『MFクラウドマイナンバー』
創業支援トータルサービス『MFクラウド創業支援サービス』
お金に関する正しい知識やお得な情報を発信するウェブメディア『マネトク!』

Pocket