最近玩 AI 一直在編譯別人寫的軟體。 最常遇到的問題, 不是別人寫的程式有錯, 而是我的環境跟他的環境沒有 100% 相同, 所以原來 ok 的程式碼, 到了我的環境就編譯失敗。 以下是我 編譯 ENet 時學到的一些除錯技巧與心得。
如果你想要編譯的軟體是很熱門的軟體, 說不定已經有人打包好 docker 了 -- 搜尋一下, 直接抓回來用比較快。 或是沒有建好的 docker, 只有 Dockerfile 可看, 一步一步照著做也比自己在黑暗中摸索要輕鬆很多。
如果沒有, 只好自己編譯。 開始編譯之前, 可能要先按照指示安裝一堆套件。 例如 ENet 用到修改版的 caffe, 所以要先編譯 caffe。 再次地, 大致照著 caffe 的 Dockerfile 的步驟, 參考 編譯文件 做, 可能會比只讀文件簡單很多。 如果可以搜尋得到已包好相依套件的現成 docker, 也可省下很多工。 例如我拿 ckhung/cvbstnpy 這個 docker 當基底, 所以可以省略 (很難編譯的) opencv、 boost 等等套件。
說編譯其實並不精確。 從設定檔、 原始碼到最後執行檔的完整流程, 稱為 build。 Build 通常包含 configure、 compile、 link 三個階段。 還有不少 AI 程式用到 cmake, 但我很不熟悉。
Compile 過程常會遇到找不到 include 檔的錯誤。
從 這裡 學到:
先用 find / -iname 'xyz.h'
找到你的 header file, 再用 make SHELL='sh -x'
重新執行一次。 這會把每個指令完整展開,
以便查看 gcc 或 g++ 到底去哪些目錄尋找 header files。
從 gcc/g++ 命令列上的諸多 -I 選項當中找出一個跟實際路徑長得最像的,
再來修改。 例如我在 make pycaffe SHELL='sh -x'
的時候, 發現 g++ 找不到 numpy/arrayobject.h 。
用 find / -iname arrayobject.h
找到它真實的位置是在
/usr/local/lib/python3.5/site-packages/numpy/core/include/numpy/arrayobject.h 。
而 g++ 的命令列上則發現了一個長得很像的
-I/usr/local/lib/python3.5/dist-packages/numpy/core/include 。
所以最簡單的解法就是: cd /usr/local/lib/python3.5 ;
ln -s site-packages dist-packages
我第一次編譯時, 為了想知道 gcc/g++ 到底從哪些目錄 include,
還搜尋過
1、
2、
3 等等問答; 不過最好用的應該還是 make SHELL='sh -x'
這招。
這招也適用於 link 階段。
比方說 g++ 抱怨找不到 boost_numpy3 函式庫,
就用 find / -iname '*boost_numpy3*.so*'
查出函式庫實際的位置 (因為全名可能叫做
libboost_numpy3.so.1.64.0 之類的),
再跟 make SHELL='sh -x'
印出的
g++ 命令列上所有 -L 路徑比對。
如果原始碼是 c++ 程式, link 時還可能會遇到
g++ name mangling 的問題:
g++ (其實是 ld) 抱怨找不到某個函數或變數,
但那個名字看起來像亂碼。 這時可以用
c++filt 把它轉換回原始碼有意義的函數名稱, 類似這樣:
echo _ZNKSt13runtime_error4whatEv | c++filt
得知它其實是這個函數: std::runtime_error::what() const
這樣才能繼續 google 追問題。
我從原作者那邊 clone 出一份 ENet, 修改的地方很少, 主要加上一個修改過的 caffe-enet 的 Makefile.config , 及加上一個 Dockerfile, 可當作本文的參考練習範例。 建好的 docker 在 ckhung/enet, 可以拿來做 街道圖片 semantic segmentation 語義分割。
雖然只看得懂一點點,不過就是在講解決問題的方法。
回覆刪除Linux 下常常要解覺問題,這種解決問題的方法的文章很有價值,即使不一定每個人都用的到。