發表文章

目前顯示的是 2011的文章

C/C++ 檢查和打開 debug 功能的小技巧

打開 debug flag 有助於了解模組的行為。工程師開發時應該都有幫自己除錯留後路, 所以找到他們開發的方式, 可省下自己瞎試的時間。 以前寫 Python 或 Java 時, 都有近乎標準的 logging module 可用, 所以要找到開啟 logging 機制的方式滿簡單的, 看是用那家的 logging module, 針對它的設定檔設 log file, 或在程式開始時塞段程式啟動 logging 即可。不過 C/C++ 好像沒有那麼一致。 留意程式碼會看到輸出訊息的巨集, 像是 P("What the F**k!!");, 然後在某些 debug 相關 header 檔裡會看到類似這樣的東西:#ifdef DEBUG #define P(x) ... #else #define P(x) ; #endif 其中 "..." 的部份是該模組用來輸出除錯訊息的機制, 可能是寫到檔案, 或輸出到 stdout/stderr 等。這樣在沒有 define DEBUG 的情況下, 完全不會產生除錯訊息的程式碼, 減少執行負擔。 要確定程式真的有編進除錯訊息的話, 隨便找個程式內有的除錯訊息, 然後用 strings 檢查: $ strings PROGRAM | grep "What the F**k!!" 看有沒有結果。有的話大概就 ok (找個少見的字串最穩), 不然就是沒編進去, 檢查看看 #define DEBUG 1 有沒有設好, 或是有沒有在 makefile 的 CFLAGS 設好 -DDEBUG。 再來, 也可以用 strace 檢查執行期間到底有沒有執行到: $ strace -e write PROGRAM | grep "What the F**k!!" 不過 write 寫入的字串不見得會一次剛好符合一整串, 這和檔案 flush 機制有關。找子字串, 或用眼睛注意一下比較穩一些。懶得找方法打開 log 檔的話, 用 strace -e write 將就看也還可以, 不過會拖滿執行速度, 且 strace 有時會影響 process 的狀態, 常期用的話還是打開 log 檔最穩。

打開 core dump 和使用 cgdb 檢查程式掛點原因

前置動作首先, 用 gcc/g++ 編程式時記得加 -g 以加入除錯資訊。 接著參考《產生 core dump 的方法》設成可以產生 core dump。 檢查 backtrace$ gdb PROGRAM 然後在 gdb 內執行core core.PROGRAM.PID.TIMESTAMP 接著就能用 bt N 看最底層 N 個 call stack 為何, 也就是所謂的命案現場啦。然後可用 up, do, l 等指令切換 call stack 和列出週圍程式。 在和 gdb 相處一段時間後, 覺得這樣在 stack 之間移動很方便, 但看週圍的程式太辛苦了, 還是會開另一個視窗用 VIM 看完整一些的程式。 看到 jserv 建議使用 cgdb, 試了發現, 人生...啊不, 是視窗變彩色的了!! 不只有彩色的程式碼, 還外加類 VI 的瀏覽方式, 相當順手。目前有用到的功能如下: 按 i、ESC 在程式碼視窗和 gdb 命令列之間切換。 程式碼視窗裡可用 VI 的部份指令移動行數; 回到命令列後和 gdb 完全相容, 可按上下鍵選用之前的指令。 在程式碼視窗按空白鍵加減 break point。官網文件有詳細的說明, 之後來掃一遍, 看有什麼好東西可用。

善用 strace、debugger 從執行期間找出問題根源

最近被迫在短時間內學會 strace、gdb 這些之前一直用不到的重兵器, 都還不熟練就是了。剛好使用 hgsubversion 時有些困擾, 雖說它和 svn 整合得很好, 無縫接好 pull / push, 但它不會顯示 mercurial 對應到的 svn 版本, 平時看其它和 svn 整合的工具 (如 issue tracking) 會很困擾, 用得都是 svn 版號。 剛剛想到可以學 Strace -- The Sysadmin's Microscope 的做法, 用 strace 找出關聯的程式, 再來看怎麼修正它。 我推測 hg 一定有記錄 svn 相關版本的方式, 不然無法和 svn server 同步資料。於是挑個會讀到 svn 資料的指令來試:strace -o trace.log -s 512 -e read,open hg svn info 用顯示的 svn 版號 123 來翻 trace.log, 發現這兩行:open("/path/to/project/.hg/svn/lastpulled", O_RDONLY) = 3 read(3, "123\n", 4096) = 5 於是找到 meta data 存在 .hg/svn/ 下。 到該目錄下找到 .hg/svn/rev_map 這個檔案, 裡面存 hg 和 svn 的版號對應表。至少這樣就有足夠的材料寫個 script 來轉換 hg、svn 的版號。 不過若能直接加到 hg 裡, 應該會更方便也較可攜。要做這點相對容易, 可以到 hgsubversion 原始碼目錄下用 ack 找相關程式。 先用 ack rev_map 找到存 meta data 的物件 revmap, 再用 ack revmap 找到 wrappers.py 是換掉 hg 指令的程式。 再來用 pdb 觀察 revmap 如何被使用。先在 wrappers.py 裡設中斷點, 然後執行 pdb /usr/local/bin/hg parents --svn 找出 meta data 如何被讀出來。於是明白可在函式 parents 的部份塞入幾行顯示 svn 版本:--- a/hgsubversion/wrappers.p…

加速 linking time

頻繁修改一點程式又要執行看結果時, 主要的時間都花在 linking。這時才體會到省下 linking time 也是很重要的事。 這篇提到兩個減少 linking time 的做法 使用 ramdisk 用 gold 我本來就有用 SSD, 改將編譯結果全放到 ramdisk 後, 提昇的效果不怎麼明顯。到是用 gold 後減少了一半的 linking 時間。 不過 gold 也不是那麼完美, 這篇提到一些問題。我自己用的時候發覺滿常遇到 ld 可以編, 但 gold 不行。最後的解套方式是寫個小 script 切換 /usr/bin/ld 連到的程式。不常編的東西就暫時換回 ld 連結個一次就好。 另外 jserv 提到 gcc 有命令列參數可直接指定用那個 linker (ref.)。wens 則查到 Debian/Ubuntu changelog 裡有寫 下可用 -B/usr/lib/compat-ld 和 -B/usr/lib/gold-ld for ld.gold。 順便 google 一下看怎麼查 changelog, 似乎是看 /usr/share/doc/binutils/changelog.Debian.gz 或用 aptitude changelog PKG, 不過這兩個作法都只有最近的 changelog, 沒有完整記錄, 像查 binutils 時就沒有找到上面提的參數說明。

iPad: waiting for sync to start 的解法

沒想到我也會有寫這種 3C 裝置文章的一天。 今天用 Windows7 的 iTunes 連 iPad 要安裝新的 app, 結果一直出現 "waiting for sync to start", 等很久後就說失敗。聽同事說可能是在不同台電腦用 iTunes sync, 有時會錯亂, 他以前也遇過。google 到不少人有類似經驗, 不過沒什麼好解法, 大概的做法是 將 iPad 的連接線拔掉重插看看 按住上方電源鈕和 home button 關機, 再重開看看 將 iPad 回復到原廠設定看看 移掉 iTunes, 再重裝看看 很不幸的, 最後是做完最後兩步才 ok。重裝後有些東西可能會不見, 軟體是認 Apple ID, 影響不大。聽說有些 in-app purchase 的東西 (如遊戲道具?), 不會重發的。 btw, 雖說 Apple 的產品包裝得相當貼近消費者, 對我這種習慣用 console 的人來說, 有錯誤卻看不到任何詳細資訊, 實在是很悶的事。只能亂 google 和無腦亂試。

列出用到的 shared library

本文整理大家的建議和自己實際操作的心得。新手上路, 有錯還請指正。 static library 就是一包 object file, 沒什麼需要提的, static library 沒有記錄其它資訊。所以, 編 shared library 或 executable 時要自行處理好 static library 的相依性, 在前篇有提到一點資訊。 shared library 有兩種, 一種在 linking 時要指定好 shared library 用到的 undefined symbol 放在那些 shared library 裡, 待執行時再載入到記憶體使用; 另一種用 dlopen() 和 dlsym() 載入 (這兩個函式存在 libdl 內)。 前者比較單純, 可用 ldd 透過靜態分析了解用到那些 shared library, 且各自實際指到的檔案。ldd 本身是一個 shell script, 用到 ld.so 事先定義的一些機制 (LD_TRACE_LOADED_OBJECTS) 來讀取資料, man ld.so 裡有相關的說明。其中 LD_LIBRARY_PATH 和 LD_PRELOAD 相當實用, 無法在連結時解決問題時, 至少還有這招可在載入時處理。若想分析透過 dlopen() 載入的動態函式庫, 有幾個做法在程式執行中觀察 /proc/PID/maps, 這個檔案記錄 process 用到的各區段記憶體為何, 可從對應到的檔案看出有載入的 shared library。必要時可配合 gdb 在想觀察的部份停住, 再從外部看 /proc/PID/maps。這裡或 man proc 有相關說明。用 strace 執行程式, 觀察開啟的檔案: strace -f -e open PROGRAM 2>&1 | grep "\.so"就我自己小試的心得, 看 /proc/PID/maps 最穩, 且方便看各別 process、thread 載入的函式庫, 也不會拖慢觀察目標的執行程式。不過 strace 不需配合 gdb 停在該停的地方, 就「快篩」的角度來看, 也滿有用的, 加上 -f 後方便追蹤 multi-process、multi-thread, 不過執行速度好像有慢一些, 不太確定。之後再比較看看兩…

用 file 查看文字檔編碼或執行檔為 32bit 或 64bit

實用的例子:file -i TEXT_FILE: 會顯示 charsetfile BINARY_FILE: 會顯示 32-bit 或 64-bit 當然還有其它更多資訊, 目前這兩個對我來說很實用, 希望這樣寫過一次多少會記久一點 ...。

解決 undefined symbol / reference

C++ 新手上路, 有錯還請幫忙指正。 基本觀念相較於 script language 或 Java 來說, C/C++ 有完整的「編譯 -> 連結 -> 執行」三個階段, 各階段都可能發生 undefined symbol。在解決惱人的 undefined symbol 前, 得先明白整個編譯流程: 編譯 .c / .cpp 為 .o (object file) 時, 需要提供 header 檔 (用到 gcc 參數 -I)。事實上, 在編譯單一檔案時, gcc/g++ 根本不在意真正的 symbol 是否存在, 反正有宣告它就信了, 所以有引對 header 即可。這也是可分散編譯的原因 (如 distcc ), 程式之間在編譯成 .o 檔時, 並沒有相依性。 用 linker (ld 或 gold) 將 *.o 連結成 dynamic library 或執行檔時, 需要提供要連結的 library (用到 gcc 參數 -L 指定目錄位置, 用 -l 指定要連什麼函式庫)。不同於前一步, 此時 symbol 一定要在。 執行的時候, 會再動態開啟 shared library 讀出 symbol。換句話說, 前一個步驟只是檢查是否有。檢查通過也連結成 executable 或 shared library 後, 若執行時對應的檔案不見了, 仍會在執行期間找不到 symbol。若位置沒設好, 可能需要用 LIB_LIBRARY_PATH 指定動態函式的位置, 但不建議這麼做, 最好在執行 linker 時就指定好位置。原因見《Why LD_LIBRARY_PATH is bad》。明白這點後, 就看 undefined symbol 發生在那個階段, 若是編 object file 時發生, 就是沒和編譯器說 header 檔在那, 記得用 -I 告訴它。若在 linking 時發生, 就要同時設好 -L 和 -l。不過難就難在要去那找 undefined symbol 的出處。 解決問題的流程首先是判斷 symbol 是不是自己用到的原始碼裡, 可配合 id-utils 找看看 (我是用 gj, 比較方便一點)。或是看有沒有 man page, 有 man page 的話, 裡面會記錄用到的 header 和該怎麼下連結參數。若在專案裡找不到, …

用 Eclipse CDT 讀 C/C++ 原始碼

上篇, 記一下 Eclipse CDT 的用法, 全部加起來耗去我不少時間啊。 1-1. 匯入舊的非 Eclipse CDT 專案: File -> New -> Makefile Project with Existing Code 之所以選用沿用舊的檔案位置, 而非開新專案再匯入程式碼, 是因為這樣比較方便和 VCS 共存。可先用 VCS ( git/hg/svn/... ) 取出專案, 再用 Eclipse CDT 來讀碼。VCS plugin 再怎麼成熟, 都不會比直接用 command line 或專屬的 GUI 來得完整。 1-2. 選 Toolchain for Indexer Settings 選 GNU Autotools Toolchain。 少勾這個, 之後匯入程式碼後會有 Type 'std::string' could not be resolved 這類鳥錯誤。因為少加了預設 header。實際要選那個 toolchain 要看開發環境而定, 沒有仔細研究, 至少在 Ubuntu 上這樣做 ok。 2. 將 Indexer 範圍改為全部 Window -> Preference -> C/C++ -> Indexer 將 Index unused headers 和 Index source and header files opened in editor 勾起來。剩下加減看看, 我有調大 Cache limits, 不知影響多大。 雖然 Eclipse CDT 會猜測 include 那些檔案而只 index 那些檔案的 symbol, 但 C/C++ 的 include 似乎是很混亂的世界, 有 macro, ifdef 等東西混在裡面, 很難光用靜態分析搞定。對我這一直活在安全的 Java 和 Python 世界的人來說, 真是晴天霹靂, 花了不少時間才明白這事。 所以結論是, 都 index 就是了, 多花些時間總比找不到來得好。還有, C++ 的世界是很危險的, 能活在 Java 或 Python 的世界的話不要過來。 3. 建 index 在左側的 Project Explorer 的 project 名稱上按右鍵, 選 Index -> Rebuil…

閱讀 C/C++ 原始碼的好幫手

最近有需求讀 C/C++ 的東西, 試了 ctags, cscope 覺得不理想。問了一下收到許多回應 (G+plurk ), 真是太感謝大家了, 減少入門摸索的時間。 試用的感想如下: grep優點: 好上手缺點: 陽春安裝: 內建於 Linuxgtags優點: 可找 caller 和 callee缺點: 因為索引檔是由 ctags 來的, 會漏東西; 執行方式也有些不便安裝: 程式很久沒人更新了, 要做一些修正才裝得起來 參照官網指示 make 時看少了什麼 header, 手動補一下 header 然後 make 還是會失敗, 將 gas.py 的 "import as" 改為 "import asm", 下面用到的模組名也要跟著改, as.py 也要改為 asm.py。python 2.6 後 as 是 keyword 編好後將幾個用到的 python scripts 第一行由 python2.4 改為 pythonack優點: 比 grep 容易使用, 省得配合一些 command 過濾檔案, 見官網的《Top 10 reasons to use ack instead of grep.》。而且還有彩色的輸出!!缺點: 因為沒建 index 的關係, 速度較 id-utils 慢, 我的測試情境要 3s, 而 id-utils 只要 0.006s安裝: curl http://betterthangrep.com/ack-standalone > ~/bin/ack && chmod 0755 !#:3id-utils優點: 速度快, 和測 ack 同樣的情況, 建索引 5.3s, 之後搜尋瞬殺缺點: 介面沒有 ack 直覺易用, 我寫了個小程式 gj 以 id-utils 為底, 提供彩色輸出和進一步過濾檔名的功能。 安裝: Ubuntu 超容易, aptitude install id-utilsEclipse CDT優點: 方便開新視窗看 caller、callee缺點: 不方便搭 vim 使用 (對 vim 重度使用者才有差); 建 index 有點久, 我的測試情境要數分鐘到十分鐘吧安裝: 結果這個是我試最久的, 因為不知怎麼建 index。參考官網 FAQ, 建索引前要先設…

Web basic access authentication

之前都沒注意到, 原來用 apache2 做使用者身份認證 (如使用 htaccess) 時, 跳出來詢問使用者名稱和密碼的對話框並不是網頁, 而是 client 軟體提供的輸入框。 《Basic access authentication》對此有詳細說明, 或用 Firefox 的 HttpFox 觀察 http header request 和 response 也不錯。幾個重點: basic authentication 的傳輸沒有加密, 最好在 SSL 下使用 server 發覺沒通過身份認證時會傳 401 並在 header 裡附上 WWW-Authenticate: Basic ... 的訊息 client 送出 Authorization: Basic ... 的訊息, 其中 "..." 是 base64(USERNAME + ":" + PASSWORD) 除關掉瀏覽器外, 沒有明確的方式「登出」這種登入方式

VirtualBox 設 shared folder

《How To Share Files In VirtualBox With Vista Guest And Ubuntu Host》設定 host OS。若 client 是 Linux, 見 《HOWTO: Use Shared Folders》, 就一行指令啦:sudo mount -t vboxsf SHARE_FOLDER_NAME /mnt/share SHARE_FOLDER_NAME 是在 host OS 設定時打的名稱。

在 Windows 上安裝和設定 VirtualBox 和 Ubuntu

CPython 的 garbage collection

來消化之前有讀沒時間寫的東西。 長期使用 python 後, 會很納悶為啥記憶體一直漲, 明明已沒用到了, 卻沒有減少。這篇提到 CPython 的 gc 機制採用 reference counting, 另有備案的 mark and sweep, 用來解決 circular reference。btw, 強烈推薦 Back to basic: Series on dynamic memory management, 淺顯地介紹各種 gc 運作的方式。 照 Scott 的說法, reference counting 算是苦工的半自動化管理記憶體, 因為實作細節交給實作者處理, 用 Python API 寫 CPython 的 extension 時, 開發者得自己管理物件的 reference count, 是件很辛苦的事。好處是在 reference count 為 0 的時候, 會立即回收記憶體。但缺點是, 若 a、b 兩物件互相參照到對方, 卻沒被任何人用到的話, a 和 b 都無法被回收, 因為 reference count 永遠會是 1。 為解決這個問題, 只好多提供 gc.collect 喚起 mark and sweep 的演算法清掉 a、b。但 mark and sweep 也有自己的問題, 主要有兩點: 不像 reference counting 會在沒用到的第一時間點立即回收, 而是 mark and sweep 執行後才回收。 執行 mark and sweep 的時候為確保沒有算錯 reference graph, 必須暫停目前所有執行中的 python process/thread, 結束後才可繼續執行。可想而知, 記憶體用量大後, 這個暫停時間也會太久, 不適合需要不斷有回應的程式 (如 GUI、Web)。 所以又有了 generational garbage collection, 關鍵的想法是: 觀察到大部份物件都很早死 (英年早逝啊~), 所以只要回收年輕的物件即可回收大部份的記憶體。於是將使用到的物件分不同「年代」, 預設只回收最年輕的一代, 沒回收到的物件就放到下一代。下回要再回收的時候, 就不動這個舊一代的物件, 減少要建立的 reference graph。 回想平時寫程式的結構, 可以想見, 像頻繁使用的 l…

Effective C++ item 30: 了解 inline

之前寫的《C++ inline 用法背後的原因》有一點小錯, 原來也有可在連結時期做 inline 的建置環境, 如 .NET CLI。另外提到 template 的實作通常要也要寫在 header 裡, 理由同 inline, 這樣 compiler 才知道怎麼具現化它。 此外, 這則條款提到 inline 的特性只是建議, 不是強制行為。並強調 inline 的缺點有 若函式有點大且頻繁地使用, inline 後 object code 會變很大。可以想見會浪費硬碟空間和記憶體, 也可能更容易造成記憶體換頁而減低 instruction cache hit rate。 在更改 inline 後的函式時, 使用到 inline 函式的 object code 必須重編。若沒用 inline 且是使用 dynamic linking 的話, 可以完全不動客戶端的程式。 大部份 debugger 不支援設中斷點 第一點的負擔比較難評估, 第二、三點到是滿顯見的問題。所以結論是, 除簡單的 getter 或 max / min 這類小函式, 盡量別用 inline, 直到發覺某個小函式占據可觀比例的執行時間後, 再考慮使用 inilne。

ARM Instruction Set 初步心得

之前看了一點點 ARM 的東西, 做個記錄。我完全不熟這個領域, 下面的心得可能會有許多錯誤。 《Tonc: Whirlwind Tour of ARM Assembly》這篇超級詳細地從頭教怎麼寫 ARM assembly code。作者原本的用意是教人寫 GBA, 而 GBA 底層是跑 ARM, 所以會需要寫 ARM assembly code 最佳化。 看完這篇後再來看大方向的觀念《ARM架構》, 就很有感覺了。對一個大學時代只寫過一點 x86 assembly, 只在課本上看 RISC 的人來說, 讓我比較印象深刻的是 ARM 的指令相當精簡, 很容易懂。概念上來看, 硬體成本應該會較低較省電。 為減少指令過於精簡不方便使用或效率差的負擔, 強化了一些功能。又一個好例子可用來說明系統設計是一連串的取捨。 ARM 的 shift 沒負擔, 透過 Barrel shifter 可同時在 OP2 做 shift / rotate 等操作。 ARM 的所有指令都有另留幾個 bit 以支援 conditional code, 藉此減少需要猜測 branch 的負擔。猜錯 branch 得另外重取要用到的指令和資料, 時間成本似乎滿大的。這裡借用《Tonc: Whirlwind Tour of ARM Assembly》實作 max() 的範例程式表示什麼是 conditional code:@ // r2= max(r0, r1): @ r2= r0>=r1 ? r0 : r1; @ Traditional code cmp r0, r1 blt .Lbmax @ r1>r0: jump to r1=higher code mov r2, r0 @ r0 is higher b .Lrest @ skip r1=higher code .Lbmax: mov r2, r1 @ r1 is higher .Lrest: ... @ rest of code @ With conditionals; much cleaner cmp r0, r1 movge r2, r0 @ r0 is hi…

超輕量級的 python web framework: Bottle

寫完上篇冗長的 Django 心得後, 改來介紹極端相反的 web framework: Bottle。 有一則關於 Python 的笑話是這麼說的:Python is the only language with more Web frameworks than keywords. 在初評估 web framework 時, 對照一大串 web framework 看到這則笑話, 實在是令我哭笑不得。不過在有不同需求後, 到覺得這樣也是有好處啦。 當初要做 ego-post 的時候, 想找個很方便部署的框架, 比較方便推廣, 最終目的是讓一般人也能用 (後來發覺寫成 Chrome App 更適合)。我的主要功能用 javascript 和 html5 (用到 localStorage) 實作, web 後端只是用來存長期資料而已。一開始是試 Flask, 但裝 Flask 需要另外裝 Werkzeug 和 Jinja 2, 所以最後改用 BottleBottle 有多小呢? 全部就一個 bottle.py 而已, 所以懶得要求使用者另外裝 Bottle 的話, 將 bottle.py 加到目前專案就結束了。而且它不到 3000 行, 有興趣了解最基本的 web 應用需要那些功能, 看 bottle.py 應該可以學到不少東西。像 http response code 418 I'm a teapot 這種蠢東西, 就是從 bottle.py 裡學到的。

Effective C++ item 23: 以 non-member、non-friend 取代 member function

看到這條款有種水到渠成的快感, 來寫下心得。 在我大學寫 Java 四年的時光裡, 我寫了一堆 over design 假 OOP 的東西。於是, 在我出社會工作的三年裡, 我改用 Python 盡可能的都寫成 function, 覺得很卡時 (像是一堆參數重覆傳來傳去), 才會包成 class。想體會看看兩種極端的寫法, 藉此看看能否從中悟出什麼道理。 這七年的時光合起來, 我沒悟出什麼大道理, 到是覺得後來這種寫法偶而是有點不便, 不過大部份時候還挺順的, 不會包得太笨重。直到看到 Effective C++ item 23, 才有種恍然大悟的感覺, 明白背後的原因。 作者提出一個觀點: 愈少程式能動到資料, 封裝的效果愈好。所以, 若這個操作可以寫成 non-member、non-friend function, 可保證它不能存取到 private data。再者, 也比較有機會讓不同 class 來重用這個 function。像是 std 裡面的 sort(), 就比放到 vector 裡成為 member function 來得好。既可提高 vector 的封裝效果, 也方便重用 sort (有提供 index 和 comparable 即可)。這篇兼顧「減少相依性」和「提高重用性」切入說明這則條款的精髓, 值得一讀。 初看這個觀點有些反直覺, 還是會覺得放到 class 裡比較像「封裝」。要找函式時也比較方便, 看 class 的 header 就可知道全部操作。但 item 23 接著強調: 若要方便找函式, 放在 namespace 下也有類似的效果, 如同 Java 寫成 utility class 的 static member function 一樣。 回想 Java 的 Arrays.sort() 和 Collections.sort() 也是同樣的設計, 只是 Java 沒 namespace, 這類函式改放到某個 class 下。當然, Python 的 sorted() 也是如此, 不過是放在 __builtin__ 這個 package 裡。其它類型的還有 max()、min() 等函式。Python 和 Java 都有提供方便的 max()、min() 來操作 container, 不知 C++ 有沒有類似的東西, 只…

C++ 建構式和解構式使用 exception 的注意事項

item 08: Prevent exceptions from leaving destructor 一樣是要避免未定義行為, 清資源清到一半卻因 exception 而沒做完, 是不道德的, 有機會造成更多 memory leak。 《[17.9] How can I handle a destructor that fails?》指出更嚴重的問題, 當 exception A 發生時, C++ 不會執行剩下的程式, 而會跳到 catch A 的部份。但跳到 catch 前, 要先清掉這之中的 call stack, 自然會觸發各 function call 的 local variable 的 destructor。若在這時又丟出 exception B, 沒辦法決定該執行 catch A, 還是跳去執行 catch B。忠孝兩難全, 只好要求 process 自盡結束它短暫的一生。 所以說有 destructor 也不是都是好事, 像 Python / Java 沒法和你保證什麼時候會呼叫 "destructor" ( Python 的 __del__、Java 的 finalizer ), 自然不用擔心有這種 exception 中的 exception 兩難。只會另外建議你不要用 "destructor"。 書中另外建議, 若想兼顧「自動回收資源」 (環保做功德) 和「避免在 destructor 內吞掉 exception」, 可另外提供方法讓使用者可呼叫函式清資源, 再自行處理可能丟出的 exception (像關 db connection)。若使用者忘了呼叫這類方法, destructor 再做最低限度的保護, 幫忙呼叫該函式清資源。不過有 exception 時, 使用者也沒得抱怨 destructor 吞掉 exception 沒處理。 那 constructor 內可不可以丟出 exception? 照理說要能丟比較何理, 不然怎麼處理 invalid argument? 查一下 FAQ, 果然有令人愉快的答案: 《[17.8] How can I handle a constructor that fails?》《[17.10] How should I handle resources if…

C++ 建構式和解構式使用 virtual 的注意事項

原本打算快速掃過一遍《Effective C++ 3/e》, 有個大略印象, 之後再視需求仔細看相關守則, 但看到一半發現忘了一堆前面看過的東西。還是來寫寫筆記好了。 item 07: declare destructors virtual in polymorphic base classes。這樣 delete base pointer 時, 才會呼叫到真正的 destructor, 然後正確地依序往上呼叫各類別的 destructor。 item 09: never call virtual functions during construction or destruction。理由是 C++ 在 constructor / destructor 裡沒有多型的效果, 因為 C++ 認為在建構或解構時呼叫子類別的函式有危險, 有可能用到未定義的資料, 乾脆定成在 constructor / destructor 裡沒有多型的效果 續上則, 補充說明原因。建構式的呼叫順序是由上往下, 而解構式是由下往上。所以若在建構式呼叫子類別的函式, 該函式可能用到子類別特有的欄位, 這時自然還沒初始化, 導致未定義行為。反之, 若在解構式呼叫子類別的函式, 子類別的解構式已清掉它的欄位, 這時再使用到也會導致未定義行為。 續上則, 值得注意的是, 在 Java 裡沒有禁止這麼做。Java 類別欄位的 (class field) 的所有型別都有預設的初始值 (像是 0、false、null 等)。Java 任何時候呼叫任何函式, 都會有多型效果。在建構式或解構式時這麼做, 會呼叫到子類別的函式, 至於這類的行為是否符合需求, 就自行判斷啦。下面附上 Java 的測試碼, 順便測一下欄位和方法的行為, 注意兩者行為不同, 方法有多型效果。class A { String m = "A.m"; void p() { System.out.println("A.p(): " + m); } A() { System.out.println("A()"); p(); } } class B extends A { String m = "B.m"; B() { System.o…

C++ inline 用法背後的原因

看過編出來的組語碼後, 深刻感受到 inline 可以省掉大量操作記憶體的時間, 特別是函式內容簡短又常被呼叫的話, 也許在 stack 上操作記憶體的時間比實際運算的還久。 初學 inline 語法讓我很困惑, 它是如此地令人困惑以致於 FAQ 裡有數則說明:[9.6] How do you tell the compiler to make a non-member function inline?[9.7] How do you tell the compiler to make a member function inline?[9.8] Is there another way to tell the compiler to make a member function inline? 令人困惑的點在於, 無論是 non-member function 或 member function, 若要使用 inline, 函式的定義都要寫在 header 裡。否則 compiler 在編譯時會抱怨「undefined reference to ...」 -- 一個不明確的錯誤訊息。 直到看了 compiler 和 linker 運作的基礎知識後, 才明白是怎麼回事。原因是 compiler 一次處理一個 .cpp 檔以及它引入的 header (由前處理器引進來的, 所以其實還是只有處理一份原始碼檔案)。所以在使用 inline 且函式定義寫在另一個 cpp 檔時, compiler 無法進行 inline 最佳化。也許是為了方便實作 compiler / linker (或為了符合既有的工具?), 而如此設定 inline 的語法吧。 附帶一提, 看了《淺談 GCC 編譯技術 Break Compilation Boundaries with GCC》才知道 compiler optimization 有分不同層級, 像是 Link-time optimization。若要移除沒用到的程式, 就得在 link-time 才能確定某些程式真的沒有被用到。

小心 C++ 的 non-local static 物件的初始順序

Java 有保證在第一次使用 class 內任何東西時, 會執行 class initialization 的程式。而 C++ 沒有保證不同編譯單元的 non-local static 物件的初始順序。 編譯單元是指同一 object file 的原始碼和其引入的檔案 static 物件指不在 heap 也不在 stack 上的東西, 包含全域變數、namespace 上的變數、class 內 static member 等 這會導致一個可怕的事實: 使用另一個 object file 的 namespace 下的物件時 (這是滿常見的需求), 會導致未定義的行為。《Effective C++ 3/e》item 4 的建議解法是用函式存 local static 物件, 然後用函式取得該物件 (類似 singleton 的概念)。 用程式來說, 就是:// Bad! Lead to an undefined behavior if clients access tfs. FileSystem tfs; // Good! FileSystem& tfs() { static FileSystem fs; return fs; } 附帶一提, 不同語言的規範差真多, 之前看 ARM 的組語, 才發覺之前只知道 x86 的東西, 而對 CPU 有些誤解。現在確信只學單一工具會有風險, 思維會受限於該工具, 誤以為那就是世界。人生有許多事也是如此, 想到莊子所說的話:井蛙不可以語於海者,拘於虛也;﹔夏蟲不可以語於冰者,篤於時也;曲士不可以語於道者,束於教也。 體悟漸多後, 漸漸也不知要寫什麼心得才好。總之就是多看看, 時時提醒自己所知很窄, 別錯將一種方法當作只有這種方法。

x86 呼叫函式的設計慣例 (call convention)

在看了《BUFFER OVERFLOW 5 - C/C++ Function Operation》後, 才明白 stack frame 的細節, 果然教人攻擊的文件講得最清楚啊。後來又看了《x86 calling conventions》, 才明白 compiler 編譯高階語言為組語時, 只是定好一套規範, 確保能和 OS 的 linker、loader 搭起來, 到沒有規定一定得怎麼做。編譯的其中一項設計, 是規範 caller 和 callee (被呼叫的函式) 之間怎麼互動, 也就是怎麼傳參數過去, 怎麼取回運算結果。《x86 calling conventions》有列多種做法。 換句話說, 這和設計及使用框架一樣, 為了方便上層開發者, 針對某項工作制定了一整個框架, 只要照著框架的規範使用框架提供的工具, 寫起來就會很快。不過也因此犠牲掉一些彈性和效率, 是不可免的 trade-off。巨觀來看, 設計工具將高階語言編成可執行檔, 也是設計一套框架。如同使用其它框架一樣, 框架幫開發者省去細節, 專心於開發應用程式上; 而了解框架能進一步讓使用者用得更順手, 還有從框架的設計中學到深入的技巧。 呼叫函式時, caller 大致上需要做以下的事, 順序是我大概列的, 只是一種 call convention, 沒有寫得很精確: 準備參數給 callee 備份 caller 用到的一些暫存器 備份 caller 下一個要執行的指令位置 跳到 callee 要執行的指令位置 ( callee 備份 caller 的 frame's base pointer, 改變 frame's base pointer (ebp)、 stack pointer (esp) ) ( callee 自己準備空間做為區域變數 ) ( callee 結束後, callee 要清掉自己的區域變數 ) 待 callee 結束後, 取得 callee 運算結果 待 callee 結束後, 清掉傳給 callee 的參數 對照用 gcc -S 編譯 C 程式成組語來看原組語碼, 有一些小心得: 了解呼叫函式要做這麼多事, 也難怪小動作要盡量 inline, 不要真的呼叫函式; 還有避免使用遞迴, 改用 loop + stack。 明白函式內用 static 宣告的變…

交接心得

將之前隨手的筆記稍微整理一下備忘。 主要參考文章: 《How to hand over a project systematically? - Stack Overflow》。Stack Overflow 真是居家必備的好參考資料。其它參考文章滿發散的, 沒有詳記出處, 看些設置專案的 best practice 也有用, 預防勝於治療。 消化後, 我自己覺得重要的事, 依順序如下: 每個專案要有個 readme 說明如何開始。確保對方知道如何跑 tests 還有跑範例指令得出範例結果, 確認專案沒有問題。建立專案環境包含知道專案相依性, 若有用適當的軟體, 會少很多痛苦, 像 python 的 virtualenv + pip 是不錯的選擇, 可以盡可能地找出最乾淨的相依組合。 為什麼我們需要這東西, 它的目的為何 System Context 和 Architecture Overview: 方便對整體有個概念, 兩張圖的用途不同, 參考《Architecture Overview》。不過我的交接對像本來就知道部份內容, 雖然有畫這些圖, 卻不知效果如何。 data flow diagram: 個人偏好有個實例表示資料如何在各元件之間轉換, 能自己想通整個資料走向, 對理解整個架構和除錯很有幫助。我自己在理解專案時, 常會自己畫資料流, 藉此弄清楚架構, 找出我不明白的環節。 從上面列的東西, 可看出來重心放在小巧實例, 方便快速上手, 有東西跑, 比較會有感覺。再來是架構 (概念)和設計背後的原因, 這些很難從程式碼看出來的東西。程式註解也該寫這麼做的原因, 而不是它在做什麼。後者看程式就可以懂了, 寫文件的時間也要花在刀口上。此外, 文件本身也會有維護成本, 盡量寫真的必要的部份。

產生 core dump 檔案

重新寫過記錄在《產生 core dump 的方法》

C/C++ const

個人認為 coding 的核心就是管理「物件/函式」之間的 state, 像 namespace (或 Java 的 package)、class、少用全域變數、static 與否等, 都是協助管理 state 的語法。另外, const 也是很有用的語法, 在 C/C++ 這裡用法更靈活。 參考《Const-Correctness in C++》, 筆記如下: const 和 pointer 的關係: 有分目標變數是否為常數, 和指標本身是否為常數兩種, 共有四種組合。詳見之前寫的: 《C 的型別宣告》。 const_cast: 將常數轉回非常數, 但為造成未定義的行為, 使用情境很窄, 見文末的例子。 基於 "..." 的實際運作情況, 寫成 char const *s = "..." 較好。若使用 char *s = "...", 之後改變 s[3] 會造成未定義結果。該篇說明的例子是, 改變 s[3] 後會改到別的常數字串, 因為是用同一個 string pool (見該篇例子); 而我用 gcc 在 linux 下試的結果是 segmentation fault。 const member function 很有意思 (即宣告成 void foo() const), 用它可做到類似 Haskell 的 stateless 的效果, 語意上是不會改變 member fields, 所以 const member function 只能呼叫其它 const member function, 和 Haskell 預設 function 的行為很像, 有助於維護程式。個人相當喜歡這個效果, 可惜 Java 沒有, 得自己用 interface 做到類似效果, 不怎麼直覺。 承上, const member function 的表現行為, 相當於讓 this 的型別變成 Klass const * const this, 其中 Klass 是某個類別名稱。所以在取用 this->xxx = ... 時自然會造成編譯錯誤。附帶一提, 一般 member function 的 this 的型別則是 Klass * const this, 也就是 this 都是常數指標, 不能讓 this 指到別的物件…

關於 C/C++ 負整數的 shift 以及 arithmetic right shift 的小技巧

在看 《Tonc: Whirlwind Tour of ARM Assembly》 的時候學到一個小技巧: 若 n 是有號 32bit 整數, 則 n>>31 相當於 n >= 0 ? 0 : -1。 看完 arithmetic shift 的定義後, 才發現我沒弄懂 arithmetic right shift, 原來是一直補進 signed bit (most significant bit), 當它是負數時, 等於一直補 1 進來。所以, -1 不管右移幾次, 結果仍是 -1, 不是 0。 將這訊息發到 G+plurk 後, 看到許多人提到 C、C++ 沒定義 signed shift 的行為, 所以用的時候要小心。jserv 提到 compiler 可以加參數改變位元運算的結果。 查看 gcc 的說明, 得知 gcc 有實作 arithmetic right shift, 但看不懂 left shift 的部份。Scott 提到這篇有解釋, 然後我明白了一件事: 關於 C99、C++0x draft 的定義, 我都看不太懂啊 ...。孩子的教育不能等, 要好好學英文。 btw, Louis 提到一個有趣的應用, ((n>>31)^n) - (n>>31) 等同於 abs(n)。拆解這個式子可得知, n 是正數時: n>>31 是 0 n^0 仍是 n n - 0 仍是 n n 是負數時: n>>31 是 -1 -1 以 2 補數表示為 0xFFFFFFFF n^(-1) 等同於 1 補數運算 最後再減 -1, 得到 2 補數運算的值 順便試著寫了以下的 C code, 再編成 assembly code, 想說看看會差多少:int abs1(int n) { return n >= 0 ? n : -n; } int abs2(int n) { if (n >= 0) return n; return -n; } int abs3(int n) { return ((n>>31)^n) - (n>>31); } 結果 abs1 和 abs3 差不多, 以下是 abs1 用 gcc 4.4.…

Ubuntu source list

Django 的 runserver 如何實作 autoreload

做完上篇的勘查後, 好奇之下順便看一下 runserver 怎麼實作 autoreload。 看 core/management/commands/runserver.py 會看到 autoreload.main(inner_run)。然後看 utils/autoreload.py, 稍微看原始碼, 配合 pdb觀察行為, 還滿簡單的。 inner_run 是真正處理 web request 的函式, autoreload 的行為如下: 在執行 inner_run 前, 先 fork 出 child process 讓 child process 開另一個 thread parent process 等待 fork 出來的 child process 結束 child process 的一個 thread 每秒掃一次所有載入的 module, 看看原始碼是否有更新。有的話, 結束這個 process, 傳回 exit code 3 child process 的另一個 thread 執行 inner_run parent process 發覺 child process 的 exit code 為 3 時, 重 fork 一次, 然後回到步驟 2 要注意的是, 要用 parent process 重 fork, 而不是 child process 自己再 fork 新 process。這樣才能確保由原本乾淨的狀態重讀全部 modules (parent process fork 完就沒做事了, 確保狀態沒有一絲改變)。 偵測程式更動的程式是 code_changed(), 關鍵部份是如何找出所有相關程式:filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())) 然後將 .pyc, .pyo 改為 .py, 接著記錄所有 py 檔的上次更新時間, 留待下次呼叫此函式時比對。從這段程式可看出, 包含 site-package 或 virtualenv 裡的程式也在偵測範圍內, 還挺方便的。

小心 Django 的 ADMIN_MEDIA_PREFIX

踏到這個雷兩次了, 記錄一下。 在使用 runserver 開發程式時, runserver 會特別處理 ADMIN_MEDIA_PREFIX, 先處理 prefix 為 ADMIN_MEDIA_PREFIX 的路徑, 再將其它情況交回給原本 urls.py 裡定義的 handler。 這導致當自己的靜態檔案放在 media/ 下, 又忘了改 ADMIN_MEDIA_PREFIX 時 ( settings.py 預設為 media/), 會讀不到自己 media/ 下的檔案, 而出現 "Page not found: ..." 的錯誤訊息。 從以下兩個現象, 推測中間有人先處理掉 request: browser 有送出 request 但得到 404 runserver 的 console 沒輸出訊息表示有收到 request 並回覆 404 所以推測有人從中做梗!! 解法很簡單, 就是將 ADMIN_MEDIA_PREFIX 隨便設一個其它名稱, 像是 "admin_media"。之前有人發 issue 請 Django 改掉 django-admin.py 的值, 不過下場是 wontfix。 相關的程式從 core/management/commands/runserver.py 找到 core/servers/basehttp.py, 然後看 AdminMediaHandler.call 的部份。 這段先看是否符合 ADMIN_MEDIA_PREFIX , 不是的話就由原本使用者寫的方式處理: # Ignore requests that aren't under ADMIN_MEDIA_PREFIX. Also ignore # all requests if ADMIN_MEDIA_PREFIX isn't a relative URL. if self.media_url.startswith('http://') or self.media_url.startswith('https://') \ or not environ['PATH_INFO'].start…

用 Chrome console 從鎖右鍵的網站中取得文字

遇到鎖右鍵的網站時, 複製文字變得有點麻煩, 要檢視原始碼再去掉 tag。剛從 Shakalaca 那看到《Become a Javascript Console Power-User》, 學到不錯的方式, 比較省事地取出文字: 按 F12 開 Chrome console 點 Elements 選放大鏡 選取要的段落 (html node) 在 console 裡打 copy($0.innerText) 這樣就複製好了, 只剩貼到文字編輯器裡去掉不要的部份即可。

C 的型別宣告

直到看了 Dan Saks 的 const T vs T const, 才(比較)明白 C 的型別宣告, 知道 int const *a 和 int * const a 的差別。原本雖然知道 int (*f[10])(int )、int (*f)(int) 和 int *f(int) 的差別, 但沒有很清楚細部原因, 看了這篇後才懂得更徹底。 C 的宣告分幾個部份, 以 static unsigned long int *x[N] 來說, *x[N] 是 declarator, 剩下的是 declaration specifier。declarator 的部份用運算子優先順序來解讀, 語意清楚許多。即 () 最優先, 其次為 [], 再來為 *。所以: int (*f)(float) 表示 f 是函式指標, 指向一個函式其傳回值為 int, 參數為 float int *f(float) 表示 f 是函式, 傳回 int*, 參數為 float int (*f[10])(float) 表示 f 是一個陣列, 每個元素是一函式指標 令人混淆的部份在於, declaration specifier 的順序其實沒有影響。所以, static unsigned int n 和 int unsigned static n 是一樣的。再加上 const 和 volatile 不同於其它 declaration specifier, 它們可以同時寫在 declarator, 於是產生不易理解的結果。 我還在參透 volatile 的效果, 這裡先以 const 為例: int const n 和 const int n 都表示 n 是指向唯讀的 int int const *n 表示 n 是指標, 指標指向的型別為唯讀的 int int * const n 表示 n 是唯讀的指標, 指向 int int const * const n 表示 n 是唯讀的指標, 且 n 指向唯讀的 int 簡單的判別方式是以 * 為分界點, 由右往左看, 包含 * 的右邊是 declarator, 表示 declarator 的型別, 所以這裡有 const 表示是唯讀的指標; * 的左邊是 declaration specifier, 這裡有 const 表示指到的型別為唯讀。附帶一提,…

用 LD_PRELOAD 替換動態連結的函式庫

換掉動態連結的函式庫有不少用途, 比方說像 Scott 提的「用來驗證是否執行到特定函式」, 或如 jserv 提的「在沒有原始碼的情況下修補執行檔的行為」。最近又看到這個東西, 記一下筆記以免忘了。 來看程式碼和執行效果。 mylib.c#include <stddef.h> #include <stdio.h> int putchar(int c) { printf("call putchar()\n"); return c; } void *memset(void *s, int c, size_t n) { printf("call memset()\n"); return s; } main.c#include <string.h> #include <stdio.h> int main(void) { char s[10]; memset(s, 0, 0); putchar('X'); putchar('\n'); return 0; } 執行結果$ gcc -Wall -fpic -shared -o libmylib.so mylib.c $ gcc -o main main.c # 沒用 LD_PRELOAD 的結果 $ ./main X # 用 LD_PRELOAD 後的結果, 記得加 "./", 不然會找不到 libmylib.so $ LD_PRELOAD=./libmylib.so ./main call putchar() call putchar() $ strings main | grep memset 可以看出 putchar 有被換成自己編的版本, 但 memset 沒有。用 strings 查看 binary 檔 main 會發覺並沒有呼叫 memset 的跡象。猜測是 compiler 發覺 memset 實際上沒任何作用, 所以沒呼叫。 修改 main.c, 將 memset(s, 0, 0) 換成 memset(s, 0, 1) 再來看看:$ gcc -o main main.c $ strings…

安裝 PythonWebkit

偶然看到 PythonWebkit 這個專案, 作者花了兩週左右的時間寫好 WebKit 的 python 介面, 於是操作 DOM 不再是 javascript 的專利, 透過 PythonWebkit 就能用 python 操作 DOM。作者自稱除一些小功能外, 這個專案已到可用的階段:The current status is that the SVG 2D Canvas element is not available, and CSS Styles have to be modified through the style.setProperty function (rather than being accessible as python properties). Other than these gotchas, the Python Webkit bindings are in a useable state, providing full W3C compliant access to the full set of DOM functionality (except SVG Canvas) as provided by Webkit, and ordinarily only accessible via javascript. 不過相關文件超少, 花了些時間才裝成功, 也增加了一些編原始碼的功力。 背景介紹WebKit歷史很有意思, 有興趣的人自行參照連結。在 Linux 上目前有兩種 port, 一個是用 GTK+ 實作的WebKitGTK+, 另一個是 QT 的實作版本。 PyWebKitGtk 是另外一個基於 WebKitGTK+ 的專案, 透過 WebKit 提供的 gobject binding 提供 python 介面, 但沒有提供直接操作 DOM 的功能。雖然有些補救方案, PythonWebkit 的作者覺得直接改出 python 介面比較省事, 效率也較好, 該頁有寫詳細的設計考量。所以, PythonWebkit 是以 WebKitGTK+ 為底, 修改部份內容直接提供 python 的 DOM API。 剛開始作者用 PyWebKitGtk 的 python module "webkit",…

HTML DOM 筆記

有了 pythonwebkit 後, 可以用 python 直接存取 DOM。於是得自己學一下 DOM 的操作方式, 不能再依賴 jQuery 啦。 花個半小時邊看邊操作 《HTML DOM Tutorial》, 很快就有個基本概念了。記一下自己不熟的部份: DOM 的 node 類型有: document, element, text, attribute, comment element node 沒有包含文字, 要從它的 text node 拿: document.getElementsByTagName('p')[0].nodeValue # null document.getElementsByTagName('p')[0].firstChild.nodeValue # the expected texts 操作 iframe 的內容 取得 iframe 的 DOM: d = document.getElementsByTagName('iframe')[0].contentDocument 接著就能用一樣的 DOM API 操作 d 我只有在 Chrome 下試, 畢竟 pythonwebkit 的功能應該和 Chrome 差不多才對。要跨瀏覽器操作 DOM 的話, 得多試幾種變數, 取出 iframe 的 DOM

vim 停住一會兒才啟動的解法

有時用 putty 連到 server, 打 vi 後會停一兩秒才進去。有時在 screen 下甚至會停住不動。參考這篇的說法, 用 vi -X 不要連線到 X Server 就 ok 了。 會有這樣的問題, 是因為我用到 gnome 版的 vim (vim.gnome):$ which vi /usr/bin/vi $ ll /usr/bin/vi lrwxrwxrwx 1 root root 20 2010-05-12 12:17 /usr/bin/vi -> /etc/alternatives/vi* $ ll /etc/alternatives/vi lrwxrwxrwx 1 root root 18 2010-08-06 13:58 /etc/alternatives/vi -> /usr/bin/vim.gnome* $ ll /usr/bin/vim.gnome -rwxr-xr-x 1 root root 2144568 2009-01-08 11:03 /usr/bin/vim.gnome* 若用 vim.tiny 或 vim.basic, 不加 -X 也不會停一會兒。 相關除錯指令: 用 vi -V 會看到「讀取 viminfo 檔案 "/home/fcamel/.viminfo" 訊息開啟 X Window 失敗」 (Reading viminfo file "/home/fcamel/.viminfo" info oldfilesOpening the X display failed) 在 X Window 下無此問題

安裝完 Ubuntu 後的初步設定

2017-04-04 更新記一下個人裝 Ubuntu 後, 會做的基本設定。 sudo apt-get install aptitude sudo aptitude update sudo aptitude install build-essential apt-file checkinstall manpages-dev htop rsync screen cdargs openssh-server mosh vim git subversion exuberant-ctags cscope python-dev python-setuptools id-utils python-pip sudo apt-file update git clone git://github.com/fcamel/configs.git cd configs && ./install.sh # 裝我個人用的設定檔, 如 vim, bash, screen 若需要裝 java, 另外需做: sudo aptitude install python-software-propertiessudo add-apt-repository "deb http://archive.canonical.com/ natty partner"sudo aptitude updatesudo aptitude install sun-java6-jdk若是用 Parallel 的話,裝完 Parallel 附的 Ubuntu 後要作的事:修改網路:Actions -> Configure -> Hardware -> Network -> 選 Ethernet,這樣會自己有獨立的 LAN IP新增硬碟空間:在關閉 OS 的情況下,Actions -> COnfigure -> Hardware -> Hard Disk 1 -> Edit,然後調整大小。作完後重開 VM。再用 gparted 動態調大 partition 大小。需要先刪掉 swap, 再刪 extended partition,才有空間調 root partition。 日後想到什麼再補。

Ubuntu 用 UUID 掛載硬碟的原因

之前看 Ubuntu 的 fstab 改用 UUID 覺得很納悶, 為啥要在 /dev/disk/by-uuid/ 下建 soft link 再指到 /dev/sdXN (X 是 abcd, N 是 1-8), 然後在 fstab 裡用 UUID 而不用 /dev/sdXN。 今天用 VirtualBox 移除硬碟後才發覺它的好處: 加裝或移除硬碟後, 雖然 /dev/sdXN 的 X 會變 (如移掉第二顆硬碟, /dev/sdc3 會變 /dev/sdb3), UUID 卻不會變。所以若 /etc/fstab 用 UUID 來掛載 partition, 可以直接開機, 而省掉進單人模式改 /etc/fstab 的步驟。

tab vs. space

討論 coding style 時, 這個常會戰到爆。記一下在多人一起開發一陣子軟體後的心得。 我後來覺得 space 比較方便, 因為可確保任何地方看到的畫面一致, 且不需另外設定。支援 tab 的人認為只需要一次設定就可以了, 這樣可保留彈性。困難的地方在於有太多地方可能會看到程式碼: IDE、editor、VCS log、issue tracking、e-mail、其它可能的 web-based 工具 (如 code review)。而有些地方無法提供客制化設定 tab 寬度 (如 e-mail)。 以我自己用 Java 來說, 平時可能用 Eclipse, 特定需求時用 vim, 偶而要用 hgtk 看別人的 commit (一些程式碼的 diff), 或用 hg blame 看修改記錄 (輸出到 console 或用 hgtk 看), 偶而寄 code diff 給別人討論。在這些過程中, 會發覺 tab 實在是不太方便。 至於 space 的缺點: 一但定了就很難改, 實務上不是問題, 各個語言都有標準規範 (大概都是四個空白), 照著公定規範做就是了。若不幸地原本已有一大堆其它長度, 讀個一陣也會習慣, 應該會比在不同地方, 縮排亂掉來得好。

MySQL vs. PostgreSQL

去年的隨手心得, 記在這備忘。主要是參考《MySQL vs PostgreSQL - WikiVS》 2010-09 的版本, 後來情況應該也有些變化。另外, 很多東西還是要自己測過才準, 後來和一些很熟 DB 的前輩聊, 發覺實際情況還是和看到的有些出入。需求不同, 又有太多細節可以調整了。 後來覺得選用 MySQL 的好處是: 資源最豐富, 有書有文件, 也有一堆顧問公司 (如 Percona) 有許多大公司實際執行到海量的實例, 不用擔心會流量會卡住 (雖說大部份時候應該要先擔心服務不夠好, 流量太小) 有些 syntax sugar 或特制功能很好用, 像是 insert ... on duplicate update , 或 insert ignore 有趣的是, 該文認為 MySQL 比 PostgreSQL 流行的原因之一, 是因為 PostgreSQL 沒法限制各個 database 的大小, 對於商用服務來說, 這點很重要 (商用服務會在意限制資源、管理權限等, open source 專案先以個人需求為主, 一開始大概都不會重視這些吧)備忘 fcamel 2010-09-01 19:54:43 MySQL vs. PostgreSQL, 有這種對照文真不錯, 書上有看過的相關觀念可以重新消化一次, 之前沒看過的....在這也有看沒感覺 MySQL vs PostgreSQL - WikiVS MySQL vs PostgreSQLFrom WikiVS, the open comparison websiteJump to: navigation, search MySQL Postgr 發表回應 1 fcamel 2010-09-01 19:55:43 PostgreSQL 只有一個 engine, 一般來說在大量同時寫入下會比 MySQL 搭各種 engine 快 2 fcamel 2010-09-01 19:56:36 PostgreSQL 比較穩, RDBMS 功能較齊全, 這點在 stackoverflow 上可找出一卡車支持者。也可找出一卡車 MySQL 使用者說他們遇到 DB 炸了多少次 3 fcamel 2010-09-01 19:57:24 MySQL + MyISAM 有無敵快的 count, 因為它沒 MVCC, …

讓 mysql 使用 utf-8 的相關設定

這對於新手來說應該很有幫助吧 在 [mysqld] 下的相關設定:character-set-server=utf8 collation-server=utf8_general_ci init-connect='SET NAMES utf8' MySQL 的設定檔位置不太好找, 細節參考《fcamel 技術隨手記: 更改 mysql conf 的注意事項》

warm up server 的一般作法

一開始用 MySQL 的 MyISAM 時, 發覺有語法可以事先載入整個 index, 就以為大部份 server 應該有提供這功能。不過後來發覺 InnoDB 沒有, 所以要自己下 SELECT 掃一次 table, 讓 MySQL 自動觸發 cache 機制。後來用 Solr, 查了一陣子, 似乎也沒有這種機制。大概設計 server 的人覺得另外設計語法會增加維護成本, 不如讓大家自己查詢全部要用的資料, 讓 server 自己管好 cache 比較實際吧。

mysql flush key cache

MyISAM 的 index 會暫存到 key cache 裡, 若想預先載入整個 table 的 indexes 到 key cache 裡, 可以用 LOAD INDEX INTO CACHE。但若要 flush key cache 的話, 並沒有像 flush query cache 那樣有 RESET QUERY CACHE 的語法。替代作法是, 改變 key cache 的大小或 block size, MySQL 就會重設 key cache。

使用 ReviewBoard API 刪掉多個 request

ReviewBoard 沒有提供批次刪掉的介面, 測試期間產生一堆 request, 懶得用手一個個刪, 想說用 API 來刪。 使用 ReviewBoardweb api, 需要在連線中的 http header 加上 cookie 資訊用作身份認證。用 python 的 httplib2 做這事也不會太麻煩, 不過若能用 command line 解決, 會更簡單一些。後來我用 curl 做這事, 順便熟悉一下它的功能, 常看文件用 curl 示範 API 用法。 參考《Use CURL to Login to Websites with a Script》的教學, 學會 curl 登入網頁的用法。依我的需求消化後的步驟如下: 用 FireBug 之類的工具找出 login form 需要的欄位。 連到 web server 產生 coolkie: curl --cookie-jar cjar http://myhost/account/login/ 送出 username/password 登入, 並更新 cookie (存到 cjar 裡): curl --cookie cjar --cookie-jar cjar http://myhost/account/login/ --data 'username=fcamel' --data 'password=xxxx' 使用 FireBug 觀察 ReviewBoard 刪除 request 時連的網址 (或是看 web api 文件) 執行指令砍掉編號 1 ~ 10 的 request: for i in {1..10}; do curl --cookie cjar --cookie-jar cjar http://myhost/api/json/reviewrequests/$i/close/discarded/; done備註: 砍 request 要用 http://myhost/api/json/reviewrequests/$i/delete/, 但要有 admin 權限

在同一個 apache 裡跑兩個 port

使用 apache2 + mod_wsgi 的作法架 ReviewBoard 的話, relative path 有 bug, 無法將 ReviewBoard 架在 http://.../reviews/ 這樣的位置。網路上搜搜看到也有人提到同樣的問題。自己試著硬解了一陣子, 沒有很乾淨又一致正確的結果。後來想說, 乾脆在同一台機器的 apache 裡跑聽兩個 port 好了, 這樣就能在用專屬非 80 的 port 來架 ReviewBoard。 查了一下, 架法還滿簡單的: 見 VirtualHost Examples, 另外 CommonMisconfigurations - Httpd Wiki 也滿有幫助的。使用多個 port 時, 記得要加 listen。Ubuntu 的話是加在 /etc/apache2/ports.conf 裡, 它會被 /etc/apache2/apache2.conf include 進去。

Synchronization and the Java Memory Model 筆記

昨天看 Effective Java 提到 synchronization 同時提供 exclusive execution 和 communication, 但多數人會忽略後面這點。原本不懂這是什麼意思, 看了《Synchronization and the Java Memory Model》才知道是怎麼回事, Java Memory Model 真的和直觀的想法很不一樣。記錄一下筆記。 final class SetCheck { private int a = 0; private long b = 0; void set() { a = 1; b = -1; } boolean check() { return ((b == 0) || (b == -1 && a == 1)); } } 這段程式有可能 return false, 因為 set() 裡的 a、b 設值可能會順序相反, set 和 check 可能同時被交錯執行。Java 只有保證單一 thread 自己執行的時候, 看起來像按順序由上而下、由左而右執行 (文中用 as-if-serial 表示, 這詞還滿妙的)。 在 multi-thread 時, 情況不同, 各個 thread 有自己的 memory, Java 沒有保證什麼時候 thread 之間才會看到更新後的情況, 這點滿可怕的。但可以用 synchronization lock 或宣告 volatile 強迫 thread 之間同步資料。我想這就是書上說 communication 的意思。 Java 有保證除 long 和 double 外的欄位, 更新值的時候是 atomic 的 (也就是包含 reference), 而 volatile 宣告的欄位也都是 atomic, 包含 volatile long 和 double。這裡要注意幾點: i++ 包含 i + 1 和設值兩個操作, 沒有 atomic。所以設 volatile 也會有危險, 有需要的話還是要用 synchronized, 或照 Effective Java 的建議, 看能不能用 java.util.concurrent.atomic 的類別滿足需求。 lo…

Effective Java 讀書筆記: Item 66 - 用 synchronized 確保 concurrency

這節提供一個不直覺的錯誤package trial; import java.util.concurrent.TimeUnit; public class Trial { private static boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(new Runnable() { public void run() { int i = 0; while (!stopRequested) { i++; System.out.println(i); } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested = true; } } 作者說在沒用 synchronization 機制的情況下, Java 沒有保證 thread 什麼時候看到彼此之間的修改。上面的例子, 在作者的機器上不會停。不過我自己試的結果是一秒後結束。作者說原因是 JVM 有可能最佳化 while 那行, 變成只檢查一次 stopRequested, 結果就不會停了。這個 VM 最佳化技巧叫做 hoisting (文中的例子則是改用 stack 上的變數取代 heap 上的)。 解法是宣告 stopRequested 時加上 volatile。不過作者後來接著強調 volatile 很難寫對, 叔叔有練過, 小朋友別亂用 volatile, 乖乖用 java.util.concurrent.atomic 下的類別, 像是 AtomicLong。這個 package 下的東西用了 machine-level 的方法減少 lock overhead, 比用 synchro…

shell script 處理含空白字元的檔名

以前我習慣寫 for f in $(ls mydir) do ... done 但遇到檔名有含空白時就炸了, 像這樣 $ ls mydir/ | cat a b c $ for f in $(ls mydir); do echo $f; done a b c 解法是設 IFS 這個變數 $ IFS=$'\n' $ for f in $(ls mydir); do echo $f; done a b c unset IFS 也可用這個方式在 shell script 裡做到等同於「字串陣列」files=$(cat <<EOF 1st line 2nd line 3rd line EOF ) IFS=$'\n' for f in $files do echo $f done unset IFS 最後附上 man bash 裡的說明:
The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read builtin command. The default value is ``<space><tab><new‐ line>''.

virtualbox 使用 USB 裝置

2012-12-16 更新 現在 (4.x 版) 似乎無需做任何設定, 只要有裝 Oracle VM VirtualBox Extension Pack, 在 VirtualBox 視窗右下角按 USB 的圖示, 再點目標裝置, 即可加入或移除該裝置 同一時間只有 host 或 guest 可擁有該裝置, 所以從 guest OS 移除, 相當於接回 host OS 目前 VirtualBox 只支援 USB 2.0 的插槽, 若偵測不到時, 注意一下是否為這個問題 有時拔拔插插, VirtualBox 會進入奇怪的狀態, 接上去 guest OS 無法連接且跳出 device is busy 的錯誤訊息。試看看拔除該裝置, 重開 guest OS (續上則) 若重開 guest OS 無效, 並且 host OS 已移除該裝置, VirtualBox 的 USB 清單卻仍顯示 "captured", 試看看拔除該裝置, 重開 host OS原文網路上搜一下, 比較多是 Ubuntu 當 host 的解法, 我的情況是 Win7 當 host, Ubuntu 當 guest。 這兩篇說明很詳細《Learn How to Set Up USB and Networking Options in VirtualBox》《幻影千瞳的部落格: VirtualBox 使用筆記(二):使用 USB 裝置》 現在的版本圖形介面很好用了, 不用像第二篇說的那樣用指令操作。這裡記下我的操作步驟: 關掉 guest OS 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB -> Enable USB 2.0 會出現訊息框, 說明要安裝 Oracle VM VirtualBox Extension Pack。下載後安裝它 host OS 插入 USB 隨身碟 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB, 點右邊有綠色 "+" 的 USB 頭的圖示, 選擇該 USB 隨身碟, 加入它的 filter 從 host OS 移除 USB 隨身碟 開啟 guest OS 插入 USB 隨身碟, 於是 guest OS 會自動偵測…

用 Sphinx 寫文件

以下是最近試用 Sphinx 的心得, 有錯還請指正。 我原本很納悶, 專案文件是用 Wiki 寫好, 還是用 Sphinx 寫來得好。為啥要特別做個工具來產生文件呢? 抱著實驗的性質試了 Sphinx, 試下去才明白, 難怪不少專案用 Sphinx 來寫。 用 Sphinx 寫有幾個好處: 文件原始碼可和程式碼放在同一個 repository, 也可藉此順便做版本管理。不過我覺得以版本管理來說, 還是沒 Wiki 方便。 可透過語法載入程式碼內的註解, 這點 Wiki 就沒輒了, 我覺得這是用 Sphinx 的最大優勢。 一堆好用的 plugin, 像是畫圖、寫數學式子、畫 graph等。將 graphviz 原始碼直接寫在文件裡, 還挺方便的, 不過這點 Wiki 也辦得到。 可嵌入 ipython 語法, 顯示 ipython 的結果, 包含自動填入執行結果等功能。這個相當好用, 適合寫執行的範例 (寫實例演練的必備工具), Wiki 就沒有啦。 產生的結果是純 html, 並有提供文章搜尋功能 (實作方式很妙, 建好 index 存成 js 程式碼, 透過 js 執行搜尋)。架網站提供文件時, 不需另裝任合套件 (PHP / CGI / Web framework / etc)。 功能彈性, 若是 Python guy 的話, 缺什麼語法可自己寫 extension 補一下。Sphinx 也有提供巨集功能自定語法。相較之下, Wiki 最大的好處是編完存檔就看到結果, 省掉 make html 的步驟, 方便大家隨時一起共筆, 提供 lock 避免多人同時編輯相衝, 方便查閱版次之間的差異。Sphinx 得透過 VCS 做, 也不方便提供 html 的版次差異。所以若是多人同時共筆, 偏重於功能說明, Wiki 還是較方便。 這裡列幾個熱門專案選擇的做法: 用 Sphinx: pythonpipvirtualenvDjangoSouth Wiki: 除 MercurialLuceneHadoopMoinMoinWikis (python 寫的 wiki), 另有不少專案的文件量不多, 會直接用 Google Codegithub 提供的 Wiki 寫文件。看完後發覺 ........, 一時好像也看不出個什麼頭緒, 之後再慢慢觀察吧…

Sphinx 的 autodoc 載入問題

為了使用 autodoc, 我在 conf.py 裡這樣寫:sys.path.append('/usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext') extensions = ['graphviz', 'autodoc'] 結果跑出:autodoc documenter <class 'autodoc.ModuleDocumenter'> must be a subclass of Documenter 正確的寫法是:extensions = ['sphinx.ext.graphviz', 'sphinx.ext.autodoc'] 開 pdb 追下去, 發現是 sphinx.application 和 sphinx.ext.autodoc 之間 circular import 造成的問題。 在 sphinx.application 裡:def add_autodocumenter(self, cls): from sphinx.ext import autodoc autodoc.add_documenter(cls) self.add_directive('auto' + cls.objtype, autodoc.AutoDirective) 會載入 sphinx.ext.autodoc, 但我原本的寫法, 讓 sphinx 透過 __import__ 載入 autodoc, 結果產生兩個不同的物件, 但都是 autodoc, 結果剩下的一些程式操作, 就發生了悲劇。 在 autodoc 裡的這段程式就丟出 exception:if not issubclass(cls, Documenter): raise ExtensionError('autodoc documenter %r must be a subclass ' cls (即 ModuleDocumenter) 真的不是 Documenter 的 subclass, 因為這個 ModuleDocumenter 是其中一個 autodo…

《CPython 源碼剖析》讀書心得 - ch2 - int

PyIntObject展開巨集後的定義typedef struct { Py_ssize_t ob_refcnt; struct _typeobject *ob_type; long ob_ival; } PyIntObject; 基本操作這篇記了加法的實作: 加完後檢查溢位,若溢位的話, 改用大數 PyLongObject。 PyInt_AS_LONG vs. PyInt_ASLong在運算過程中, 視情況會用不同方式取出 ob_ival: 前者是巨集, 沒檢查 type, 速度快 後者是函式, 有檢查 type, 速度慢其它 type 也會看到類似的操作。 immutable不可變的物件有許多好處, 包括可以任意被所有程式共享, 不用擔心造成依賴關係, 會不小心互炸。代價是各項操作後, 時常需要產生新的物件, 可能會拖慢速度。若會用到大量物件的話, 通常會搭配 object pool 減少生成物件的次數, 藉此簡省計算時間, 或是進一步在生成前先檢查是否已存在同樣物件, 有的話直接共享同一物件, 不生成重覆的物件。 在後面的例子會看到, CPython 有用 object pool 處理 int, 但也付出無限擴大記憶體的代價; 另外, CPython 只針對小部份範圍的數字共用物件, 以取得效率平衡。 memory managementintobject.c 開頭寫下為了減少 malloc 次數, 而採取的作法, 以及它造成的問題: /* Integers are quite normal objects, to make object handling uniform.
(Using odd pointers to represent integers would save much space
but require extra checks for this special case throughout the code.)
Since a typical Python program spends much of its time allocating
and deallocating integers, these operations should be very fast.
Therefore we use a dedica…

Sphinx customized theme

官網說明有完整說明, 這裡記個人速記版。 找出 default theme 位置並複制一份出來, 供之後修改:# 找出 default theme 的位置 $ locate default/static /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default/static /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default/static/default.css_t /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default/static/sidebar.js # 切到自己建的 Sphinx 目錄 $ cd docs # 複制 default theme 出來 $ cp -r /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default MY_THEME 編輯 conf.py 加入:html_theme = 'MY_THEME' ... html_theme_path = ['.'] 這樣之後改 MY_THEME 下的東西即可。 若只需要改 css 的話, 不需複製整個 theme 出來, 可以改 conf.py 裡的 html_static_path, 改用自己的 default.css 覆蓋 default theme 用的 css。照上面的方式找到 default theme 用的 css, 複制出來放到 _static/default.css, 之後再修改這個檔案即可。

小撇步: 直接看指令的原始碼

在 ~/.bashrc 裡這麼寫:function see() { vi `which $1` } complete -c command see 就能打:$ see apt-file 看 apt-file 的內容, 並支援 completion, 可按 TAB 補完指令名稱。

在 Ubuntu 上使用 CUnit + curses 執行 unit test

安裝 sudo aptitude install libncurses5-dev wget -O CUnit-2.1-2-src.tar.bz2 http://downloads.sourceforge.net/project/cunit/CUnit/2.1-2/CUnit-2.1-2-src.tar.bz2?r=&ts=1312211219&use_mirror=nchc tar jxvf CUnit-2.1-2-src.tar.bz2 cd CUnit-2.1-2/ ./configure --enable-curses make sudo make install 這樣會裝到 /usr/local/include/CUnit 和 /usr/local/lib/libcunit.*。
編譯 貼上這裡的範例程式, 存檔為 example.c 註解掉最下方的 CU_basic_run_tests();, 改為 CU_curses_run_tests;, 並在上面加入#include "CUnit/CUCurses.h" gcc example.c -lcunit -lncurses -o example執行 ./example失敗的話, 檢查一下 /usr/local/lib 是否有在 /etc/ld.so.conf 裡, 加進去後執行 /sbin/ldconfig –v 更新設定。細節參考: 《error while loading shared libraries的解決方法》
若不想用 curses 的話, 就用 CU_basic_run_tests。
若確定用不到 curses, 安裝時可以不裝 libncurses5-dev、configure 時可以少掉 --enable-curses。這樣的話, 編譯時也不用加 -lncurses。
不過用 curses 跑還挺炫的說, 會有綠色的 progress bar!! 之後再來看看是否實用。

找出占用 port 的程式

netstat -nap | grep 8000 會找出使用 port 8000 的程式

semantic versioning

WanCW前篇留言 提到 《Semantic Versioning》, 是 github 的創辦人 Tom Preston-Werner 寫的。當初好像就是看到這篇吧。 文中先說明為了減少 dependency hell 的影響, 若大家定版號時共同遵守同樣的規範, 就能比較放心地說明自己的 package 需要那些版本的 packages: If the dependency specifications are too tight, you are in danger of version lock (the inability to upgrade a package without having to release new versions of every dependent package). If dependencies are specified too loosely, you will inevitably be bitten by version promiscuity (assuming compatibility with more future versions than is reasonable). Dependency hell is where you are when version lock and/or version promiscuity prevent you from easily and safely moving your project forward. 接著提出 semantic version 的定義: Consider a version format of X.Y.Z (Major.Minor.Patch). Bug fixes not affecting the API increment the patch version, backwards compatible API additions/changes increment the minor version, and backwards incompatible API changes increment the major version. 然後用嚴僅的方式陳列 semantic version 的定義, 言簡意賅。這裡用我的方…

Python 2 to 3 的問題

看到有朋友在問 python 2 和 python 3 有何差距, 來寫一下自己的理解。先聲明, 我沒有實際轉過。
之前曾在別的地方讀到版本編號的含意, 忘了出處, 找了一下發現 RubyGems 的說明和我先前看到的內容一樣, 對版本 a.b.c 來說: a 往上 +1, 表示有大幅改版, 會有不向下相容的部份, 官方 release note (或 changelog) 裡會描述那些地方沒有向下相容 (backwards incompatible changes), 如 Fabric 的 Changelog 有提到那些函式行為和之前不同。 b 往上 +1, 表示有曾加新 feature 或 bug fix, 但介面沒有改變, 可以安心昇級 c 只是修改實作方式, 對使用者來說, 行為完全沒變。當然還有許多其它的表示方式, 就.....先忽略它們好了。說明這點是想強調 2 到 3 的難處, 在於有些不相容的改變。
《Case study: porting chardet to Python 3 - Dive Into Python 3》提供一個轉換的案例, 其中 Fixing What 2to3 Can’t 說明要手動修改的部份。
除了修改自己程式的問題外, 但是若別人的程式沒有升到 3, 改完也沒用。比方說大家都愛用的 NumPy, 官方說目前只支援 2.4 ~ 2.6, 像 SciPymatplotlib (強大的繪圖函式庫) 會用到 NumPy, 在 NumPy 升上去以前, 它們也無法升級。所以在重量級 packages 支援 python 3 以前, 一般使用者仍不方便升到 python 3。2011-07-30 Updated看到 Python 3 Wall of Shame 有統計各 package 被下載的次數以及是否支援 python 3, 才發覺原來 NumPy 已支援 Python 3 了
2011-07-31 Updated經 WanCWEric Chen 提醒, 更新如下:
* 版號的定義是寫在 Semantic versioning
* matplotlib 已有官方的 Python3 branch
2011-08-02 Updated經 Eric Chen 提醒, SciPy 也有支援 3.x