印象的な性能向上が重要でないとき
(blog.colinbreck.com)- 性能最適化は複雑なシステムを理解し、製品を改善するための強力な手段だが、10倍速い結果でも実際の作業方法や処理量を変えられないことがある
- クエリ応答を5〜10分から30秒〜1分に短縮しても、人が待ちながら注意を維持できる約10秒のしきい値を超えていると、体感上の効果は限定的になる
- 作業が1日1件・2件のように整数単位で区切られる場合、25〜50%の改善だけでは不十分で、移動時間を含めて各作業が4時間以下になって初めて1日2件が可能になる
- データパイプラインでは遅い段階が上流にバックプレッシャーをかけるため、単一段階が大幅に速くなっても、すべてのボトルネックが解消されるまではエンドツーエンドの処理量が増えないことがある
- 性能改善は個別ベンチマークではなく望む結果を基準に評価すべきであり、注意維持・作業単位の増加・全体スループットといった制約を超えられなければ、大きな改善でも実質的な効果は小さい
性能指標と実際の結果が食い違う理由
- 性能改善の作業はシステムをより効率的にし、顧客に新たな可能性を開くことができるため、やりがいのある仕事である
- 規模と負荷がかかった複雑なシステムがどのように相互作用するかを経験的に理解する助けにもなる
- システムを間近で扱う過程で製品やサービスを改善するアイデアが生まれ、その一部は性能最適化と直接関係しない
- ただし「10倍高速」「リソース50%削減」のような見栄えの良い成果でも、隠れた制約を越えられなければ期待した変化を生みにくい
10秒を超えるとユーザーの注意は途切れる
- 新しいデータベースのクエリ性能を改善し、既存データベースで最も高コストだったクエリの実行時間を5〜10分から30秒〜1分に短縮した事例がある
- この結果は10倍級の改善だったが、ユーザー体験を変えるにはさらにもう一段大きな改善が必要だった
- 人間とコンピュータの相互作用に関する研究では、人が作業全体に注意を維持できる限界は約10秒とされる
- 0.1秒は即時フィードバックとして認識されるしきい値
- 約1秒は作業の流れが続いていると感じられるしきい値
- 約10秒は作業全体への注意を維持できるしきい値
- 進行表示や予想時間のようなフィードバックは注意維持に役立つことがある
- 30秒と5分はいずれも10秒を超えるため、ユーザーはメッセージを確認したり、コーヒーを取りに行ったり、会話を始めたり、別の作業へ切り替えたりする
- ユーザーが数分後または数時間後に戻ってきたときにUIがすでに読み込まれているなら、実際の待ち時間が30秒だったか5分だったかは、作業の進め方に大きな差を生まない
- そのプロジェクトでは最終的に多くのクエリを10秒以下に短縮し、以前はタイムアウトのため不可能だった一部のクエリも実行可能になった
- データクエリの遅延だけでなく、メタデータクエリの遅延やWebページのレンダリング時間も全体的な性能改善に重要だった
- 非同期I/Oとデータ集約の改善により、さらにもう一度10倍の改善が見込まれており、そうなれば以前は数分かかっていたクエリが1秒未満で返る可能性がある
1日1件から2件へ移るしきい値
- あるプロジェクトでは、手作業の自動化、不要な段階の削除、一部の並列化、後で非同期に処理できる段階の遅延によって、プロセス全体を数時間から安定して1時間未満へ短縮した
- 改善幅はおおよそ**25〜50%**だったが、全体のプロセスは物流上の制約によって変わらなかった
- 配管工、電気工事士、大工のように、現場を予約して移動し、作業を完了しなければならないケースを考えられる
- 1日8時間働き、1か所での作業に8時間かかるなら、2〜3時間節約しても新しい現場へ移動して新たな作業を終える時間はない
- 移動時間を含めて各作業が4時間以下にならなければ、1日2件を完了することはできない
- このしきい値を超えるまでは、中間段階の効率改善は生産量の増加につながらない
- 性能に集中したおかげで、顧客体験に直接影響する品質と安定性の改善も同時に実現できることがある
- 本番環境では突破口にならない小さな性能改善でも、テスト環境での反復速度を高め、機能開発や不具合解消を速めることができる
バックプレッシャーのあるパイプラインのボトルネック
- 多くの業務ソフトウェアのインフラには、車両、工場設備、携帯電話、金融取引など複数のソースからのイベントを処理するデータパイプラインが含まれる
- イベントは通常、耐久性のあるログに保存され、下流サービスがこれを消費して処理する
- 大規模な高スループットのためにはログをパーティション化する必要があり、下流サービスはバッチ処理、パイプライニング、並列性、効率的なメモリ割り当て、動的スケーリングのような手法を使う
- データパイプラインのボトルネックは、システムの動作が相互に関連しているため見つけにくい
- 遅い段階は意図的に上流段階へバックプレッシャーをかける
- ボトルネックが複数ある場合、最後のボトルネックが取り除かれるまでは全体スループットは増えない
- パイプラインを段階に分け、各段階の性能特性と限界を理解することは、優れたエンジニアリングの実践である
- しかし単一の段階を何桁分も改善しても、全体スループットには影響しないことがある
- スループット改善で重要な数値は個別段階のベンチマークではなく、エンドツーエンドのスループットである
ボトルネックを見つける経験的アプローチ
- このようなシステムのダイナミクスとボトルネックを理解するには、パイプラインの始点から経験的に確認していく方法が有効である
- たとえば、分散ログからイベントを読み取って破棄する段階から始める
- この段階だけで目標スループットに到達できないなら、下流段階を最適化しても時間の無駄になる
- データベースに毎秒何行挿入できるかといった下流のベンチマークも重要になり得るが、分析は上流から始めるべきである
- 複雑なシステムと性能を理解するうえでは、シミュレーションも価値のある方法である
性能改善の基準は望む結果
- 性能改善の作業は難しいが、複雑なシステムを深く理解し、より良い製品を作るための訓練でもある
- 人の注意を引き留める必要があるなら、約10秒以内に応答しなければならない
- 作業単位全体が制約であるなら、パーセント改善だけでは不十分で、1日1件から2件へ移れるようにならなければならない
- バックプレッシャーのあるパイプラインのスループットを最大化するには、1つや2つのボトルネックではなく、すべてのボトルネックを解消する必要がある場合が多い
- このような制約を超えられなければ、10倍級の性能改善でも望む結果を生み出せない
1件のコメント
Lobste.rs のコメント
全体のスループットに影響しない 1 つの段階を何十倍も改善してがっかりするような場合なら、ここでは Amdahl's law に触れる価値がある
次々に新しい法則を耳にするが、頻繁に使うほどではないニッチなものなので、また忘れてしまいがちだ。とはいえ、それらの法則が世代をまたいだ知識として有効でない、あるいは有用でないという意味ではない
そもそも、なぜ全体時間のごく小さな断片しか占めない部分を最適化していたのか疑問だ
ガイドが悪かったのか、性能ツールが不足していたのかは分からない。意識的にほとんど影響のない作業をしようとする人は少なく、たいていはもっと大きな問題が隠れている
ある問題を見ていると、サンプリング結果で特定の関数が大きく見え、少し見てみると実装をかなり簡単に改善できそうに見える。しかし今は別の作業中なので、「あとで対応しよう」と先送りにする
後になってその簡単な改善を思い出して着手するが、いざ修正してみると思ったより複雑になってもトンネルビジョンに陥り、パズルを解きたくなって多くの時間を費やしてしまう
実際には、その性能問題は些細なものだっただけだ。当時見ていた文脈の中で割合として大きく見えただけか、絶対時間にほとんど意味がなかったか、CPU ではなく入出力で詰まっていた状況だったのかもしれない。ある種の自分自身に仕掛けられた nerd snipingに近い
それでも無視して最適化を進め、全体の改善が 0.1% 未満になると困惑していた。ドメイン知識があれば直感的にも高コストではない部分だったが、時間を無駄にしないよう、実際の性能コストの測定まで手伝った
あるいは、ベンチマークが誤解を招いたのかもしれない。すべてのシステムが、本番環境であらゆるメソッドやプロセスのコストを簡単に見せてくれるわけではない
こうした説明はいずれも程度の差こそあれ機能不全に近く、個人の問題により近いものもあれば、環境の問題により近いものもある
3.1. ハイパースケーラーで働いていると、ユーザーにはまったく見えない計算量 0.1% の削減だけでも、海辺の家を買えるほど損益に影響が出ることがある
よくあるプログラミング慣行そのものが全体的に遅いことが多い。たとえば、ポインターが大量にあり、一般的なアロケーター経由のヒープ割り当てが非常に多いオブジェクト指向コードが思い浮かぶ。どこを直しても全体時間の小さな断片にすぎず、最悪の場合は完全な書き直し以外では解決できない。運がよければ段階的に書き直せる
ボトルネックが 2 つだけでも、互いに 1 桁倍の範囲に収まっているなら、最悪のボトルネックを完全に直しても 1 桁倍の改善すら出ない。多くの場合、記事で述べているように、意味のある利益を得るにはそれよりはるかに大きく超える必要がある
また、コンピューターは並列に動く分散システムに近い。いくつかの CPU、GPU、ディスク、Ethernet などがあり、プロセスの速度はパイプラインで最も遅い段階に制限される。その段階を直すと、次に遅い段階が制限し、最悪の場合は最も遅い段階が複数あるため、1 つだけ直しても何の利益もない
それでもこれは善意に解釈した説明であり、ときには単に最適化ゲームにのめり込んで優先順位を見失ったり、ミスをしたりすることもある
ユーザーが気づかなくても、ソフトウェアの計算時間削減は依然として良いことだ
コストを下げ、スケールを容易にできるからだ
コードを速くする代償として過剰な複雑さが入り込むと、保守性はさておき、長期的には性能を損なう後続の結果が生じる。構造変更の際に、今では不要になるコードが残り、それを理解したり削除したりするには、全体の複雑さを完全に理解しなければならないからだ
「速くなる」という理由が、複雑さや保守性への懸念を無視してよい免罪符になってはならない
今まさに似た状況にあり、複数の小さなプロジェクトで5 分かかっていたクエリを 30 秒未満に短縮している
長期的には十分ではないとチームには伝えているが、明らかな改善であり影響も大きい
顧客の立場では、待ち時間が激怒するレベルから、単にいら立つレベルに下がる
今の焦点はユーザーごとの性能ではなく全体性能だ。5 分、10 分、30 分のプロセスを数十個最適化すると、システムの他の部分との競合が大きく減る。10 分間データベースを叩き続けるのは非常に長い時間で、最終的にはすべてが速くなり、全員が利益を得る