はじめに
- これは、OpenCV Advent Calendar 2018 4日目の記事です。
- 関連記事は目次にまとめられています。
- 昨日に続き手島が担当します。
- なお、本記事は筆者個人の意見であり、筆者の所属組織とは無関係です。
TL;DR
-
書きかけで公開します。修正しました。 - OpenCVには長らくプログラム終了時に
UMat
の二重解放問題が潜んでいて、OpenCV 4.0で正式に対応が入ったが、そのせいで特定条件下でCUDA付きビルドがコケることになった。 - 具体的にはWindowsでCUDA付きでビルドしようとすると、
unresolved external "__declspec(dllimport) bool cv::__termination"
が発生する - 次リリースでは直されるが、それまでは cmake時に
-DOPENCV_SKIP_DLLMAIN_GENERATION=ON
をつける - (2018年12月26日追記)OpenCV 4.0.1がリリースされましたので、そちらでは本件修正されております
OpenCV 4.0 のリリース
OpenCV 4.0 のリリースで、個人的に最大の変更点はCUDA関連のモジュールがopencv_contribに移動になったことです。(なお、繰り返しになりますが、あくまで本記事は筆者個人の意見であり、筆者の所属組織とは無関係です)
OpenCV 4.0 をソースからCUDA付きでWindows上でビルドしようとすると、重大な罠が潜んでいますので、そちらの紹介を。
そもそも何が起きるのか
プログラムの終了時に二重解放による実行時エラーが発生するのですが、終了間際であること、確実に発生するわけではないこと、などの理由により、普段は気づかないと思います。
始まりのものがたり
- 時系列的には5年前に既に終了時の問題が提起されていました
- Proposals for OpenCV_Init()/OpenCV_Shutdown() by alalek · Pull Request #1490 · opencv/opencv
手島が問題を踏み抜く
-
このissue、ちゃんと読めば分かるのですが、
- opencv_test_imgprocを実行すると、何もFAILせずにプログラムがちゃんと終了する
- しかし、CMake付属の
ctest
を使うと、Segmentation Fault
と表示される - というちょっと見ただけは分かりづらい実行時エラーです。
Start 9: opencv_test_imgproc
1/1 Test #9: opencv_test_imgproc ..............***Exception: SegFault137.81 sec
- デバッガ付きで実行して初めて分かったのですが、終了時に確かに実行時エラーが起きるのです。
- テストプログラム自体には何ら問題が無いため、一見エラーには気づきません
- 通常の、デバッガなしで実行した場合は、実行時エラーがプログラムの終了がほぼ同時のため、見た目には実行時エラーに気づ くことはほとんどありません。
- しかし、ctestから実行した場合、ctest側でsegmentation faultを検知できるので明示的に実行時エラーが表示される訳です。
コメントをもらう
- はじめは
static
を外せば発生が免れるので、「static
を外す」PRを出しました - しかし、
static
に設定されている配列は、LUTを始めとする、定数の置き場でありました。 -
static
がついている場合は最初に計算されて保持されているわけですが、static
でなくなると、関数呼び出し毎にLUTを構築することになり、LUTの意味を成しません。 - また、OpenCV メイン開発者の一人である、alalek先生から以下のコメントをもらいました。
Looks like you catched:
- termination order fiasco: OpenCL context is destroyed / corrupted before allocated buffers.
- or Win32 ExitProcess terrible behavior - it terminates all non-main threads unconditionally - this usually corrupt multi-threading processes / libraries (deadlock is one way).
- Windows側の問題か、OpenCLプラットフォーム(ドライバ?)側の問題か分からないけれど、二重解放が発生しているよ、と教えてくれました。
- OpenCL用の配列、
UMat
は、coreモジュールで管理されているのですが、UMat
用のLUTはimgproc
で管理されています。 -
imgproc
の終了処理とcore
モジュールの終了処理は順序が不定で、場合によっては両方のモジュールから解放が発生するが、2度目の解放では既に領域が存在しないため、エラーになる、と。 - 余談ですがこのalalek先生、本当に何でも知っていて、PR出す度に私が成長することになるほど、圧倒的にいろんなことを知っている方です。1
@alalek, you really know everything.
alalek先生が修正してくれる
- 幾度かのやり取りの後、alalek先生自身が、OpenCVの各モジュールにDllMainを追加するPRを出しました。
- cmake: add DllMain() into each OpenCV DLL by alalek · Pull Request #12791 · opencv/opencv
- このPRのポイントは、
- 終了処理を各モジュールごとに関数でまとめる
- coreモジュールだけが参照していた
__termination
フラグをモジュール間で共有することでUMat
の二重解放を回避する
- 点にあります。
- 見ると分かりますが、テンプレートファイル1つをCMakeで見事に操って、最低限の修正で各モジュールにDllMainを追加しています。
- 余力があればどこかでOpenCVで使われている華麗なCMakeのテクニックを紹介したいと思います。
が、結果としてWindows + CUDA ビルドがコケることに
- build failure (link) with cudev in 4.0.0-beta · Issue #12865 · opencv/opencv
- alalek先生のPR#12791により、私のissue#12750 は無事解決されました。
- が、ここに
cudev
モジュールが関係してきます。 -
8日目の記事に紹介しますが、
cudev
モジュールはcore
モジュールに依存していない、不思議なモジュールなのです。なので、__termination
フラグをcudev
モジュールから参照しようとしても、core
モジュールに依存していないため、参照できません。 - めちゃくちゃ不思議な関係なのですが、どうやらcudevモジュールとはそういうモジュールのようです。
- で、
extern
付きで宣言したものの、実体とリンクできないため、表題のエラーが発生する訳です。
unresolved external "__declspec(dllimport) bool cv::__termination" in DllMain
4.0のリリースと修正
- 早速、修正がマージされる(2018年11月26日)も、それよりも前に4.0がリリース(2018年11月18日)される事態に
- cmake: don't generate dllmain for cudev module by alalek · Pull Request #13209 · opencv/opencv
- (2018年12月26日追記)2018年12月22日(日本時間)にOpenCV 4.0.1がリリースされましたので、そちらを使えば本記事のエラーは発生しません。
迂回方法
- (2018年12月26日追記)繰り返しになりますが、2018年12月22日(日本時間)にOpenCV 4.0.1がリリースされましたので、そちらを使えば以下迂回作業も不要です。
- WindowsでCMakeする際に
OPENCV_SKIP_DLLMAIN_GENERATION=ON
を指定します。 - Windowsではコマンドラインでcmakeを叩く人は少ないと思いますが、一応PATHを通したりフルパスを叩けば普通にcmakeが使えます。
cmake -DOPENCV_SKIP_DLLMAIN_GENERATION=ON ...
- なお、この修正はLinuxでは必要ありません。
- また、OpenCVをstaticライブラリとしてリンクした場合も本件は発生しません。
- 各モジュール間で
__termination
を共有するのはWindowsでの実装なので、LinuxやMacではリンカエラーは起きません
参考
-
既に3年前の記事で@dandelion1124 先生が
__termination
について解説されています。 - OpenCVの初期化処理,終了処理 - Qiita (OpenCV Advent Calendar 2015の @dandelion1124 先生の記事)
終わりに
- とりあえず差し替えました。
- 明日は @hon_no_mushi さんの投稿でRISC-Vだとぉ!?楽しみですね!
- 最後に、繰り返しになりますが、あくまで本記事は筆者個人の意見であり、筆者の所属組織とは無関係です
-
"Not everything, I know only what I know"って返してくれたらホチキスとか蟹とかに出会えそうでしたが、そういうリプライはありませんでした ↩