Perlと出会い、Perlを作る

     株式会社ミクシィ
 Masaaki Goshima(@goccy54)
Perl作成を決意
• 「深く理解したいなら作る」という
  モットーのもと、学ぶなら作って学びた
  い!
 – どうせなら、スクリプト言語界最速のPerlを作りたい
gperl
• 世界一高速なPerl処理系を目指している

• 大目標はPerl5.16とある程度互換性を持
  ち, mixiのホーム画面を表示させるスクリプ
  ト(home.pl)を動かすこと

• C++でスクラッチから実装
 – 依存ライブラリは極力入れない方針
  • (readlineも自作)
アジェンダ
•   gperlの概要と性能評価
•   高速化技術あれこれ
•   高速化のために断念したPerlの仕様
•   まとめ
gperlの概要(Speed)
• 導入済みの高速化技術
 – asmベースの2番地コード命令
 – Register型VM
 – Direct Threaded Codeを用いた
   高速関数呼び出し
 – 引数オブジェクトの事前構築
 – NaN-boxingによる型検査
 – アノテーションによるStatic Typingの実現
 – JIT Compile
gperlの概要(Syntax)
• サポートされている文法やデータ構造
 – データ型 :
   int, double, String, Array(Ref), Hash(Ref), CodeRef, B
   lessedObject
 – ループ: for, while, foreach
 – 条件文: if-elsif-else
 – 関数呼び出し(再帰処理可)とクラス作成(package)
 – 演算子(一部) : +, -, *, /, +=, -=, ++, --
   , <<, >>, <, >, <=, >=, !=, ==
 – その他: ローカル変数, 複数変数の宣言と代入など
性能評価(1/3)
                      Fibonacci数列の計算
N=35
       Language          time(real)[sec]   X(倍率)
        gperl(JIT)            0.10         X91.2
                                                         測定環境
  LuaJIT(2.0.0-beta10)        0.12         X76.0

         v8(0.8.0)            0.18         X50.6      MacOSX(10.7.4)
                                                    2.2GHz Intel Core i7
   gperl(StaticTyping)        0.44         X20.7       Memory: 8GB
           gperl              0.46         X19.8   Darwin Kernel Version
                                                       11.4.0 x86_64
       Konoha(1.0)            0.52         X17.5

       PyPy(1.9.0)            0.53         X17.2

  SpiderMonkey(1.8.5)         2.17          X4.2

        Lua(5.2.1)            2.82          X3.2         測れるもの
       Ruby(1.9.3)            3.65          X2.5
                                                        数値演算速度
       Python(2.7.3)          4.32          X2.1      関数呼び出し速度
                                                      VM設計の良し悪し
       Perl(5.16.0)           9.12          X1.0
性能評価(2/3)
                  たらい回し関数の計算
(x,y,z) = (13, 6, 0)
       Language           time(real)[sec]   X(倍率)
        gperl(JIT)             0.29         X102.0
                                                           測定環境
        v8(0.8.0)              0.41         X72.1
   LuaJIT(2.0.0-beta10)                                 MacOSX(10.7.4)
                               0.46         X64.3
                                                      2.2GHz Intel Core i7
    gperl(StaticTyping)        1.24         X23.9        Memory: 8GB
                                                     Darwin Kernel Version
       Konoha(1.0)             1.38         X21.4        11.4.0 x86_64
          gperl                1.56         X19.0
       PyPy(1.9.0)
                               4.30          X6.9

   SpiderMonkey(1.8.5)         5.70          X5.2          測れるもの
        Lua(5.2.1)             5.97          X5.0
                                                         複数引数の処理
       Ruby(1.9.3)             7.63          X3.9         数値演算速度
                                                        関数呼び出し速度
      Python(2.7.3)            17.18         X1.7       VM設計の良し悪し
       Perl(5.16.0)            29.59         X1.0
性能評価(3/3)
                         binary-trees
N=15
       Language          time(real)[sec]   X(倍率)
         v8(0.8.0)
                              0.32         X35.8         測定環境

  LuaJIT(2.0.0-beta10)        1.29          X8.9      MacOSX(10.7.4)
       Konoha(1.0)            1.46          X7.9    2.2GHz Intel Core i7
                                                       Memory: 8GB
       PyPy(1.9.0)            1.98          X5.8   Darwin Kernel Version
       Python(2.7.3)          2.25          X5.1       11.4.0 x86_64
  gperl(Static Typing)        3.97          X2.9

           gperl              4.06          X2.8

       Ruby(1.9.3)            5.23          X2.2

  SpiderMonkey(5.7.3)         5.73          X2.0         測れるもの
        Lua(5.2.1)            7.56          X1.5          GCの速度
       Perl(5.16.0)           11.47         X1.0
高速化技術あれこれ
引数オブジェクトの事前構築(1/2)
• 引数格納用の配列を事前構築して高速化
 – Perlは, シンプルに実装すると関数呼び出しのた
   びに引数用の配列オブジェクトが作られる
  • 関数呼び出しの度に配列を生成すると遅い


 – 通常、引数の数は一定数以下に抑えられてコー
   ディングされるため、あらかじめ引数用の配列を
   確保しておくことで対処する
引数オブジェクトの事前構築(2/2)
                                        関数呼び出しスタックの一つ一つに対応して




                           ・・・・
関数呼び出しスタック                            引数用の配列を事前に作り、引数はそこに積まれる
スタックの伸びる方向




                   3
                       4
                                  5
                                            1 :subf {
                                            2 : my $a = $_[0];
                   2
                       3
                                  4
                       引数用の配列               3 : my$b= $_[1];
                                            4 : my$c= $_[2];
                   1
                       2
                                  3



                                            5 : if ($a > 10) {
                                            6 : return 1;
                                            7 : }
                                            8 : returnf(++$a, ++$b, ++$c);
                                            9 :}
       関数が呼ばれるとstack_topが一つ上がる              10 :f(1, 2, 3);
          関数から抜けると一つ下がる
             しかしPerlには、引数が一定数以下に抑えられるという常識が通じない
             仕様があるため、一筋縄ではいかない =>後述
NaN-boxingによる型検査(1/2)
• Perlのような動的型付け言語は、変数の型を保
  存する仕組みが必要
 – 通常は、値(int, double, boolean)であっても、オブ
   ジェクトとして扱い、型情報を持たせる(boxing)
 – しかしboxingしてあると、計算の時に毎回オブジェク
   トから値を取り出す操作(unboxing)が入るため遅い.
   また、全てオブジェクトにするためメモリ効率も悪
   くなる
                     case ADD:
 structIntObject {   Object *a = reg[0];
                     Object *b = reg[1];
 inttype; //型情報      if (a->type == TypeInt&&b->type == TypeInt) {
 intdata; //実際の値          ((IntObject *)a)->data += ((IntObject *)b)->data;
 };                    } else if (a->type == TypeString&&b->type == TypeString) {
                          ……
                       }
NaN-boxingによる型検査(2/2)
• NaN-boxingは、double型のNaN領域に型
  情報を埋め込む技術
  – unboxingと同じように扱えて、型情報も持た
    せられる               unboxingされている
                case ADD:                                 のに型検査できる
union{          switch (typecheck(reg[0], reg[1])) {
intidata;       caseInt_Int:
double ddata;        reg[0].idata += reg[1].idata;//データに直にアクセスできる
char *sdata;    break;
void *odata;      case String_String:
};                   …..
                  }

しかしPerlには、関数呼び出し時に渡される変数は全てリファレンス
として扱われるという仕様があるため、一筋縄ではいかない =>後述
アノテーションによる
  Static Typing/JIT Compileの実現
• 大規模開発を行う場合、型は静的に決定できる
  場合がほとんど
 – 型安全性が損なわれるとバグのもとに
 – そもそも、開発するときに型のイメージはあるはず


• 型を静的に決められるケースが多いなら、その
  恩恵を受けようじゃないか
 – アノテーションを付加することで、静的型付け用の命
   令を生成したり, JIT Compileできるようにする
アノテーションによる
   Static Typing/JIT Compileの実現
• 関数の宣言前に、”#@static_typing” or
  “#@jit_safe”と記述することで、静的型付け用の命
  令やJIT用の命令が生成される
 – #~とすることで、Perlとの互換性を保つ
                 1: #@static_typing
                 2: subfib {
型が決まらないと処理できない   3: if ($_[0] < 2) {
    命令において、      4: return 1;
初めて処理した時に分かった型   5: } else {
    を覚えておき、      6:         return fib($_[0] - 1) + fib($_[0] - 2);
 2回目からは分かった型用の   7: }
    命令で処理する      8:}
                 9:print(fib(35), "n");
高速化のために断念した(い)機能
高速化のために断念した(い)機能
1. 関数引数にあるリストの展開
2. デフォルトでのスカラー変数の参照渡し
1.関数引数にあるリストの展開
• Perlでは, 関数の引数にリストがあるときは, 中
  身を展開して一つの配列にしてから関数に渡す
 – 配列結合のSyntax SugarがPerlでは(@a, @b)で表現
   でき、引数では常に有効になる
      subf{
      my@args= @_;
      print $args[0], "n"; # $args[0] == 1
      print $args[1], "n"; # $args[1] == 2
      }
      my@a = (1, 2, 3);
      my @b= (4, 5, 6);
      f(@a, @b);
1.関数引数にあるリストの展開
• gperlではあらかじめ引数の数を想定して引数ス
  タックのサイズを決めているため、引数の数が
  多いとスタックを拡張しなければならない
                                          gperl内部では@aも一つの
subf{                                    配列オブジェクトとして扱わ
my@args= @_;                             れているため、そのままだと
print $args[0], "n"; # $args[0] == @a   $args[0]の値は@aになり、
print $args[1], "n"; # $args[1] == @b   $args[1]の値は@bになって
}                                                 しまう
my@a = (1, 2, 3);
my @b= (4, 5, 6);
                                         これを簡単に避けるために、
f(@a, @b);
                                         リストを展開・結合したい場
                                         合は、f((@a, @b))のように呼
                                          び出すルールを決めている
2.デフォルトでのスカラー変数の
        参照渡し
• Perlでは、関数の引数は全て参照渡しになるの
  で、明示的にリファレンスにしなくとも、値の
  書き変えが可能

subf{                           NaN-boxingではこの挙動を
    $_[0] = 3;                  再現できないため、
}                  $_[0]には$aの   スカラー変数のデフォルトでの
                  リファレンスが入っ     参照渡しは未サポート
my $a = 1;        ているので、値を書
f($a);            き変えると元の値も     $aを使ってリファレンスを
print $a, "n”;     書き変わる       渡すことを明示すれば、
                                そのときにboxingを行って対処する
まとめ
• VM、GCやJITなど、ベースとなる技術の
  設計や実装はできてきた
• 今後の予定 (文法や機能の充実)
 – 後置文, 三項演算子, format, 正規表現 ,
   map/grep, eval, use/FFI, 継承, 括弧の省略など
 – ドキュメントの整備

• 研修時に書いたPerlスクリプトがやっと動く
  ように・・
 – 本当の意味で研修内容を理解できた・・・!?
https://github.com/goccy/gperl
Perlと出会い、Perlを作る

Perlと出会い、Perlを作る

  • 1.
    Perlと出会い、Perlを作る 株式会社ミクシィ Masaaki Goshima(@goccy54)
  • 2.
    Perl作成を決意 • 「深く理解したいなら作る」という モットーのもと、学ぶなら作って学びた い! – どうせなら、スクリプト言語界最速のPerlを作りたい
  • 3.
    gperl • 世界一高速なPerl処理系を目指している • 大目標はPerl5.16とある程度互換性を持 ち, mixiのホーム画面を表示させるスクリプ ト(home.pl)を動かすこと • C++でスクラッチから実装 – 依存ライブラリは極力入れない方針 • (readlineも自作)
  • 4.
    アジェンダ • gperlの概要と性能評価 • 高速化技術あれこれ • 高速化のために断念したPerlの仕様 • まとめ
  • 5.
    gperlの概要(Speed) • 導入済みの高速化技術 –asmベースの2番地コード命令 – Register型VM – Direct Threaded Codeを用いた 高速関数呼び出し – 引数オブジェクトの事前構築 – NaN-boxingによる型検査 – アノテーションによるStatic Typingの実現 – JIT Compile
  • 6.
    gperlの概要(Syntax) • サポートされている文法やデータ構造 –データ型 : int, double, String, Array(Ref), Hash(Ref), CodeRef, B lessedObject – ループ: for, while, foreach – 条件文: if-elsif-else – 関数呼び出し(再帰処理可)とクラス作成(package) – 演算子(一部) : +, -, *, /, +=, -=, ++, -- , <<, >>, <, >, <=, >=, !=, == – その他: ローカル変数, 複数変数の宣言と代入など
  • 7.
    性能評価(1/3) Fibonacci数列の計算 N=35 Language time(real)[sec] X(倍率) gperl(JIT) 0.10 X91.2 測定環境 LuaJIT(2.0.0-beta10) 0.12 X76.0 v8(0.8.0) 0.18 X50.6 MacOSX(10.7.4) 2.2GHz Intel Core i7 gperl(StaticTyping) 0.44 X20.7 Memory: 8GB gperl 0.46 X19.8 Darwin Kernel Version 11.4.0 x86_64 Konoha(1.0) 0.52 X17.5 PyPy(1.9.0) 0.53 X17.2 SpiderMonkey(1.8.5) 2.17 X4.2 Lua(5.2.1) 2.82 X3.2 測れるもの Ruby(1.9.3) 3.65 X2.5 数値演算速度 Python(2.7.3) 4.32 X2.1 関数呼び出し速度 VM設計の良し悪し Perl(5.16.0) 9.12 X1.0
  • 8.
    性能評価(2/3) たらい回し関数の計算 (x,y,z) = (13, 6, 0) Language time(real)[sec] X(倍率) gperl(JIT) 0.29 X102.0 測定環境 v8(0.8.0) 0.41 X72.1 LuaJIT(2.0.0-beta10) MacOSX(10.7.4) 0.46 X64.3 2.2GHz Intel Core i7 gperl(StaticTyping) 1.24 X23.9 Memory: 8GB Darwin Kernel Version Konoha(1.0) 1.38 X21.4 11.4.0 x86_64 gperl 1.56 X19.0 PyPy(1.9.0) 4.30 X6.9 SpiderMonkey(1.8.5) 5.70 X5.2 測れるもの Lua(5.2.1) 5.97 X5.0 複数引数の処理 Ruby(1.9.3) 7.63 X3.9 数値演算速度 関数呼び出し速度 Python(2.7.3) 17.18 X1.7 VM設計の良し悪し Perl(5.16.0) 29.59 X1.0
  • 9.
    性能評価(3/3) binary-trees N=15 Language time(real)[sec] X(倍率) v8(0.8.0) 0.32 X35.8 測定環境 LuaJIT(2.0.0-beta10) 1.29 X8.9 MacOSX(10.7.4) Konoha(1.0) 1.46 X7.9 2.2GHz Intel Core i7 Memory: 8GB PyPy(1.9.0) 1.98 X5.8 Darwin Kernel Version Python(2.7.3) 2.25 X5.1 11.4.0 x86_64 gperl(Static Typing) 3.97 X2.9 gperl 4.06 X2.8 Ruby(1.9.3) 5.23 X2.2 SpiderMonkey(5.7.3) 5.73 X2.0 測れるもの Lua(5.2.1) 7.56 X1.5 GCの速度 Perl(5.16.0) 11.47 X1.0
  • 10.
  • 11.
    引数オブジェクトの事前構築(1/2) • 引数格納用の配列を事前構築して高速化 –Perlは, シンプルに実装すると関数呼び出しのた びに引数用の配列オブジェクトが作られる • 関数呼び出しの度に配列を生成すると遅い – 通常、引数の数は一定数以下に抑えられてコー ディングされるため、あらかじめ引数用の配列を 確保しておくことで対処する
  • 12.
    引数オブジェクトの事前構築(2/2) 関数呼び出しスタックの一つ一つに対応して ・・・・ 関数呼び出しスタック 引数用の配列を事前に作り、引数はそこに積まれる スタックの伸びる方向 3 4 5 1 :subf { 2 : my $a = $_[0]; 2 3 4 引数用の配列 3 : my$b= $_[1]; 4 : my$c= $_[2]; 1 2 3 5 : if ($a > 10) { 6 : return 1; 7 : } 8 : returnf(++$a, ++$b, ++$c); 9 :} 関数が呼ばれるとstack_topが一つ上がる 10 :f(1, 2, 3); 関数から抜けると一つ下がる しかしPerlには、引数が一定数以下に抑えられるという常識が通じない 仕様があるため、一筋縄ではいかない =>後述
  • 13.
    NaN-boxingによる型検査(1/2) • Perlのような動的型付け言語は、変数の型を保 存する仕組みが必要 – 通常は、値(int, double, boolean)であっても、オブ ジェクトとして扱い、型情報を持たせる(boxing) – しかしboxingしてあると、計算の時に毎回オブジェク トから値を取り出す操作(unboxing)が入るため遅い. また、全てオブジェクトにするためメモリ効率も悪 くなる case ADD: structIntObject { Object *a = reg[0]; Object *b = reg[1]; inttype; //型情報 if (a->type == TypeInt&&b->type == TypeInt) { intdata; //実際の値 ((IntObject *)a)->data += ((IntObject *)b)->data; }; } else if (a->type == TypeString&&b->type == TypeString) { …… }
  • 14.
    NaN-boxingによる型検査(2/2) • NaN-boxingは、double型のNaN領域に型 情報を埋め込む技術 – unboxingと同じように扱えて、型情報も持た せられる unboxingされている case ADD: のに型検査できる union{ switch (typecheck(reg[0], reg[1])) { intidata; caseInt_Int: double ddata; reg[0].idata += reg[1].idata;//データに直にアクセスできる char *sdata; break; void *odata; case String_String: }; ….. } しかしPerlには、関数呼び出し時に渡される変数は全てリファレンス として扱われるという仕様があるため、一筋縄ではいかない =>後述
  • 15.
    アノテーションによる StaticTyping/JIT Compileの実現 • 大規模開発を行う場合、型は静的に決定できる 場合がほとんど – 型安全性が損なわれるとバグのもとに – そもそも、開発するときに型のイメージはあるはず • 型を静的に決められるケースが多いなら、その 恩恵を受けようじゃないか – アノテーションを付加することで、静的型付け用の命 令を生成したり, JIT Compileできるようにする
  • 16.
    アノテーションによる Static Typing/JIT Compileの実現 • 関数の宣言前に、”#@static_typing” or “#@jit_safe”と記述することで、静的型付け用の命 令やJIT用の命令が生成される – #~とすることで、Perlとの互換性を保つ 1: #@static_typing 2: subfib { 型が決まらないと処理できない 3: if ($_[0] < 2) { 命令において、 4: return 1; 初めて処理した時に分かった型 5: } else { を覚えておき、 6: return fib($_[0] - 1) + fib($_[0] - 2); 2回目からは分かった型用の 7: } 命令で処理する 8:} 9:print(fib(35), "n");
  • 17.
  • 18.
  • 19.
    1.関数引数にあるリストの展開 • Perlでは, 関数の引数にリストがあるときは,中 身を展開して一つの配列にしてから関数に渡す – 配列結合のSyntax SugarがPerlでは(@a, @b)で表現 でき、引数では常に有効になる subf{ my@args= @_; print $args[0], "n"; # $args[0] == 1 print $args[1], "n"; # $args[1] == 2 } my@a = (1, 2, 3); my @b= (4, 5, 6); f(@a, @b);
  • 20.
    1.関数引数にあるリストの展開 • gperlではあらかじめ引数の数を想定して引数ス タックのサイズを決めているため、引数の数が 多いとスタックを拡張しなければならない gperl内部では@aも一つの subf{ 配列オブジェクトとして扱わ my@args= @_; れているため、そのままだと print $args[0], "n"; # $args[0] == @a $args[0]の値は@aになり、 print $args[1], "n"; # $args[1] == @b $args[1]の値は@bになって } しまう my@a = (1, 2, 3); my @b= (4, 5, 6); これを簡単に避けるために、 f(@a, @b); リストを展開・結合したい場 合は、f((@a, @b))のように呼 び出すルールを決めている
  • 21.
    2.デフォルトでのスカラー変数の 参照渡し • Perlでは、関数の引数は全て参照渡しになるの で、明示的にリファレンスにしなくとも、値の 書き変えが可能 subf{ NaN-boxingではこの挙動を $_[0] = 3; 再現できないため、 } $_[0]には$aの スカラー変数のデフォルトでの リファレンスが入っ 参照渡しは未サポート my $a = 1; ているので、値を書 f($a); き変えると元の値も $aを使ってリファレンスを print $a, "n”; 書き変わる 渡すことを明示すれば、 そのときにboxingを行って対処する
  • 22.
    まとめ • VM、GCやJITなど、ベースとなる技術の 設計や実装はできてきた • 今後の予定 (文法や機能の充実) – 後置文, 三項演算子, format, 正規表現 , map/grep, eval, use/FFI, 継承, 括弧の省略など – ドキュメントの整備 • 研修時に書いたPerlスクリプトがやっと動く ように・・ – 本当の意味で研修内容を理解できた・・・!?
  • 23.