發表文章

目前顯示的是 八月, 2010的文章

保持小而頻繁的 commit

一年半前我有個疑問, 究竟要寫成多個小 commit, 還是一個個完整子工作的大 commit。實際運作一陣子後, 我發覺答案很明顯, 小的 commit 才有用處。我現在甚至不明白為啥當時會為這問題困惑...

小的 commit 有以下的好處 (指令用 mercurial 表示):
發覺某個功能掛掉時, 可以用二元搜尋法快速找到改爛的那版, 接著因為 commit 很小, 很快就能找出是那段 (甚至是那行) 程式造成問題。我已用這招在五分鐘內找出兩個數十、數百版前的錯誤。看到原因後發覺, 若不用版本管理系統, 我大概花很久都不見得能找到問題。當同伴有時間時, 他們可以容易地 code review, 每個修改都很簡單易懂。當同伴沒時間時, 他們可以容易地先用 log 濾出需要仔細看的 commit, 並只看一小部份和他相關的程式 (hgtk log 超方便的!)。寫程式難免會被中斷或忽然亂了方寸, 每做完一件小事就 commit, 確保自己一直都能掌握狀況。真的亂掉不知如何除錯時, 可以先將目前更新存起來 (hg shelve 或 hg revert), 再一個個加回去, 釐清目前狀況。這招數度解救我於混亂之中。隨時能放心地嘗試, 一看 diff 就知道目前做了那些事。commit 前看 diff 也能很快找出漏砍的除錯碼 (hgtk commit 是大家的好朋友!)。另外, 用 DVCS (如 git、mercurial) 再附帶一些好處: 不用擔心自己頻繁的小 commit 造成 build fail, 讓大家共用的 repository 毀了。做個一陣子確定告一個小段落且不會炸到別人, 再 push 這段時間做的一系列小 commits。當然, 每個 commit 本來就該是完整的工作, 且可以正常編譯和通過所有測試。但考量到效率, 小 commit 可以先用較粗略的測試 (如只跑 smoke test suite, 或目前修改部份的 unit test)。push 前再跑完整測試。從 DAG 可以輕易看出大家開發的支線。但像 SVN 那樣一直線的記錄, 就無法看出各個人開發的脈絡。可能自己的 commit 變 11、14、16 成一個完整的工作, 中間卻搜了別人的 12、13、14。任何人都可以輕易地在自己的 repository 上做實驗以熟悉版本管理系統…

mysql 快速批次更新資料的方法

一直忘了記, 來簡記一下。

若要更新表格 target 裡部份資料欄位 name 的值的話, 假設 id 是 target 的 primary key, 做法如下:
CREATE TEMPORARY TABLE temp ... ENGINE = MEMORY;INERT INTO temp ... VALUES ..., ...;UPDATE target, temp SET target.name = temp.name WHERE target.id = temp.id;先開一個暫存用的 table, 記得 storage engine 選 MEMORY, 存在記憶體待會兒塞入資料較快。欄位只要有和 target 一模一樣的 value 和 id 即可。前者存更新的值, 後者是待會做 join update 時用的。接著將要更新的值塞入 temp, 記得用批次塞入的方法。最後就是將兩個 table join 起來再更新對應的資料。 這個作法的好處是, 當需要更新十萬筆資料時, 只要三個 SQL 就搞定。反之, 若下十萬次 SQL 更新, 會因需要連線多次而慢很多。用一個 SQL 更新全部資料, 也可能減少存取硬碟的次數。 用 TEMPORARY 建立暫存表格的好處是, 這個表格只有這個 connection 看得到, 也不用擔心衝到名稱 (若原本有 temp, temp 會被暫時隱藏起來)。待 connection 結束後會自動丟掉暫存表格。

nosetests 的用法

將之前寫過的東西複製過來備忘。


安裝方式: easy_install nose。 寫 unittest 時,管理 test suit 是件很瑣碎又易犯錯的事,相信很多人會想說,能不能跑個程式,自行搜集目錄下全部的測試碼並自動執行。沒錯,大家的心聲 nose 聽到了!這裡直接用例子說明 nose 的使用方式,詳細說明請見《An Extended Introduction to the nose Unit Testing Framework 》: 執行目前目錄下所有測試:1 nosetests 只執行 package PKG 下的 module MOD 內的測試:1

Python 強迫釋放記憶體的解法

悲劇發生的情境:
你寫了個 python 程式, 它先做些事吃掉個 10G 記憶體, 但隨後用不到這 10G。然後它 fork 成 4 個 processes 處理一些事。結果用 htop 一看, 發現有 4 + 1 個 processes 各吃掉 10G 記憶體。幸好 Linux  有用 copy-on-write, 所以系統顯示總共只用掉 10G。但在 5 個 processes 忙碌做事的途中, 別人的程式也在搶記憶體, 造成 OS 判斷錯誤把這幾個 processes 之一搬到 swap (或是誤把別人搬到 swap), 結果整個系統噴了, 大家都慢到做不完事。解法見《How can I explicitly free memory in Python?》, 結論是無法強迫 python 釋放記憶體, 別再相信 gc.collect() 有效這類不實謠言, 至少我試了沒有效。只好用最根本的解法, fork 一個 subprocess 讓它去做那吃掉 10G 的事, 做完掛掉後, OS 自然會回收那 10G, 就和大自然生生不息的循環一樣。由於一開始執行的 process 本身沒吃掉任何記憶體, 之後 fork 4 個 processes 自然沒有占記憶體的問題。

聽起來頗麻煩的, 好消息是, 自 python 2.6 版起有了 multiprocessing, 2.4 和 2.5 版也有 backport, 只要改用 multiprocessing 執行原來吃記憶體的函式即可。若需要讀函式的回傳值, 參考這裡的說明, 即可搞定。

Ubuntu 複製大檔案讓系統變慢的解法和原因

複製大檔案後, 系統會變得超慢, 明明 htop 顯示沒有執行 application, 卻會顯示全部 CPU (我用的機器是四核心) 都被 kernel 占住 (htop 用紅色表示)。而且 ram 明明沒滿, 卻用掉大量的 swap。查了一下後發現這已是很舊的「bug」, 仍未被解掉, 參見 Ubuntu bug 226784 365775

《Linux: how to explicitly unswap everything possible?》 提到關掉 swap 可以讓 kernel 釋放 swap, 好讓 application 回頭用 ram。所以先打 "swapoff -a", 用 htop 會看到 swap 上限先縮為目前的使用量, 而非系統的設定值。接著以極緩慢的速度縮小, 最後到零。這時就能再打 "swapon -a" 重開 swap。這時仍會看到 kernel 占掉全部 CPU, 不過等個一陣子 (大概十分鐘吧) , 系統就恢復正常了。

為了避免再度發生這問題, 查了一下相關資料, 才發現這不太算是 bug。原因出在 linux kernel 決定是否要將 ram 裡的資料搬到 swap 的策略。詳細的說明見《Patrick Lauer: The mistery of swappiness》《swapping behavior》 (後面那篇挺好笑的, 推薦一看!), 大意是當 application 和 kernel cache 用的記憶體量過高時, 即使 ram 仍有空間, kernel 會參考 swappiness 的值, 決定要捨棄 cache 還是將 application 的記憶體搬到 swap。swappiness 的值介於 0 ~ 100 之間, 值愈高, kernel 愈會將 application 用的記憶體搬到 swap。悲慘的是, 預設值一直都是 60, 但現在的電腦有很大的 ram (早期 512MB vs. 現在最少 4GB), 用 60 的結果是, 明明還有很多空間可用, 一搬大檔案, file cache 吃掉過多記憶體, kernel 就將 application 用的記憶體搬到 swap, 於是整個系統進入莫明奇妙緩慢的狀態。

若要避免這問題再度發生, 參照這篇

Python 從 MySQL 資料庫取出整個表格的快速作法

處理大量資料時, 發覺資料愈大, python 取出 mysql 資料愈慢, 變慢的比例並非線性的, 推測是 client 吃掉太大的記憶體而拖慢速度。

但是用 mysql client 直接寫入檔案, 以及 python 讀檔的速度都不慢。另外, mysql 有提供兩種方式從 server 端取回查詢結果, 有機會用 sequential 讀資料的方式加速。想說來比看看這三種寫法。

我從一個有近三千萬筆資料的表格取出單欄資訊, 型別為 varchar。各個方法只測一次, 記憶體用量是用 htop 大概看的, 別太在意。結果如下:

methodtimememory (G)mysql client + read file (all)1m8s~0.64mysql client + read file (sequential)0m57s~0python api (store)13m40s~3.7python api (use + fetchall)>30m>0.9python api (use + fetchmany)1m9s~0
上欄各個方法說明如下, 所有 python 操作都有轉 unicode:
mysql client: 直接用 mysql client (系統指令) 寫入檔案 。read file (all): 從檔案讀入全部內容到一個 list。read file (sequential): 從檔案一行一行讀入內容。python api (store): 用 MySQLdb 預設的連線方式, 會用 mysql server 提供的 api mysql_store_result() 取回全部內容到 client 端。python api (use + fetchall): 改用 mysql server 提供的 api mysql_use_result() 一筆筆取回到 client 端 (透過 SSCursor 使用 mysql_use_result() )。python api (use + fetchmany): 同上, 改設 size = 10000, 一次取回 10000 筆以減少取資料次數。從結果來看, 先用 mysql client 取出資料寫到檔案, 再用 python 讀檔案比 python api 預設的方式快了十倍左右。其中 m…

增加自定的 logging 的級別

參考 logging.info() 和 logging.root.info() 後, 總算弄出 loggin.detail(message), 用來記錄 DETAIL 級別的 log, DETAIL 是我自訂的級別, 重要性介於 DEBUG 和 INFO 之間。不確定這樣是不是最好的寫法, 總之有弄成功啦。

def _detail(msg, *args, **kwargs): """ Log a message with severity 'DETAIL' on the root logger. """ if len(logging.root.handlers) == 0: logging.basicConfig() if logging.root.manager.disable >= logging.DETAIL: return if logging.DETAIL >= logging.root.getEffectiveLevel(): apply(logging.root._log, (logging.DETAIL, msg, args), kwargs) def add_detail_to_logging(): """ Add a customized level between INFO and DEBUG. """ logging.DETAIL = 15 logging.addLevelName(logging.DETAIL, 'DETAIL') logging.detail = _detail

安裝 MySQL-python

先說裝法:

sudo vi  /etc/apt/sources.list: 加入 hardy-security 列表: deb http://security.ubuntu.com/ubuntu hardy-security main restricted deb-src http://security.ubuntu.com/ubuntu hardy-security main restricted deb http://security.ubuntu.com/ubuntu hardy-security universe deb-src http://security.ubuntu.com/ubuntu hardy-security universe deb http://security.ubuntu.com/ubuntu hardy-security multiverse deb-src http://security.ubuntu.com/ubuntu hardy-security multiverse sudo aptitude update sudo aptitude install libmysqlclient-dev(enter virtualenv)sudo pip install MySQL-python若不用 virtualenv, 第 4, 5 步就改為 sudo easy_install MySQL-python。

用 pip 裝 MySQL-python 時, 出現「EnvironmentError: mysql_config not found」的錯誤訊息, 摸索一陣子後才發覺 mysql_config 是個程式, 要裝 libmysqlclient-dev 才會有 (用 apt-file search mysql_config 查出來的)。結果用 aptitude install libmysqlclient-dev 卻說找不到。查了官網 package 列表, 才發覺它是放在 hardy-security 裡。在 /etc/apt/sources.list 裡加上 hardy-security 的位置, aptitude update 後, 就能安裝 libmysqlclient-dev 了。裝好 libmysqlclient-dev…

mysql server 連線問題

剛發覺可以從 mysql server 本機連 localhost, 但無法從別台機器連到 mysql server, 會出現 "Can't connect to MySQL server on X.X.X.X" 的錯誤訊息。查了一下, 這篇提到解法。將 /etc/mysql/my.cnf 的 "bind-address       = 127.0.0.1" 註解掉, 就可以從別台機器連了。看來 Ubuntu 上 mysql 預設是只能從本機連。