この記事は衝動的な動機によって書かれており、事実を正確に捉えるのに必要な証拠に欠けていることを予め断っておきたい。
また、分かりやすい数字というものは人間の良からぬ想像力を掻き立てる災いのもとになることも思い出して欲しい。この記事にはなるべく事実だけを書くことにして、自分の推測は「〜かも知れない」とか「〜と思う」と書くことにする。この記事を読まれたみなさんが何らかの推論をコメントやツイッターに書き散らしたい衝動にかられた時は、ぜひ同じことを実践して欲しい。
GAS のランタイムで V8 エンジンが使えるようになった
ソース
https://developers.google.com/apps-script/guides/v8-runtime?hl=ja#enabling_the_v8_runtime
Google App Script (以降 GAS) とは、Google スプレッドシートや Gmail など複数の Google サービスのデータにアクセスできるスクリプト実行環境のプラットフォームである。スクリプトとは、実際には JavaScript のことである。ユーザーが .gs
という拡張子で JavaScript を書くと、それを Google のサーバー上で実行してくれる。これが無料で使えるとあって、昔から業務効率化などの目的でよく利用されてきた。
しかし、つい先日までの GAS は大きな欠点を抱えていた。使える JavaScript の文法が古いのだ。ここ数年で JavaScript (正確には ECMAScript というべきかも知れないが)の文法は大きく変わっている。例えば元々 var
しかなかった変数宣言は var
let
const
の三つに増え、用途に応じて使い分けられるようになった(そして今では var
はアンチパターンとすら言われるようになった!)。2010年代前半はこれらの機能に対応しているブラウザとそうでないブラウザとがあり、開発者はブラウザの差異に大いに苦しめられたが、6 to 5 (現 Babel) の普及によって状況は一変した。モダンブラウザの普及を待つまでもなく、開発者らは新しい文法の JavaScript を書く自由を手に入れたのだ。そうして Babel は現代 JavaScript を語る上で欠かせないエコシステムの根幹となった。少し話が逸れたが、要するに、開発者はとにかくモダンな JavaScript を書きたくて仕方ないのだという性質を理解していただければ問題ない。
GAS の話に戻る。以前の GAS は Rhino という Mozilla 製の JavaScript インタプリタを利用していたそうだ。インタプリタとはプログラミング言語を実行してくれるソフトウェアの一形態である。一方、昨今モダンブラウザと呼ばれるブラウザに採用されているのは V8 (ブイエイトと読めばいいと思う)という Google 謹製の JavaScript エンジン(こちらはインタプリタと呼ぶべきではない)である。ここでは V8 と Rhino が実際にどう違っているかを詳しく説明することはしない。ここで重要なのは次の一点だけだ。 Rhino は新しい JavaScript の文法に対応していないのである。つまり、開発者は GAS のために古い文法の JavaScript を書かなければならなかった。勘の言い方は、前段に出てきた Babel の存在を思い出すだろう。確かに Babel を使えば GAS の開発でも新しい文法の JavaScript を書くことが可能だ。しかし、GAS の大きな特徴であるウェブ上でコードが書けることと Babel が相入れなかったのだと思う。他の開発者たちがどうしていたのか僕はよく知らないのだが、少なくとも僕は Babel の導入を諦めてしまった意志の弱い開発者のうちの一人だ。
そんな中、熱いニュースが飛び込んできた。ついに、GAS のランタイムで V8 エンジンが使えるようになったのだ。具体的にいうと、実行環境を V8 にするモードが提供され、任意でオンに出来るようになった。今はまだ、新規作成された App Script はデフォルトでこのモードがオフになっているが、これは近い将来変更されるかも知れない。互換性の問題ですぐにはオンにできないプロジェクトもあるだろうが、互換性がないことにゆるぎない自信を持てるほどにプロジェクトが小さければ、後述する理由によって V8 モードをオンにすることを推奨したい。さて、ここまでが前置である。
実行速度は速くなるのか?
この件に関して知り合いのプログラマーの方が「実行速度も速くなるのかな?」というツイートをしているのを見かけて、僕はこう思った。全体の実行速度は JavaScript を実行しているインスタンスにもよるから一概には言えない。確かに V8 は様々な面で JavaScript の実行を最適化している(と聞いたことがある)ので、純粋なコードの実行速度を比較すれば有利になるはずだ。
この時、僕は何か不思議な引っ掛かりを感じていた。うまく言語化出来ないのだが、それは実験してみたら面白い結果が出るんじゃないか、という期待感のようなものだったと思う。 App Script の編集画面から次のメニューを使ってランタイムを切り替えられたので、実際に速度比較を行ってみることにした。
function speedTest() {
var now = Date.now();
var sum = 0;
for (var i = 0; i < 1000 * 1000; i++) {
sum += Math.random();
}
var time = Date.now() - now;
Logger.log('time ' + time);
}
簡単なコードだが一応説明しておくと、1M 回乱数を生成して足し合わせるのにかかった処理時間を出力している。Date.now()
は現在時刻をミリ秒単位の Unix エポックで返してくれる組み込み関数で、もちろん GAS でも使える。 Logger.log
は GAS の標準的なログ出力関数だ。
さて、結果は次のようになった。
V8 オフ:5900ms
いろいろ思うところはあるが、 確かに V8 に切り替えたらものすごい速くなっている。 その後、ランタイムを切り替えながら2〜3回試してみたが、およそ 50% 程度の誤差はあるものの、そこまで大きな解離はなかった。平均や分散をちゃんと求めてはいないので、興味のある人は実験してみて欲しい。
しかし、仮にも同じコードで、ランタイムが違うというだけで、これだけの違いが生まれるだろうか?
僕がパッと思いついたことは試してみたが、他の要因が思いついた人はぜひレスしてみて欲しい。
実は sum を計算してないのでは?
最初のコードでは sum
を出力していなかった。だから計算をまるごとスキップしていたのではないか?という疑惑だ。しかし、これは sum
を出力に含めたところすぐに間違いだと分かった。計算時間は両者ともほとんど変わらなかったのだ。
Date.now() が意図的に正しくない値を返しているのでは?
セキュリティ的な理由でそういう感じのことをしているというのを聞いたことがあった。数千ミリ秒単位で誤魔化すとは思えないが、一応次のコードを試した。
var now = Date.now();
var time = Date.now() - now;
Logger.log('time ' + time);
しかし、これはどちらのランタイムでも 0ms という結果になった。
インスタンスのスペックが違うのでは?
要するに V8 モードをオンにすると、より高級なインスタンスが起動するのではないかという疑惑だ。これはもはや言いがかりのような理屈だが、かといって否定する証拠もないので、この可能性は捨て切れないと思う。インスタンスのスペックが Math.random()
や for
の実行速度にどのように影響を及ぼすのかを僕はよく知らないが、スペックが良いなら実行速度も単純に上がるのではないかと予想できる。実際に GCP アカウントでインスタンスを立てれば再現できるのかも知れないが、それは App Script と同じ環境と言っていいのかよく分からない。
ここからは本当に僕の私見で、邪推と呼んでもいい理屈だが、もし仮に僕が GAS の V8 移行を指揮する立場にあったとしたら、実際の V8 の性能がどうであれ、ただ V8 モードをオンにするだけでコードの実行速度が明らかに改善されるという結果が望ましいものだと考えるだろう。それは直接的に顧客の利益に繋がるし、計算時間が短くて済むなら Google にとっても嬉しいはずだ。なぜなら GAS は無料だからである。
おわりに
長々と書いてきたが、Qiita に残すべき事実としては、表題の一行だけで十分だったのかも知れない。僕はこの記事で事実だけを述べていると自負しているが、ある人が読めば風評被害を起こそうとしているかのように、またある人が読めば Google のステマをしているかのようにも読めるかも知れない。あるいは、全ては何の有用性もない戯言だったのかも知れない。それではなぜ僕がこの記事を書く衝動に駆り立てられたかと言えば、V8 モードをオンにするだけでものすごい速くなるのは何故かという、その謎に惹かれたからだ。たった 4000ms の違いと思われるかも知れないが、その違いは、40分以上の時間を執筆に向かわせるに十分なものだったのだ。