性能や安定度が向上してきたアプリが急に動作しなくなった
ゲームFF14の動画専用というニッチな物体検出アプリを作成し公開しています。
これまで性能改善や安定度向上、機能追加など改修を実施し、ある程度満足できる形になってきていました。
機能追加もしたのでアプリ適用のデモ動画を作成し、youtubeにアップロード。
自分の仕事に満足してツイッターを眺めていると、「アプリが一晩経っても処理が終わらない」という投稿を見かけました。
状況を確認したかったので問い合わせしてみたのですが、いまいち原因がわかりませんでした。
そのしばらく後、私のWindows PCに24H2が降臨。
すると、なんということでしょう!うちでも自作AIアプリが動作しなくなりました。
自作AIアプリの概要
こんなアプリです。
- WINUI3を利用した動画加工アプリ
- 動画の入出力にはffmpeg.exeを利用
- 物体検出にはyolov8を基にしたONNX RUNTIMEやTensorRTを利用
アプリの動きの概要は、
- ffmpeg.exeで動画から1フレームを取り出して
- フレームごとにONNXやTensorRTで物体検出、検出箇所をOpenCVで加工、
- 加工したフレームをffmpeg.exeで動画にする
入出力のffmpeg.exeはスレッド化して動作しています。
ネタバレ結論:私の考慮不足でした。
物体検出処理と出力のffmpegの間にはフレームバッファーを設けていたのですが、ハードウェアエンコーダ/デコーダを利用しているffmpegに対して物体検出処理は遅いため、開始直後は十分にバッファーが貯まらず書き出し側のffmpegが仕事がない!と早々に終了していました。
読み取り側のffmpegはボチボチと動作しますが、書き出し側のffmpegはもう落ちているので、処理が永遠に終わらない、という顛末でした。
コード修正
読み取りスレッドと書き出しスレッドの間に単純な時間の遅延処理を追加しました。
当初は0.2秒としたのですが、処理が重いHDR動画では正しく動作できなかったため、最終的には1秒としています。
// 読み取りスレッド:ffmpeg_input の出力(raw video)を読み込む
std::thread read_thread([&]() {
run_ffmpeg_input(ffmpeg_input_cmd, [&](HANDLE input_pipe) {
while (true) {
cv::Mat frame = ReadFrameFromPipe(input_pipe, width, height);
if (frame.empty()) {
break;
}
{
std::unique_lock<std::mutex> lock(queue_mutex);
std::cerr << "Write thread loop check: frame_queue.size() = " << frame_queue.size()
<< ", finished_reading = " << finished_reading << std::endl;
queue_cv.wait(lock, [&]() { return frame_queue.size() < max_queue_size; });
frame_queue.push(frame);
}
queue_cv.notify_one();
}
// ここでパイプを閉じることで、ffmpeg に EOF を通知する
std::cerr << "Read thread: closing input pipe handle to signal EOF to ffmpeg." << std::endl;
CloseHandle(input_pipe);
{
std::lock_guard<std::mutex> lock(queue_mutex);
finished_reading = true;
}
queue_cv.notify_all();
});
});
+ // 少し待機する(例:200ミリ秒の遅延)
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// 書き込みスレッド:ffmpeg_output の入力側へ処理済みフレームを書き込む
std::thread write_thread([&]() {
run_ffmpeg_output(ffmpeg_output_cmd, [&](HANDLE output_pipe) {
while (true) {
cv::Mat processed_frame;
{
std::unique_lock<std::mutex> lock(queue_mutex);
// 1秒間待っても返事がなければタイムアウトとする
if (!queue_cv.wait_for(lock, std::chrono::milliseconds(200), [&]() {
return !frame_queue.empty() || finished_reading;
})) {
std::cerr << "Write thread: wait_for timed out (no data for 1 second)." << std::endl;
break;
}
if (frame_queue.empty() && finished_reading) {
std::cerr << "Exit write loop: frame queue empty and finished_reading is true." << std::endl;
break;
}
if (!frame_queue.empty()) {
processed_frame = frame_queue.front();
frame_queue.pop();
}
}
queue_cv.notify_one();
if (!processed_frame.empty()) {
// ここで物体検出などの処理を実施
dml_process_frame(processed_frame, processed_frame, detector, rects, count, name_color, fixframe_color,copyright, blacked_type,fixframe_type,blacked_param,fixframe_param);
total_frame_count += 1;
WriteFrameToPipe(output_pipe, processed_frame);
}
}
queue_cv.notify_one();
std::cerr << "Write thread: closing output pipe handle to signal EOF to ffmpeg." << std::endl;
CloseHandle(output_pipe);
});
});
24H2の功罪
上記のように、発生した問題の根本原因は私の考慮不足ですが、23H2では問題なく動作してた点、また24H2では動作不良となった点が個人的には興味深かったです。
24H2は不具合が多いらしくなかなか一般に提供されない状態が続いていました。
そんな悪名高い24H2がとうとう来たか!受けて立つ!!という気持ちでした。
しかし当方のWindowsPCのハードウェア環境は比較的シンプルなためか、特に不具合はなさそうです。
逆に、上記のコードのように、遅延を入れないと正しく動作できないくらいにffmpegのレスポンスが良くなっていたり、コード修正後に24H2で動作させた自作AIアプリは、従来39FPSだったものが50FPSと大きく性能が向上しました。
恐らく24H2ではハードウェアへのアクセスが従来よりも直接的になり、メモリやGPU、エンコーダなどの処理が大きく向上したのだろうと推測しています。
逆に、USB機器や複数のディスプレイアダプタなど、ハードウェア構成が込み入っていると、機器のドライバーの出来などによってBSODが発生しやすいのではないか、と想像しています。
アプリの処理速度もですが、単純にWindowsの動作も軽快になっており、個人的には24H2おすすめです。