🚅

高速化への手順

2021/02/12に公開

まずは計測

高速化をする前に、まずは何が遅いのかを計測しましょう。このときに一番大切なのは、必ず一番遅いところ(ボトルネック)を見つけるということです。その理由は三つあります:

  1. ボトルネック以外の高速化は無意味
  2. 計測なしでは、そもそもどこが遅いのかが分からない
  3. 高速化の影響を数値で表せられる

ボトルネック以外の高速化は無意味

車の運転、特に渋滞のときを思い浮かべてください。道路の交通量を決めるのはどの車ですか?一番遅い車です。その車より前の車が速く走っても、その車より後ろの車が速く走ろうとしても、全体の交通量は変わりません(安全運転は忘れずに🦺)。

計測なしでは、そもそもどこが遅いのかが分からない

もし計測なしに遅い部分が分かったとしたら、それは運がいいだけです。計測してみたら予想外の場所がボトルネックだった、なんてことは多々あります。

最悪の場合、苦労して高速化をしたにも関わらず、実は見当違いの場所を高速化していたので、高速化できていない上にコードは前より分かりにくくなってしまった、なんてこともあり得ます😱

高速化の影響を数値で表せられる

高速化前の数値を計測しているので、高速化後に同じ計測をして、実際にどれほど速くなったかを知ることができます。これがないと、他人はもちろん、自分でも本当に速くなったのか分かりづらく、最悪前より遅くなっていても気づかない場合も。

また、きちんと数値で高速化の影響を表すことで、開発メンバーだけでなく、ビジネス側のメンバーや顧客にも何が変わったのか伝えやすくなります。例えば「この部分を速くしました!どれほど速くなったかは分からないけど」というよりも、「この作業が前は5秒かかっていたのを、今は1秒で行えるようになりました」という方が具体的にどのように改善されたのかが分かりやすいです🎉

全体を高速化したいときに、何から計測すればいいか

顧客の目線になって、一番大切なところから計測しましょう。例えばフロントエンドなら、よく使うボタンを押してから作業が完了するまで。バックエンドなら、API毎にどれほど時間がかかっているか、などです。

大きな範囲の測定が完了したら、その大きな作業の中でのボトルネックを見つけましょう。例えばボタンを押してから作業が完了するまでの間に、いくつもの小さな作業があるはずです。それらの速度を計測して、ボトルネックを見つけましょう。多くの場合、全ての作業が均一に時間を食っているのではなく、やたら時間を食っている一つの作業(ボトルネック)が見つかるはずです🐢

具体的な計測方法

高速化したい範囲が分かっている場合

どのように計測するかは、どこを何の目的で計測しているかによって変わってくるので、ここに書くのはあくまでも例になります。

もし高速化したい場所が分かっているのなら、プロファイラを使うのが簡単な場合が多いです。例えばWebフロントエンドなら、ブラウザについてくるプロファイラを使うことができます。プロファイラを走らせたまま計測したいコードを走らせることで、どこにどれだけの時間がかかっているのかが分かります。

https://developers.google.com/web/tools/chrome-devtools/rendering-tools/js-execution?hl=ja

バックエンドでもプロファイラを付けられる場合が多いので、自分が使っているテクノロジーのプロファイラを探してみてください。もちろんログだけで計測を終わらせられる場合も多いので、状況に応じて簡単な方法で計測しましょう。

なんとなく全体的に高速化したい場合

もし顧客に言われる前に積極的に全体を高速化したいなどという場合は、普段からいたるところに早さを計測するコードを忍ばせておく必要があります。フロントエンドの速度を計測した場合は、それをサーバー側に送る必要があるので注意しましょう。

顧客からすれば、大切な作業の大きな範囲でのパフォーマンスにしか興味がありません(例えばボタンを押してから作業が完了するまでのパフォーマンスに興味はあるが、その結果走ることになるバックエンドの細かなコードのパフォーマンスには興味がない)。なので大切な作業の大きな範囲の計測から始めて、次に小さな範囲の計測をするようにしましょう。大きな範囲での計測をゴールにし、小さな範囲の計測は開発者がボトルネックを見つけ、改善するための材料にしましょう。

例えばバックエンドの高速化を行いたい場合、各APIを計測するところから始め、その後にコードレベルでの計測を始めます。バックエンドのフレームワークによりますが、大体のフレームワークでは、APIが呼ばれる前と後にコードを実行できるインターセプタを実装できるようになっています。これを使えばAPIが呼ばれる前の時間を保管しておき、APIが呼ばれた後の時間と比較することで、各APIの速度を計測できます。

コードレベルでは、時間がかかりそうな作業は基本的に計測しましょう。例えばデータベースにアクセスするコードだったり、何かの計算をするコードなど。

個人的にお勧めなのは、無名関数を使った計測方法です。例えば下のコードでは、"Do something"という作業の計測を、measurePerformanceという関数で行っています。measurePerformanceは与えられた無名関数にどれほどの時間がかかったかを計測し、作業の名前毎に保存しておきます。また、無名関数が返した値をmeasurePerformanceがそのまま返すようにすれば、既存のデザインへの影響を最小限に、どこでも計測することができます。

measurePerformance("Do something", () => {
  return doSomething()
    .then(() => doAnotherThing());
});

例はJavaScriptを使っていますが、無名関数を使えるならどの言語でも使えるテクニックです。また、既存のライブラリを使って計測しても問題ありません。

最低でも以下の数値を、作業の名前毎に保管しておきましょう(グローバル変数に保管しておいて問題ないです):

  • 作業が成功した回数
  • 作業が失敗した回数
  • 成功した作業にかかった時間の合計
  • 失敗した作業にかかった時間の合計
  • 成功した作業にかかった時間の最大値
  • 失敗した作業にかかった時間の最大値

平均値や成功率はこれらの値から導き出せるので、保管しておく必要はないです。最大値を保管しておくのは、例えば平均で三秒で終わる作業がたまに三十秒かかっているとしたら、それを知っておかなければならないからです。

計測した値は専用のAPIから見れるようにしてもいいですし、もしこれをSREやモニタリングに結び付けたいのなら値を他のサービスに運ぶ必要もでてきますが、そこは各々のやりたいことによって異なってくるので好きにしてください🤘

ボトルネックが分かったら、高速化

計測が済んだら、高速化を行ってください。どうやったら高速化できるかはケースバイケースなので省きますが、共通する手順としては下記の通りになると思います:

  1. 計測する
    • 値に満足できたら終わり、出来なかったら2に進む
  2. 高速化する
  3. 1に戻る

まとめ

高速化を始める前に必ず計測をして、ボトルネックを見つけること。ボトルネックが見つかったら、そこから高速化すること。高速化が終わったらもう一度計測し、満足できるまでこの作業を繰り返すこと。

積極的に全体の高速化をするなら、全体のパフォーマンスを普段から計測しておくこと。顧客の目線で、一番大切な作業から計測を始めること。大きな作業から計測を始め、小さな作業の計測へと移ること。

Discussion