JVM(HotSpot) VS. Dart VM
環境は、core i7 x86(ia32)です。
JVMは、HotSpot(OpenJDK7)と比較します。
Dart VM(Dart VMのbleeding edge)
性能の比較
下記URLでは、Dart VMとJVM間の性能比較を行っています。
Dart vs Java (cont’d) — Richards and Tracer
Dart vs Java — the DeltaBlue Benchmark
benchmarkgames
性能のみ比較。2013/10
Benchmark Time
fannkuch-redux ±
spectral-norm ±
fasta 2×
n-body 2×
mandelbrot 2×
binary-trees 2×
fasta-redux 3×
pidigits 10×
reverse-complement 16×
regex-dna 31×
binary-trees
Dart VMと-new-gen-heap-size=128Mを指定すれば、ほぼ等速。
newしすぎなので、gcのオーバーヘッドを取り除けばほぼ等速。
同じheapsizeで比べた場合、HotSpotのほうが20%くらい速い。
nbody
JVMと比較してnbodyが遅い(4x)のは、doubleをメンバに持つクラスだからでしょう。
Dart VMは、double型のメンバにアクセスするのが、JVMと比較して遅いはず。
pigidits
OSRを実装して性能向上
Java7版はgmpをつかって多倍長演算を高速化しているため非常に速い。
Java7のBiginteger版と比較すると、Dart VMとほぼ等速。
reverse-complement
dartのIOの遅さが際立ってる。。
stdinからreadして、全部List<int>にしてるのがよくないのかも。
regex-dna
dartにはチューニングされた正規表現エンジンが標準搭載されていないからです。
Irregexpを使うように変更すれば、なかなかのパフォーマンスになると思います。
アーキテクチャの比較
HotspotとDart VMのアーキテクチャは非常に似ています。
JVMの性能面での利点
primitive型がたくさん
マルチスレッドでconcurrentなJITコンパイルと、parallelなGC
静的型付けなので、クラスのフィールドアクセスが高速。
Dart VMの性能面での利点
JITコンパイルとGCは完全に逐次処理であり、
1つのIsolateが1つのThreadなので、(ライブラリ内に)Lockが存在しない。スループット上有利かも。
Dart VMは動的型付けなので、型の解決はすべてruntime feedbackで行う。
メソッドのオーバーロードをサポートされておらず、ライブラリ内でメソッドの動的なdispatchが少なくなるように抑制されている。
primitive型
JVMはbyte int long float double等がprimitive型です。
dartの場合、int doubleはnum型のオブジェクトになります。
そのため、dartはunboxing boxing処理が適時入ります。
dartのintは、31bit 63bit 多倍長に自動的にscaleします。
intが多倍長でない場合、JVMのprimitive型とほぼ同等の速度で計算できるはず。
dart vmは内部でSmi型を使用し、ia32の場合31bit整数、x64の場合63bit整数を高速に計算できます。
そのため、
ia32の場合、Smi(31bit integer)は非常に高速で、JVMのprimitiveと同等の速度のはずです。
ia32の場合、Mint(63bit integer)は、なかなか高速ですが。ほぼxmmレジスタで計算を行うはず。
x64の場合、Smi(63bit)は非常に高速です。
クラスのフィールドアクセス
Dart VMはdynamic型が前提であるため、クラスの各フィールドもdynamic型と仮定します。
そのため、クラスのフィールドからgetFieldした後に、必ず型チェックのオーバーヘッドが発生します。
最近では、クラスのフィールドへのputFieldの際にtype feedbackを行い、
フィールドごとに何のクラスが入っているのか型情報を収集するようになりました。
JITコンパイル時にはfieldごとの型情報を使用し、型チェックを除去したり、getFieldを型伝搬の起点にできるようになりました。
また、後述のWBをある程度スキップできます。
Collectionの比較
JVMは、[]でprimitive型の配列が使えます。
List型等を宣言すると、Listの各要素をGCがたどらないといけないし、
Listの各要素にassignした際に、store burrierする必要があります。
Dart VMの場合、[]による配列の宣言はありません。
代わりに、Listとtypeddataパッケージがあります。
Listは、JVMのListと同様です。
typeddataは、bytearray型の配列です。
例えば、Float64Array型は、JVMのdouble[]と同じで、性能もほぼ同じはずです。
Dart VMは、primitive型と配列[]がない代わり、typeddataというパッケージで代替します。
GCの比較
JVMは世代別GC、もしくはg1gcです。
Dart VMは世代別GCですが、parallelでもconcurrentでもありません。GCがisolate内のすべての処理をblockingします。
Dart VMの世代別GCは、newgenがcopy gc、oldgenがmark&sweepです。
Dart VMのnewgenは5命令程度でallocateでき、その後クラスの初期化に3-4命令であるため、
JVMのallocateとほぼ等速のはずです。
JVMはTLABにallocateする場合、lock不要で10命令程度だとか。
上記GCは、どれもwrite barrier(WB)を行うタイプです。
JVMのWBは、平均して2~3%のオーバーヘッドが発生するようですが、
Dart VMはそれ以上のオーバーヘッドが発生しているはずです。
例をあげると、クラスのフィールドにアクセスする場合、
JVMの場合、class descriptorでどのフィールドがprimitive型か分かるため、
putFieldの際にprimitive型のWBをスキップできます。
しかし、Dart VMはdynamic型が前提であるため、すべてのputFieldでWBを行うかどうか判定しています。
また、WBをスキップできるのはSmi型のみであるため、その他(たとえばdouble)はWBの対象になります。
メモリ使用量の比較
computer language benchmark gamesを参照すると、
Dartのほうが少ないようです。
しかし、heapの最大値が、JVMとDart VMで異なるからでしょうけど。。
使用できるheap sizeを同じにして、GCされた量を比較すべきなのかな?
JVMは、オブジェクトのヘッダサイズが3wordだっけ?2wordだっけ?
Dart VMのほうは、2wordです。
どっちも2wordかな?
JVMはprimitive型の場合、オブジェクトのヘッダサイズは加算されません。
Dart VMは、Smi型である限り、ヘッダサイズは加算されません。
起動時間の比較
おそらくDart VMのほうが速いはず。
Dart VMは、coreのライブラリを読み込んだVMの内部状態をDart(約7M)内部にシリアライズしておき、起動時にデシリライズして読み込みます。
JVMは起動時にjarからclassを読み込みますが、再読み込みや検索、scan/parseが発生しておりオーバーヘッドになっています。
その他、これからの追記事項(アーキテクチャ上の比較ではなく、頑張ればなんとかなりそうな項目)
最適化の比較 optoとFlowGraphOptimizer
コード生成器の比較
intrinsicsの比較(Dart VMは今のところAllocateくらい)
Stringの比較(16bit固定のJVMとlatin1のDart VM、16byteもあるよ)
メモリの比較(Compressed Oops, Compressed Stringなど、64bitでの比較など)
追記
SIMD並列化(JVMのみ)
JVMはSuperword Level Parallelによる自動ベクトル化をサポートしています。
また、copyやcompareをSSE4.2やAVX, AVX2を使用した高速なバージョンを生成でき、非常に高速。
Dart VMはtyped_dataにSIMD命令をラップしたAPIを定義しており、これを使えば高速なコードが生成できる。要SIMDの知識。
On-Stack ReplacementとEscape Analysisのサポート。
Dart VMはOn-Stack ReplacementとEscape Analysis相当の最適化をサポートしています。
JVMがEscape Analysis + Scalar Replacement of Aggregateのセットで、
EscapeしないオブジェクトをHeapではなく、Stackに生成します。
同様のことをDart VMは Store Sinkingという最適化で行います。
Dart VMの苦手なこと
公式ページにもまとまっています。
31bit 63bit境界の処理は苦手です。
特に、64bitの即値を扱うのは圧倒的に遅いです。
なぜなら、64bitの即値はJVMのようにprimitiveではなく、BigIntだからです。
あとは、JVMと比較して、doubleをメンバに持つクラスを多用すると遅いんじゃないでしょうか。
JVMはprimitiveだけど、Dart VMはObjectなので、boxing, unboxingのオーバーヘッドがある
doubleをフィールドに持つ場合、WBの対象になる。
doubleの配列を多用する場合、それほど性能差はでないはず。
Dart VMはFloat64List型で、primitiveなdoubleの配列を使用でき、上記のboxing/unboxing WBのオーバーヘッドは存在しない。
- 最終更新:2014-02-22 00:04:05