EigenのOne Definition Rule違反

Eigenライブラリの利用法に関するバグで、丸二日間くらいはまった。 正しい原因にたどり着くのが難しいため、備忘録として残しておく。

Eigenは数値演算の高速かつ使いやすいライブラリで、かつtemplateを使ったヘッダファイルだけで構成されているため、あらゆる環境でビルドしやすく重宝されている。 最適化演算ライブラリのCeres, OpenCVなどでも使われている。

ところが、その有用性が徒となるケースがあった。

Eigenのようなヘッダファイルを使ったアプリケーションが実行時にクラッシュなどで落ちて原因が分からない場合は、今回のケースを疑ってみて欲しい。

エラー内容

今回遭遇したバグは、コンパイル・リンクは問題無いが、Ceres solverの最適化実行時にアプリがクラッシュ。 障害モジュールの名前:StackHash_**** 例外コード:c0000374 と出る(Windows) さらに、Linuxでビルドしたものも同じようにCeres solver実行時に落ちた。 最終的に解決したのだが、これは予想外の原因で落ちていたことが分かった。

原因

まず、C++にはOne Definition Ruleというものがあり、関数でも何でも同じ名前では定義(実装)は一つしか取れない。

One Definition Rule - Wikipedia

Eigenはヘッダファイルのみで構成されるおり、Eigenを使っているプログラム間で同じ定義が出来てしまう可能性がある。例えばEigenを使っているCeres solverのライブラリを使い、さらに自分で作った本体でもEigenを使っていると、コンパイルはライブラリと本体で独立に行っているため、同じ関数に二つの定義が出来てしまうことになる。 リンカエラーになるケースは良いが、今回はそうならなかったケースだ。

どういう状況で問題があるかというと、

  1. バージョンの異なるライブラリ(今回はEigen)を複数インクルードしていて、そのバージョン間でコードが異なっている時

  2. バージョンは同じライブラリ(Eigen)を複数インクルードしていて、インクルードしたそれぞれの呼び出し間で、コンパイルに関わる環境が変わっている時

今回自分のケースは2だ。Eigenを使っているCeres solverライブラリをビルドし、Ceres solverを使っている本体側でもEigenを使っていた。

独立にビルド済みのライブラリを使っている場合は、リンク時に一つのライブラリインスタンスしかリンクしないので通常問題にならない。

同じバージョンのEigenを使えば問題無いかと思っていたら、本体側のコードでAVX命令を使っているため、本体側のみがコンパイラオプションとしてAVX有効にしていたために起こった。

実はEigenのコードは、AVXやSSEといったSIMDコンパイルオプションが設定されていると、高速な実装のためにSIMD命令を使うのだが、その際メモリアロケーションを自前で書いたものを使うようになっている。 それが、Core下のutil\Memory.hに定義されているもので、handmade_aligned_mallocという関数をインラインで定義している。 SIMDを使ったことが無い人にはチンプンカンプンかもしれないが、これはSIMD命令を使うときに良く使うテクニックで、SIMD命令に必要なメモリアラインメントを揃えるために大きめにmalloc()しておいて、アラインメントされたポインタを返すというものだ。解放する時は演算に使ったポインタではなく、ちゃんと元のmalloc()したポインタを使ってfree()しないといけない。

/* ----- Hand made implementations of aligned malloc/free and realloc ----- */

/** \internal Like malloc, but the returned pointer is guaranteed to be 16-byte aligned.
  * Fast, but wastes 16 additional bytes of memory. Does not throw any exception.
  */
inline void* handmade_aligned_malloc(std::size_t size)
{
  void *original = std::malloc(size+EIGEN_DEFAULT_ALIGN_BYTES);
  if (original == 0) return 0;
  void *aligned = reinterpret_cast<void*>((reinterpret_cast<std::size_t>(original) & ~(std::size_t(EIGEN_DEFAULT_ALIGN_BYTES-1))) + EIGEN_DEFAULT_ALIGN_BYTES);
  *(reinterpret_cast<void**>(aligned) - 1) = original;
  return aligned;
}

/** \internal Frees memory allocated with handmade_aligned_malloc */
inline void handmade_aligned_free(void *ptr)
{
  if (ptr) std::free(*(reinterpret_cast<void**>(ptr) - 1));
}

解決方法

この二つの実装の齟齬によって今回のAppCrushは引き起こされた。 解決策は以下である。

Windowsの場合、ライブラリをDLLにすることで、Exportされた関数以外は外からは見えないのでconflictが起きない。 Linuxの場合や静的ライブラリを使いたい場合は、万事ハッピーな解決策はあまり無くて、Eigenをバージョンが全く同じで、同じコンパイル環境でビルドする。 もしくは、別のバージョンを使いたければ、それぞれのEigenに異なるnamespaceを付けることだ。

以下が参考になる。

http://eigen.tuxfamily.narkive.com/fweQWUaX/eigen-and-the-one-definition-rule

夜明け前

島崎藤村「夜明け前」を読みました。(青空文庫

長かった。。すごく読みにくいわけではないけれど、幕末から明治の人々の暮らしや考えが事細かに描写されていて、たいへんな長編である。

さすが名作だけあって、色々な書評があるので、立派な書評はそちらに譲るとして、何の知識も無い一俗人の感想を。

この本で終始テーマとなっているのは、主人公の半蔵が平田篤胤(平田派)から学んだ国学思想が王政復古への実現を果たしたように見えて、明治の文明開化によって裏切られていくさまである。 しかも西洋化という文明開化を推し進めたものは、尊王攘夷を声高にさけび、倒幕・王政復古を成し遂げた維新志士たちなのだからまた矛盾を禁じ得ないのだ。

ではなぜ国学は衰退し、脱亜入欧に突き進んだのだろう。

「夜明け前」では、明治以前の人々の暮らしとか風俗がリアルに描かれている。 半蔵は当時の農民というよりは庄屋なので特権階級であったが、それでも半蔵の父が病にかかると百度参りするしか無かったり、半蔵の子供(藤村の兄弟)も、何人か幼くして死んでいるし、藤村自身も栄養失調で子供たちを失っている。 つまり幕末から明治というのは、今では考えられないくらい文明が発達していない。

また飢饉などが来ると、隣村から融通してもらったり、年貢を軽くしてもらうための愁訴を実行したりするのだが、それ以上の根本的対策はなかなか組織的には行われない。大名や領主といった支配階級は支配階級で、人民の生産性を上げれば自分たちが得をするという発想もほとんど無かったようだ。

ところがそこに、西洋の知識が入ってくると、例えば馬鈴薯はやせた土地でも良く育つと言って広められ、多くの農民を救う。馬鈴薯は1600年頃には既に日本に入って来ていたにもかかわらず。 日本の農学は明治政府のもと1870年頃、アメリカから学者を雇い入れてようやくスタートする。

では明治以前、日本の学問とは何だったのか。メインはもちろん儒学である。孔子の教えである儒学とは一言で言うなら、徳で国を治める王道を説いたものである。

幕末に勃興した国学とは、その儒学を否定して、日本古来の思想や学問を目指そうという動きである。 国学は、本居宣長平田篤胤などが、「古事記」や「万葉集」を研究して体系化して完成させたとあるが、古事記万葉集を読んだことがあるだろうか? 日本人の思想にまで深く読めたと思っていないが、書いてあることと言えばだいたい、色恋沙汰と謀略謀殺、そして春夏秋冬もののあわれだけだ。

こういうことを考えると、やはり日本という国が明治維新まで長きにわたって高度に封建的であった(インドのカーストも同じだ)が故に、学問は完全に支配階級の所有物であって、人民のための学問というものが一部の医学を除いて、ほとんど発展しなかったのだろうと思う。

そうなると、当時の農民を始めとする人民にとって、日本古来の思想が何をしてくれるわけでもなく、飢えや病気から救ってくれる西洋文明と実験的・合理的な西洋思想を受け入れるのは当然の流れではないかと思ったのだ。

東大医学部 その2

同級生で東大医学部に行った友人二人目の話。

その彼も小学校の塾から一緒だった。つまり、一人目の彼ともみんな同じ塾出身だ。 その後中学・高校も同じで、長い付き合いだったので本当に仲のいい友人だった。

そして大学で進路は分かれた。彼は東大の理Ⅰに合格し、自分は東大に落ちた!

おや、理I?医学部は理Ⅲでは?と思われるかもしれない。

実は東大は1年目と2年目途中までの成績と、学生の志望を元に、三年生で進学する部科が決まる。 そして東大医学部は、理Ⅲのみならず、少数ながら理Ⅱや他科からも進学できる。

例えば2016年でいうと、64人を理Ⅲから(理Ⅲからは、志望すればほぼ無条件で行ける)。10人を理Ⅱから。3人を他の全科から取る。という内訳になっている。結果、3人の枠を争う理I、文科が最も高い点数が必要となる。

そのあたり以下のサイトに詳しい。 https://todai.info/shinfuri/medical.php

友人は東大に入った後一念発起し大学で勉強して(東大に入った時点で既に十分に勉強したのだが)、一年留年はしたものの、進振りで医学部に進んだ。 そして、元々子供好きだったのもあり、今は小児科医をやって超忙しい日々を送っているそうだ。すごい!

彼の性格はというと、小さいころから考え方が大人びた人間で、シニカルなおぼっちゃんという感じ。ガリ便では無いけれど決して道を踏み外さないタイプ。 でも、ゲーセンなんて不良の行く場所だと思っていた若き中学生の自分を、ゲーセンに連れ込んでくれたのは彼だ。

そのくせに、結局私の方がゲームにはまって、青春をゲーセンに捧げていると、「まだゲームなんてやってるの?」と突っ込んでくれたのも彼だ。 そんなシニカルな面はあれど、とにかく性格が良くてまっすぐな人間だった。

子供のために難病とも戦う彼にエールを送りたいです!

<備忘録> OpenCVのVisual Studioビルドにハマった件

OpenCV 3.0をVisual Studio2013でビルドしたものを使っていたが、Visual Studio2015に環境を変えたいので、VS2015でもビルドした。

無事ビルド完了し実行したのだが、RELEASEビルドが"BEX64 例外コードc0000417"で実行時に落ちる。 DEBUGビルドを実行すると、OpenCVの特徴点Matcherを呼び出した時に得た、マッチング結果の解放時に落ちている。

これはいわゆる、ライブラリとアプリケーションのランタイムの不整合が原因だなと思ってソリューションを見直してみたが、ランタイムはともにマルチスレッドDLLで違い無し。 どうもVS2015から、CランタイムのDLLがユニバーサルCRT(ucrtbase.dll, ucrtbased.dll)に変わったらしく、これが当初バグを含んでいたらしい。(この記事が詳しい)

https://dev.activebasic.com/egtra/2016/04/19/878/

ということで、検索結果にしたがってランタイムを正しいものに更新。するとDEBUGビルドは直ったのだが、RELEASEビルドが直らない。

DependencyWalkerで探ってみても、アプリとOpenCVとで呼び出しているランタイムは全く同じに見える。

結局、VS2013のアンインストール、VS2015の修正などさんざん試行錯誤したあげく、試しにOpenCV3.1.0をビルドしてリンクするとエラーが無くなった。 公式のビルド済みバイナリも、VC14バージョンの提供はOpenCV3.1.0からだから、OpenCV3.0 と Visual Studio2015(VC14)に問題があったのかな?それともOpenCVも全てソースからビルドしているわけでは無いので、ビルド済みバイナリのどれかが問題になったのかもしれない。

というわけで、使っているAPIが全て問題無く動作することを確認した上で、OpenCV3.0.0を捨てて3.1か3.2に移行する予定。

結論

ビルド&テスト済みのバージョンの組み合わせ以外は疑ってかかるべし

追記

OpenCVVisual Studioビルドでハマッタ時のレシピ 基本的には、以下の手順でビルドできます。

  1. OpenCVソースコードOpenCV libraryから取ってくる
  2. CMAKEでビルドする
  3. 作られたslnファイルを開き(Visual studio起動)ビルドする
    • DEBUGもしくはRELEASEで、ALL_BUILDを実行する。INSTALLをビルドすると、指定したinstallディレクトリにバイナリとincludeファイルをコピーしてくれる
ビルド失敗ケース
  1. CMAKEのconfigureが失敗する
    • プロキシ認証がある環境だと、ippicvやffmpegなどの3rd party製バイナリをダウンロード出来ずにhashチェックで失敗することが多い。この場合アドレスを調べ、手作業でダウンロードし、必要なディレクトリにコピーする(必要な場所はCMAKEに表示される)
  2. ビルドは成功したものの、実行時に落ちる
    • 一番多いのは、ライブラリのビルド時と、アプリケーション側のビルド時の設定が異なっているケース。まずはランタイムライブラリがちゃんと同じになっているか確認する。(マルチスレッド・マルチスレッドDLL・マルチスレッドデバッグDLLなど)
    • バージョンの組み合わせを疑う

スマホと会話

毎週子供をスイミング教室に連れて行っている。 そのスイミング教室に、上の子が習っている間、下の子を連れてきて待っているお父さんがいる。

その父親、待っている間いつもスマホでゲームをやっている。下の子がお父さんに話しかけてもゲームに夢中で応えないし、下の子がスマホを覗き込んで掴もうとすると、不機嫌にしかりつけるのだ。

最近はこういう光景が至る所で見られるようになったのだが、なんとも寂しい気分になってしまう。 先日外食にいった際などは、子供3人連れた5人家族がいたが、食事が出るまでずっと親はスマホゲーム、子供が3DSをやって待っていた。

彼らにとって子供とはどういう存在なんだろうか。 子供に夢を見ないのだろうか。子供は情熱を注ぐ対象では無いのだろうか。自分の経験を伝え人生の楽しさを知らしめ、大きく羽ばたいて欲しい存在では無く、それはただの義務なのか。

親が子供でしょうがない。。

東大 医学部 その1

東京大学の医学部といえば、言わずと知れた日本最難関の学部・学科である。毎年東大には3000人も入るが、医学部は100人ちょっとで、東大の中でも別世界と言われる頭脳が必要だ。

良く知った同級生に、東大医学部に進学した人間が4人いる。 彼らがどんな人物だったか、軽く紹介しようと思う。

一人目は、小学校で同じ塾に通った同級生。 中学受験を経験した自分は、6年生には二つの塾を掛け持ちしたが、その両方で同じクラスになった。

当時中学受験といえば最も権威があったのは四谷大塚で、四谷大塚定期テストといえば泣く子も黙る存在である。 四谷大塚で毎週日曜日に行われるテストは、上位100位だったかに入ると、全国に配布される結果に名前が掲載される。

受験戦争なんて世界が狭いもので、この四谷大塚定期テストで上位のさらに上位に入るのは、自分のような凡人には到達できない領域で、だいたいメンツも固定されている。そんなんで、こちらはいつしか名前も覚えてしまうものである。しかもこのあたりの成績優秀者はみんな結局東大に合格する(東大を志望すれば)。

彼もまた上位一桁とかそこらへんの常連で、結局小学生から大学受験まで受験エリートのまま理3に合格して医学部に行った。

ただ面白いのは、そんな受験エリートだが決して机にかじりつくガリ便のタイプではなく、塾が終わると同級生と肩を組みながら街で悪さをしたり、いつも他人をからかって遊ぶのが好きな奴だったこと。休み時間もたいていジャンプとか読んでたので、始終勉強に打ち込むタイプでは無かったと思う。みんなに"親分"と呼ばれ、典型的なガキ大将。

ところが高校時代に会った時には、随分身も心も落ち着いた感じで、ガキ大将の面影が薄くなっていたのが印象的でした。今は医師としても研究者としても立派にやっているようです。

ちなみに一度だけ塾内の小テストで自分が彼を上回り、点数順で席が並べられるシステムだったので彼を悔しがらせたことがある。よほど嬉しかったのか、今でも覚えている。

1984年

ジョージ・オーウェルの「1984年」を読みました。

Amazon CAPTCHA

読んで数日は、その世界観が頭の中をグルグル回っておかしな気分になりました。 それくらい衝撃が大きかったですね。

内容はいわゆるディストピアものですが、読み始めてすぐに、映画「未来世紀ブラジル」を思い出しました。テリー・ギリアム未来世紀ブラジルを、1984年版「1984年」と言っていた意味がようやく分かりました。全体主義の恐怖や、記録省・情報省・情報隠滅のためのダクトなど、世界観を作りだす要素が少しだけ形を変えて共通して出てくる所なんかも。

これを読む人は、あくまで小説の世界であって現実には起こりえないと思うかもしれませんが、一度全体主義や情報統制が強固になってしまうと、再び自由な世界に戻すことが如何に難しいことであるかを、想像させてくれます。

実際世界にはそういう状況に陥っている国がいくつもあるし、日本も近年は右傾化・全体主義の流れを感じます。自由は無条件で保証されているものではなく、命がけで守るものだという事を忘れないようにしたい。