同僚が「パフォーマンスのため」に関数をRustで書き直すのに1週間使った。その関数はリクエストごとに1回実行。2msかかる。その隣のデータベースクエリ?400ms。
200倍の差で間違ったものを最適化した。
開発者に何が遅いか聞け。90%の確率で間違う。
開発者の直感:
「ループが遅い」 → 実際: 0.1ms
「文字列連結が遅い」 → 実際: 0.01ms
「その関数は大丈夫」 → 実際: 3000ms(中にN+1クエリ)
これが下手なのはパフォーマンスが直感に反するから。高価に見えるコードはよくそうじゃない。無害に見える行がデータベースを100回呼んでる。
最初に測定しろ。プロファイラーが示すものを最適化しろ。
ほとんどのWebアプリ:
forループは問題じゃない。欠けてるインデックスが問題。
インデックス1つ。600倍改善。コード変更ゼロ。
先月検索機能を書き直した。元:O(n²)のネストしたループ。10,000アイテムで8秒。
8秒 → 50ms。いくらマイクロ最適化しても元のは直せなかった。アルゴリズムが間違ってた。
1. まず動かす
2. ベンチマーク書く
3. 現実的な負荷でプロファイル
4. 実際のボトルネック見つける
5. 直す
6. 十分速くなったら止める
ステップ6が重要。「十分速い」は本当の目標。それを超えて最適化するな。
まずシップ。後でプロファイル。1%じゃなく48%を最適化しろ。
パフォーマンスは考えて到達できない。測って到達するしかない。
— blanho
UUIDがいつも正解ではない。助けより害になるときがある。
直接のデータ転送が扱いにくくなったら、間接参照のレイヤーを追加しろ。Netflixはこれを痛い目で学んだ。
1,000万件の保存検索がある。新しいアイテムが来る。1,000万件のクエリを実行せずにすべてのマッチをどう見つける?
# プロファイラー出力:
# 48% time: データベースクエリ(users)
# 31% time: 外部APIコール
# 12% time: JSONシリアライズ
# 9% time: その他全部
# 開発者が最適化したいもの: 「その他全部」
# 重要なもの: その48%クエリ-- 前: 1200ms
SELECT * FROM orders WHERE user_id = 123;
-- インデックス追加後: 2ms
CREATE INDEX idx_orders_user_id ON orders(user_id);# 前: O(n²)
for item in items:
for other in items:
if matches(item, other):
results.append((item, other))
# 後: ハッシュルックアップでO(n)
lookup = {item.key: item for item in items}
for item in items:
if item.target_key in lookup:
results.append((item, lookup[item.target_key]))