Engineering notes

iOS のバックグラウンドで音声を処理するなら Neural Engine を使う

2026 年 11 月 6 日 · Liner Notes · English

iPhone で録音した会話を、アプリが画面の裏にいる間(バックグラウンド)に処理しようとしたら、フォアグラウンドなら33秒の処理が5分たっても終わらず、やがて記録も残さずに消えました。原因を探していくと、問題は3つ重なっていました。中でも大きかったのは、バックグラウンドで遅くなるのは CPU で動く処理だけで、Neural Engine で動く推論はほとんど遅くならないことです。同じ処理が、CPU では12〜21倍遅くなり、Neural Engine では2倍以内でした。バックグラウンドで音声を処理するなら、できるだけ Neural Engine に載せるのが安全です。どう調べて、何を直したかを書きます。

やりたかったこと

iPhone の中で会話を文字にする日記アプリ(Liner Notes)を作っています。録音を止めたあと、話者の分離、文字起こし、要約をまとめて行う「取り込み」の処理があります。44分の録音で、フォアグラウンドなら4分ほどかかります。利用者は録音を止めたらすぐ別のアプリに移るので、この処理をバックグラウンドで最後まで終わらせたいと考えました。

iOS のバックグラウンド処理の種類

iOS では、アプリがバックグラウンドで動ける時間が仕組みごとに決まっています。

仕組み動ける時間向いている用途
beginBackgroundTask約30秒画面を離れる直前の仕事を終わらせる
BGProcessingTask実行の時刻はシステムが決める急がない処理(充電中の夜など)
BGContinuedProcessingTask(iOS 26〜)進捗を報告し続ける間利用者が始めた長い処理。進捗が画面に表示される
オーディオのバックグラウンドモード録音や再生をしている間録音アプリ、音楽アプリ

取り込みは利用者が録音を止めたことで始まる長い処理なので、BGContinuedProcessingTask を使いました。

起きたこと: 遅いうえに、記録を残さず消える

フォアグラウンドなら33秒で終わる「声を分ける」段階が、バックグラウンドでは5分たっても終わりませんでした。そのうち処理ごと消えます。クラッシュログも、期限切れの通知も、メモリ不足の記録も残りません。

ログを数えると、継続処理の申請62回のうち18回は10分たっても始まらず、始まった回も40〜627秒で消えていました。Apple の開発者フォーラムにも「バックグラウンドで4〜5倍遅い」という投稿があり、原因は分かっていませんでした。

最初に試したこと: CPU を休ませる、進捗を細かく報告する

まず「CPU を使いすぎて止められている」と考え、処理の合間に待ちを入れて CPU を休ませました。処理が遅くなるだけで、消える回数は変わりませんでした。

次に、フォーラムに「進捗の報告が約30秒止まると打ち切られる」とあったので、メインスレッドの外から5秒ごとに進捗を進めるハートビートを入れました。それでも開始25秒で消えました。ハートビートの記録を見ると、進捗は止まっていません。原因は別にありました。

原因1: 自分のコードが猶予の期限を無視していた

取り込みの入口で beginBackgroundTask(expirationHandler: nil) を呼んでいました。この書き方だと、画面を離れて約30秒で猶予が切れても、アプリは自分で処理を終えません。iOS はそのアプリを記録なしに終了させます。「継続処理が始まらない」ように見えていた18回も、実はこれで30〜55秒後に消えていました。

猶予が切れたら自分で終える形に直すと、黙って消えることはなくなりました。29分の会話が、バックグラウンドだけで2分15秒で保存まで終わるようになります。ただし、遅さは残りました。

原因2: CPU の処理だけが、遅いコアで動いていた

何が遅いのかを切り分けるため、計測用のアプリを作りました。同じ Core ML の処理(日本語10分の音声の文字起こし)を、計算ユニットと、アプリの状態を変えて測ります。iPhone 16、iOS 27、2回の中央値です。

計算ユニットフォアグラウンドバックグラウンド(継続処理)バックグラウンド(録音中)
Neural Engine2.2秒4.3秒(2倍)2.4秒
CPU のみ8.4秒102〜174秒(12〜21倍)7.3秒

日本語10分の文字起こしにかかった時間

秒(短いほど速い)。CPU のみのバックグラウンドは102〜174秒の中央で描いている

Neural EngineCPU のみ
フォアグラウンド
2.2秒
8.4秒
バックグラウンド(継続処理)
4.3秒
102〜174秒
バックグラウンド(録音中)
2.4秒
7.3秒
表で見る
Neural EngineCPU のみ
フォアグラウンド2.2秒8.4秒
バックグラウンド(継続処理)4.3秒102〜174秒
バックグラウンド(録音中)2.4秒7.3秒

Neural Engine で動く推論は2倍以内に収まり、CPU で動く処理だけが12〜21倍遅くなっています。録音中(マイクを開いている間)は、バックグラウンドでもどちらもフォアグラウンドと同じ速さでした。

CPU が遅い理由を見るため、プロセスが使った CPU 時間と消費電力量を task_info で読みました。

CPU のみの処理経過時間使った CPU 時間消費電力量(フォアグラウンドを1とする)
フォアグラウンド8.7秒7.9秒1
バックグラウンド112秒86秒0.22

CPU のみの処理の、経過時間と使った CPU 時間

秒。バックグラウンドでも CPU 時間はほぼ経過時間ぶん割り当てられている

経過時間CPU 時間
フォアグラウンド
8.7秒
7.9秒
バックグラウンド
112秒
86秒
表で見る
経過時間CPU 時間
フォアグラウンド8.7秒7.9秒
バックグラウンド112秒86秒

バックグラウンドでも、CPU 時間は経過時間のほとんどで割り当てられています。それなのに、同じ処理を終えるのに11倍の CPU 時間がかかり、電力は4.6分の1しか使っていません。CPU の割り当てを絞られて待たされているのではなく、省電力の遅いコアで、低いクロックで動いていると考えると、数字がすべて合います。Instruments でコアの配置を直接見てはいないので、これは推定です。

音声は最長20秒の区切りに分けて処理しているので、区切り1つあたりの時間も見ました。バックグラウンドでは最初の区切りから遅く、途中でさらに遅い区間に切り替わることもありました。

区切り1つあたりの時間CPU のみ
フォアグラウンド0.14秒
バックグラウンド(最初の区切りから)1.7秒
バックグラウンド(遅い区間)8秒前後

Apple のフォーラムには「フォアグラウンドでも、負荷が30秒続くとスレッドが効率コアに移される」という別の報告があります。今回は最初の区切りから遅いので、その現象とは違うものです。

もう一つ分かったのは、Core ML を呼ぶタスクの優先度を .background にすると、フォアグラウンドでも Neural Engine の推論が3.6倍遅くなることです。CPU の処理は優先度では変わりませんでした。

タスクの優先度を変えたときの処理時間(フォアグラウンド)

秒

Neural EngineCPU のみ
userInitiated
2.2秒
8.4秒
utility
2.8秒
7.6秒
background
7.9秒
8.3秒
表で見る
Neural EngineCPU のみ
userInitiated2.2秒8.4秒
utility2.8秒7.6秒
background7.9秒8.3秒

消えていた「声を分ける」段階には、音声の変換や区間の切り出しなど CPU で動く処理が含まれていて、そこが遅くなっていたと考えています。

原因3: 継続処理が始まらない回がある

1と2を直したあとも、継続処理を申請して9分以上起動しない回を再現しました。これは iOS 側の挙動で、アプリからは直せません。

直したこと: 重い処理を録音中に終わらせる

録音中はバックグラウンドでもフォアグラウンドと同じ速さで動くので、取り込みの重い処理を録音中に移しました。録音を止めたあとは、その結果を使うだけにしています。

処理以前(録音を止めたあと)今(録音中)
文字起こし録音全体をまとめて処理12秒ごとに録音中に処理
声の分離録音を流し直す(バックグラウンドで5分以上)録音中に済ませる。止めたあとは0.3秒
笑い声の検出29分の録音で167秒録音中に済ませる。止めたあとは0秒

録音を止めたあとの処理時間(笑い声の検出、29分の録音、バックグラウンド)

秒

以前(録音後に検出)
167秒
今(録音中の結果を使う)
0秒
表で見る
項目値
以前(録音後に検出)167秒
今(録音中の結果を使う)0秒

録音中に済ませられない処理は、Core ML の計算ユニットを .cpuAndNeuralEngine にして、Neural Engine に載せています。バックグラウンドでは GPU が使えないので、.all にしていても意味がありません。

継続処理が始まらない回に備えて、文字起こしは録音を止めた時点で保存し、要約は次にアプリを開いたときに付け直します。

録音を止めたあとに残っている重い処理は、話し手の分け直しだけです。10分の録音で、バックグラウンドで28秒でした。内訳は、区切りと埋め込みの計算が8秒、CPU のクラスタリングが0.5秒、残りはモデルの読み込みです。

まとめ

計測に使ったアプリとデータは GitHub で公開しています(yamannyama/granite-speech-coreml)。

ほかの開発ノート