先日の Claude Code 更新で、ストリーミング時の CPU 使用が約37%削減されました。長時間回し続ける運用にとっては素直にありがたい底上げです。ただ、私自身が複数サイトの自動投稿を1台のマシンで並行させていると、この種の改善で楽になるのは「1本あたりの重さ」であって、「同時に何本も束ねたときに先に音を上げるのはどこか」という問題は別に残り続けます。
実際、私の手元では、各サイトの記事生成を並行で走らせた瞬間にファンが唸り出し、1本ずつ流していたときには気づかなかった遅延が p95 で跳ね上がる、という現象に何度もぶつかりました。メモリは余っているのに、です。ここで取り上げたいのは、その律速が「ホストの CPU」だったときの対処です。勘で決めた固定の並行数をやめ、実測 CPU に合わせて並行本数を自動で絞る仕組みを、動くコードと手元の実測値で組み立てていきます。
import asyncio# 勘で決めた固定値。開発機では快適だったが…sem = asyncio.Semaphore(12)async def run_one(site, client): async with sem: async with client.messages.stream( model="claude-sonnet-4-6", max_tokens=4096, messages=[{"role": "user", "content": build_prompt(site)}], ) as stream: async for _ in stream.text_stream: pass return await stream.get_final_message()
このコードの問題は、12 という数字がある特定のマシンの、ある特定の瞬間に最適化されている点です。私の場合、開発に使っている性能の高い Mac では12本でも余裕でしたが、同じスクリプトを古いミニ PC のスケジュール実行に載せた途端、CPU が96%前後に張り付き、各ストリームの実時間が単独実行のときの2.4倍ほどに伸びました。逆に、他の処理が一切いない大きなマシンでは12本では使い切れず、遊ばせてしまいます。固定値は「速い側」にも「遅い側」にも合いません。
私の環境(記事生成バッチを複数サイト分まとめて流す構成)で、古いミニ PC を実行環境にして比べた一例です。数値は私の特定環境での実測であって、どのマシンでも同じになるわけではありません。傾向の参考としてご覧ください。
項目
固定セマフォ(12本)
適応ゲート(目標70%)
定常時の CPU 使用率
約96%で張り付き
約70±8%で安定
落ち着いた実効並行数
12本固定
6〜7本に自動収束
1本あたり実時間(p95)
単独比 約2.4倍
単独比 約1.3倍
バッチ全体の所要時間
基準
約12%増
体感(ファン・発熱)
サーマルスロットリング発生
発生せず
興味深いのは、並行数を半分近くまで自動で絞ったにもかかわらず、バッチ全体の所要時間は12%しか延びなかった点です。CPU が飽和した状態では、本数を増やしても1本あたりが遅くなるだけで、スループットはほとんど伸びていなかったということです。固定で詰め込むより、目標 CPU を守りながら流した方が、発熱と遅延を抑えつつ、完走時間の差はわずかで済みました。
複数マシンで同じコードを使い回すために
私が個人開発でこの仕組みを重宝しているのは、同じスクリプトを性能の違う複数のマシンに置いても、各マシンが自分の身の丈に合わせて並行数を決めてくれるからです。固定値だと、マシンごとに数字を調整して回る羽目になりますが、目標 CPU 使用率という1つの方針だけを共有すれば、あとは各ホストが実測に従って勝手に最適点へ寄っていきます。