画面切り替えでのメッセージ送信処理を並行処理で54%高速化した(ChatSpooler開発記)

  • macOSのAIチャット自動送信アプリで、送信に1.3秒もかかる問題がありました。
  • 原因は待機時間を固定値で設定していたこと。
  • フォーカスを動的確認してクリップボード処理を並行実行することで、必要最小限の待機時間に変更したところ、54%高速化に成功しました。

はじめに

前回の記事では、macOS向けのAIチャット自動送信ツール「ChatSpooler」を開発しました。しかし、実際に使い始めると気になる問題が見つかりました。送信処理に1.3秒もかかることです。

macOSアプリ送信処理の54%高速化 改善前 フォーカス 1.0秒 クリップボード 0.3秒 送信 合計: 1.3秒(逐次処理) 改善後 フォーカス クリップボード 送信 0.6秒 0.05秒 合計: 0.6秒(並行処理) 3つの改善ポイント 1 動的確認 処理完了を実時間でチェック 2 並行処理 フォーカス+クリップボードを同時実行 3 状態復元 作業環境を自動保護 54%高速化達成

毎回1秒以上待つのは、思った以上にストレスでした。さらに、送信のたびにクリップボードが勝手に変わったり、別のアプリに切り替わったりして、作業が中断される問題もありました。そこで今回は、この送信処理を見直すことにしました。結果として54%の高速化を実現し、作業中断もしにくくできました。

待機時間の原因と根拠を調べる

まず、なぜ送信処理が遅いのかを調べました。現在の処理は次のような流れになっています。

スライド2: 遅い原因を探る 現在の処理フロー フォーカス移動 クリップボード ペースト送信 安全のための待機時間 フォーカス移動: 1.0秒 実際は0.5秒で完了 クリップボード: 0.3秒 実際は0.01-0.02秒で完了 過度に保守的な設定が原因 並行処理と動的確認で解決
  1. 送信先のアプリにフォーカスを移すために1秒待機します。
  2. その後、テキストをクリップボードにコピーして0.3秒待機。
  3. 最後にペーストと送信を実行します。

この合計1.3秒という時間は、すべて「安全のための待機時間」でした。アプリの切り替えが完了するまで、クリップボードの処理が終わるまで、それぞれ十分な時間を取っていたのです。しかし、この待機時間は本当に必要なのでしょうか。

  • クリップボード処理の0.3秒について調べてみると、興味深いことがわかりました。macOSでは、クリップボード操作は実際には10〜20ミリ秒程度で完了します1。0.3秒という設定は、過度に保守的だったのです。
  • フォーカス移動の1秒についても同様でした。通常のアプリ切り替えは0.5秒程度で完了します2

つまり、安全を重視しすぎて、実際の処理時間より長く待機していたことになります。

並行処理と動的確認機能の実装

まず、気づいたのは、フォーカス移動とクリップボード処理は独立した作業だということです。まるで「お湯を沸かしながら野菜を切る」ように、同時並行に実行できる処理でした。

スライド3: 並行処理という発想 従来: 逐次処理 フォーカス クリップボード 送信 合計: 1.3秒 改善: 並行処理 同時実行 フォーカス クリップボード 送信 合計: 0.6秒 キーアイデア フォーカスとクリップボードは独立した処理 → 同時実行可能
  • 従来は「フォーカス移動→クリップボード処理」という順番で実行していました。
  • しかし、この2つを同時に開始すれば、より遅い方の処理時間だけで済みます。

次に実装したのが、「動的確認機能」です。これは、処理が実際に完了したかをリアルタイムで確認する仕組みです。

スライド4: 動的確認機能の実装 動的確認とは 処理が実際に完了したかをリアルタイムで確認 → 必要な分だけ待機 フォーカス確認 100ms間隔でチェック 目的アプリがアクティブ → 即座に次の処理へ 最大1秒でタイムアウト クリップボード確認 10ms間隔でチェック 指定テキストが確認 → 即座に次の処理へ 最大0.5秒でタイムアウト 固定待機 → 必要最小限の待機
  • フォーカス確認では、100ミリ秒ごとに最前面のアプリをチェックします3。目的のアプリがアクティブになったら、即座に次の処理に進みます。
  • クリップボード確認では、10ミリ秒ごとにクリップボードの内容をチェックします4。指定したテキストが正しくコピーされたことを確認できたら、すぐに次の処理に移ります。

この動的確認により、「必要な分だけ」待機する仕組みが実現できました。

並行処理の実装詳細(threading)

並行処理の実装では、Pythonのthreadingモジュールを使用しました5。2つのスレッド(処理の流れ)を同時に起動し、それぞれでフォーカス処理とクリップボード処理を実行します。

def copy_and_focus_parallel(self, app_name: str, text: str) -> bool:
    clipboard_thread = threading.Thread(target=copy_task)
    focus_thread = threading.Thread(target=focus_task)
    
    clipboard_thread.start()
    focus_thread.start()
    
    clipboard_thread.join()
    focus_thread.join()
Code language: PHP (php)

両方のスレッドが完了したら、処理時間をログに出力します。この実装により、従来なら1.3秒かかっていた処理が、0.5〜0.6秒で完了するようになりました。

状態復元機能という新しい課題

処理が高速化されても、まだ問題が残っていました。送信処理中に作業が中断される問題です。

スライド5: 状態復元機能 高速化しても残る問題 送信中に作業アプリから離脱 クリップボードが上書きされる → 作業の流れが中断される WorkspaceState による状態復元 送信前に保存 クリップボード内容 アクティブアプリ 送信後に復元 元の状態に戻す 0.5秒後に実行 透明な送信処理を実現

チャットツールに送信するたびに、それまで作業していたアプリから離れてしまいます。クリップボードの内容も送信用のテキストで上書きされてしまいます。これでは、せっかく高速化しても使い勝手が悪いままです。

そこで考えたのが「状態復元機能」です。プリンターが印刷後も元の状態に戻るように、送信処理も元の作業状態に自動で戻る仕組みを作りました。

WorkspaceStateクラスの設計

状態復元機能の中心となるのが、WorkspaceStateクラスです。このクラスは、送信前の作業状態を記録し、送信後に復元する役割を担います。

  1. 記録する情報は、現在のクリップボードの内容と、アクティブなアプリケーション名です。これらの情報を送信処理の直前に保存しておきます。
  2. 送信処理が完了したら、0.5秒の待機を挟んで復元処理を開始します。この待機時間は、チャット送信が確実に完了するためのマージンです。
  3. 復元処理では、まずクリップボードを元の内容に戻します。次に、元のアプリケーションにフォーカスを戻します。すべての復元が完了したら、ログに結果を出力します。

設定による細かい制御

最適化された処理は、設定値で細かく調整できるようになっています。フォーカス確認の間隔、クリップボード確認の間隔、復元処理の待機時間など、環境に応じて調整可能です。

また、状態復元機能は個別にオン・オフできます。クリップボード復元のみ、フォーカス復元のみ、といった使い方も可能です。

これらの設定により、様々な環境や用途に対応できるよう配慮されています。

最適化によるパフォーマンスの改善

今回の最適化により、以下の改善を実現できました6

スライド6: 最適化の成果 処理時間の改善結果 改善前 1.3秒 逐次処理 改善後 0.6秒 並行処理 54%短縮 個別処理の改善 クリップボード 85%短縮 フォーカス 40%短縮 作業中断 ほぼゼロ 透明な高速送信を実現

実際の処理ログを見ると、改善効果がよくわかります。

作業状態保存: アプリ=Python, クリップボード='# core.py の SendingController ...'
クリップボードコピー完了 (0.044秒)
フォーカス完了: Claude (0.599秒)
並行処理完了 - コピー:0.044秒, フォーカス:0.599秒
送信完了。0.5秒後に状態復元します...
クリップボード復元完了: '# core.py の SendingController ...'
アプリフォーカス復元完了: Python
作業状態復元完了
Code language: JavaScript (javascript)

クリップボード処理が44ミリ秒、フォーカス処理が599ミリ秒で完了しています。並行処理により、実際の処理時間は遅い方の599ミリ秒となりました。従来の1300ミリ秒と比べると、大幅な改善です。動的確認機能が効果的に働いている証拠です。

また、状態復元機能により、作業中にウィンドウフォーカスを戻す手間もなくなりました。

まとめ

今回の最適化により、ChatSpoolerの送信処理は54%高速化され、同時に作業中断もほぼゼロになりました。並行処理による同時実行、動的確認による必要最小限の待機、状態復元による透明な処理という3つの要素が組み合わさって、この結果を実現できました。

技術的には、threading による並行処理、リアルタイム状態確認、WorkspaceState による状態管理が核心的な実装となっています。これらの技術により、ユーザー体験を損なうことなく、大幅な性能向上を達成しています。


  1. Python threading — Thread-based parallelism – Pythonの並行処理実装に関する公式ドキュメント
  2. PyAutoGUI Documentation – macOSでのGUI自動化とパフォーマンス設定に関する公式ガイド
  3. Apple Developer Documentation – Workspace Notifications – macOSアプリケーション切り替えとフォーカス管理の公式仕様
  4. pyperclip – A cross-platform clipboard module – クリップボード操作ライブラリの公式情報とパフォーマンス特性
  5. macOS Human Interface Guidelines – User Experience – macOSアプリケーションのユーザー体験設計原則
  6. Concurrent Programming with Python – Python並行処理のベストプラクティスと実装パターン
  7. AppleScript Language Guide – macOSシステム制御とアプリケーション間通信の公式ガイド
  1. pyperclipライブラリはmacOSで内部的にpbcopyとpbpasteコマンドを使用しており、これらのシステムコマンドは非常に高速に動作することが知られています – Pyperclip Documentation
  2. macOSのアプリケーション切り替えパフォーマンスは、システムの負荷やアプリの種類によって変動しますが、Activity Monitorなどのシステムツールで測定可能です – Activity Monitor User Guide for Mac – Apple Support
  3. この手法は「ポーリング」と呼ばれる監視方式で、システムリソースへの負荷を抑えながら確実な状態確認を行うための一般的なプログラミング手法です – Python Multithreading: Concurrency and Parallel Execution in Python
  4. pyperclipモジュールは、paste()メソッドでクリップボードの内容を即座に取得できるため、短い間隔での監視が可能です – Copy and Paste Text to the Clipboard with pyperclip in Python
  5. Pythonの並行処理は、I/O待機時間の有効活用に特に効果的で、CPU集約的なタスクではGlobal Interpreter Lock(GIL)の制約がありますが、今回のようなI/O処理との組み合わせでは大幅な性能向上が期待できます – Speed Up Your Python Program With Concurrency – Real Python
  6. パフォーマンス最適化の効果測定は、ベンチマークツールや実際の処理時間ログによって定量的に評価することが重要です – Mac Speed Test: Test your Mac’s performance with these benchmark tools