kairo-gokko (39) コンパイル時間短縮のために data.rb をスリム化



require_remote にかかる時間がさすがに長すぎるので、なんとかしたい……。 開発効率的にも辛いですし、他の人に見てもらうときもなるべく待たせないようにしたい。


というわけで調べてみました。 ネックになっているのは data.rbコンパイル時間のようです。

今までのデータでサイズが一番大きいのは step 36 のものなので、今回はこのファイルを使って比較します。

data.rbコンパイルにどのくらい時間がかかっているのか *1 測ってみると、 Firefox で 5回の平均が 22.8 秒でした。うーん、これはひどい

サイズを見てみます。

  • 741 KiB
  • 行数: 35,163

うーむ。Opal もこんなものを渡されて大変ですね。


ちなみにこの data.rb を CRuby で実行してみるとこんな感じでした。

$ ruby -v
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux]

$ time ruby data.rb 

real    0m0.192s
user    0m0.107s
sys     0m0.095s

単純に大きいので小さくしましょう。

以下は data.rb の内容の一部です。

      {
        "edges": [
          {
            "pos1": {
              "x": 59,
              "y": 9
            },
            "pos2": {
              "x": 60,
              "y": 22
            },
            "wfs": [
              {
                "pos1": {
                  "x": 59,
                  "y": 9
                },
                "pos2": {
                  "x": 60,
                  "y": 9
                }
              },
              {
                "pos1": {
                  "x": 60,
                  "y": 9
                },
                "pos2": {
                  "x": 61,
                  "y": 9
                }
              },

Point オブジェクトが大量にあります。 まずは これをコンパクトにしましょう。

オブジェクトのフォーマット { ... } になっていたところをただの文字列にします。 フォーマットの変更なので、 step 38 までのデータとは互換性がなくなりますが、やむなし。

--- a/unit.rb
+++ b/unit.rb
@@ -9,17 +9,12 @@ module Unit
     end
 
     def to_plain
-      {
-        x: @x,
-        y: @y
-      }
+      [@x, @y].join(",")
     end
 
     def self.from_plain(plain)
-      Point.new(
-        plain["x"],
-        plain["y"]
-      )
+      x, y = plain.split(",").map { |s| s.to_f }
+      Point.new(x, y)
     end
 
     def hash

たとえば上に挙げた箇所がこんな感じでコンパクトになります:

      {
        "edges": [
          {
            "pos1": "59,9",
            "pos2": "60,22",
            "wfs": [
              {
                "pos1": "59,9",
                "pos2": "60,9"
              },
              {
                "pos1": "60,9",
                "pos2": "61,9"
              },

これで 11.4 秒になりました。


それから、JSON の整形でもかなりサイズが水増しされているので、 整形をやめることに。

--- a/preprocess.rb
+++ b/preprocess.rb
@@ -21,6 +21,6 @@ circuits =
 plain = circuits.map { |circuit| circuit.to_plain }
 
 puts "$data_json = <<EOB"
-print JSON.pretty_generate(plain)
+print JSON.generate(plain)
 print "\n"
 puts "EOB"

もちろん整形されている方がデバッグ時などに見やすいわけで、 整形をやめるかどうかちょっと悩みました。

しかし、デバッグのために data.rb を調べなければいけない場面は今までそんなになかったですし、 整形したければまた JSON.pretty_generate に戻せばよいだけだと考え、やってしまうことにしました。


最終的な前後比較です。

所要時間: requrie_remote "./data.rb" の所要時間
サイズ: data.rb のサイズ

before:
  所要時間: 22.8 秒
  サイズ:   740.8 KiB

after:
  所要時間: 2.6 秒    ... 88%減
  サイズ:   109.6 KiB ... 85%減

かなり縮みました。元がひどかったですね……。


デモ用に 1bit CPU だけのデータを作り直しました。

https://sonota88.github.io/kairo-gokko/pages/39/index.html



*1:正確には「リソースの取得にかかる時間+コンパイル時間」を見ているのですが、 コンパイル時間でほとんどを占めていると思われるので、 単に「コンパイル時間」としています。