例のleftpad, GCを虐めるためとかコンパイラの最適化を確認するために用意する、「無駄に一時オブジェクト量産するクソコードの典型例」みたいな実装なので、こんな小さい関数のために、信頼できない人のコードを、実装を見るでも無く、依存性追加してたってことで、
— INADA Naoki (@methane) March 24, 2016
ここから始まる一連の、モジュールの依存性に関する議論はなかなか興味深いが、自分的に気になったのは以下の一節
GCを虐めるためとかコンパイラの最適化を確認するために用意する、「無駄に一時オブジェクト量産するクソコードの典型例」みたいな実装
ソースを見てみようか。
left-pad/index.js at 0e04eb4da3a99003c01392a55fa2fdb99db17641 · azer/left-pad · GitHub
なるほど一見するとクソコードにみえる。確かに、過去のJS処理系では str = ch + str
のような文字列連結を大量に行うコードは非常に遅かった。このため多数の文字列を連結する場合、Arrayに結合する文字列をpushし、最後にjoinするという方法で高速化を図っていた。
しかし、それはv8登場以前の話である。今では文字列連結は処理系によって強烈な最適化を施され、Arrayを使うよりも高速に動作するようになっている。内部的には string-builder によって無駄なオブジェクトを生成する事なく文字列を構築するので、現在のJSでは一目でクソと言うほどクソではないはずだ。
と思ったのでベンチとってみた。処理系はnode v4.3.1
- leftpad1、例のnpmモジュールのコード
- leftpad2、現状ES.next proporsalのString.prototype.repeatで一気にpad文字列を作るコード
- leftpad3、arrayに詰めてjoinするコード。ただしpushするのではなくES6のArray.fillで一気にpad文字列を作成するようにした(whileループでpushするよりも高速)
結果
padding len | 4 | 8 | 16 | 32 | 64 | 128 |
---|---|---|---|---|---|---|
leftpag1 concat with + | 13 | 80 | 194 | 328 | 607 | 1138 |
leftpad2 repeat and substring | 92 | 103 | 202 | 228 | 305 | 463 |
leftpad3 array fill and join | 16 | 517 | 677 | 1012 | 1683 | 3007 |
数値は処理に要した時間、単位はミリ秒
- Arrayでjoinは今となっては遅い。
- lenが16以下の場合は+で接続する方が速い
- lenが16より大きい場合はrepeatで一気にpad文字列を作るのが有利
- len = 4のとき、leftpad1/3はほとんどの場合でstrをそのまま返すのでleftpad2より圧倒的に高速
結論
- シンプルで高速なrepeatを使おう!