2010年6月26日 星期六

Python 2.6 的好東西

我目前在用 Ubuntu 8.04, 但 Ubuntu 8.04 只提供 python 2.5 的 deb檔。想換 Ubuntu 10.04, 但 10.04 卻只有 python 2.6 的 deb 檔。只好先暫時守著 Ubuntu 8.04 + python 2.5。平時看文件偶而會看到一些 2.6 才有的好東西, 不能用真是太難過了。這篇暫記一些看到的好東西, 避免一段時間過去沒用過就忘光了。
  • with: 2.5 可用 from __future__ import with_statements 取得。
  • multiprocessing: 2.5 可從 python-multiprocessing 裝。
  • namedtuple: 產生 value object 的 factory function。之前傻傻地寫過類似的 code generator, 功能弱多了。
  • productpermutationscombinations: 產生排列組合的函數。用 product 可以將多組參數的組合攤平成一個迴圈, 語意上更容易理解。參考官網的示意說明, 我自己寫了個 product 來用。
  • sys.getsizeof: 取得物件在記憶體中占的 byte 數。最佳化和協助理解 python 內部結構的好東西啊, 我一直很困惑為什麼沒用多少東西就吃掉一堆記憶體。
之後看到再陸續補上吧, 有些已忘掉了...。

2010年6月22日 星期二

查看 Linux / Ubuntu 下的硬體資訊

參照這篇這篇的回答, 學到一些看硬體資訊的好工具。 serverfault.com 果然是系統管理員的好朋友啊。

dmidecode 超級好用, 而且 Linux 都可使用, 配合 -t 會只顯示細部資訊, 像是:
  • dmidecode -t processor: 明白是幾 CPU 幾 core, 有無 hyperthead。
  • dmidecode -t cache: 查詢 L1, L2, L3 cache 的大小。
  • dmidecode -t memory: 知道插了幾張 RAM 卡, 各張卡大小為何。
  • dmidecode -t slot: 查詢有那些插槽可用, 像是有幾個 PCI Express x16, 有幾個 PCI 32 bits 等。各個 slot 會顯示有無插卡。
  • dmidecode -t system: 主機的廠商。比方說買 Dell 的電腦, 可從這裡看到主機型號和序號。
  • dmidecode -t baseboard: 顯示 on-board 的設備, 像是網卡、音效卡之類的。
其它幾個被提到的指令有 lspci 和 lsusb, 和檔案 /proc/cpuinfo 和 /proc/meminfo。可以看到不同細節。lspci -vv 和 lspci -nn 不錯用。有人提到查 driver 時可配合它使用。之後有需求時再試吧。Ubuntu 另有 lshw 會顯示詳細資訊, 不過用 dmidecode 就解決我原本的疑問了, 沒有仔細試它。

待查事項: 
  • 目前還不知道要怎麼查那個 slot 上插的是什麼卡。比方說 dmidecode -t slot 顯示 PCI Express x16 和 x8 各插了一張卡, 而 lspci 會列出各張卡的詳細資訊。但是要怎麼知道那張槽是插那張卡呢?

2010年6月17日 星期四

用 decorator 計時

最近讀 open source project 時看到的小技巧, 稍加改寫如下:
def log_it(function):
    def wrapper(*args, **kwargs):
        start = time.time()
        try:
            return function(*args, **kwargs)
        finally:
            stop = time.time()
            logging.debug('%s: %.3fs' % (function.__name__, stop - start))
    return wrapper

# usage
@log_it
def some_function(...):
   ...

如此一來 call some_function 時就會記錄它的執行時間到 log file 裡。之前沒想過 finally 可以這麼用, 配上 decorator 太簡潔啦。先存下 function 的傳回值, 寫入 log 再回傳也可做到同樣效果, 不過當 function 丟出 exception 時, 就沒記到時間。而上面的寫法仍然會 log 時間。若要區別 function 是否有正常執行完, 得在 try 和 finally 之間加個 except:, 做完該做的事再執行 raise 將 exception 重丟出去。

2010年6月15日 星期二

試改 Chrome Extension

extension Create Link 滿好用的, 可以自定取出網址和網頁標題的格式, 方便貼到 blog 或是 plurk。美中不足的是, 它沒支援短網址。有不少 extension 提供短網址服務, 可惜沒提供像 CreateLink 那樣自制的格式, 讓我能按一個鍵就取出短網址並以我想要的格式排版。比方說按個鍵產生 "SHORT_URL (TITLE)", 就能直接貼到 plurk 上了。

看了一下 Shorten URLs 和 Create Link 的程式碼後, 決定抽出 Shorten URLs 產生短網址的程式碼, 加到 CreateLink 裡。比想像中簡單許多, 懂 JavaScript 的話, 只要了解 Chrome Extension 的規範, 就能輕易上手。不過不知是不是 Chrome 的問題, 有時 Extension 會沒反應, 但重開 Chrome 就好了。

改完的結果在這裡, 整個修改流程如下:
  1. 閱讀 Chrome Extension 的入門教學。照著做一遍就會了, 再看一下如何除錯就能上工了。
  2. 在 github 上 Fork CreateLink 的專案, open source 真好啊。
  3. 在 Windows 上用 TortoiseGit 連 github, 這一步花掉我最久的時間, 實在是很挫折的事。參考官網說明。途中遇到不少問題, 最後不知怎麼弄對了, 懶得理了。下回考慮用 andLinux 連 github。不過這樣得溫習 git 的指令, 原本就是懶得查指令, 結果讓 TortoiseGit 能和 github 連線, 反而花了更多時間...。
  4. 載入本機未封裝的 CreateLink, 改一下 manifest.json, 確定自己的修改有發揮作用。個人認為改程式時, 這是最重要的第一步, 愈早完成愈好。
  5. 看一下 Shorten URLs 的程式明白怎麼用 XmlHttpRequest 透過 GET 連網站, 再查一下相關參數說明, 將 async 設成 false 即可。將程式寫成等三秒沒結果就放棄短網址改用原網址。原本有考慮用 jQuery 做, 看到原作者全部檔案加起來都比 jquery-1.4.2.min.js 小 (34.2KB vs. 70.4KB), 就打消這個念頭了。
  6. 為了能用 JavaScript 連 tinyurl 使用它的 API, 得在 Chrome Extension 裡允許 cross site javascript, 參考官網說明輕鬆解決。
  7. Create Link 程式寫得很乾淨, 改起來很容易。最後卻是花了不少時間將改完的結果 push 回 github。
第一次改別人的程式並 push 回 open source repository (雖說只是 push 回自己的啦), 還有寫瀏覽器擴充套件, 滿有意思的。最後, 就用剛才改好的 CreateLink 產生連結貼到微網址吧!!



2010年6月12日 星期六

在 Ubuntu 8.04 上透過 gmail 用命令列寄信

2012-03-01 更新

找到更通用的作法, 用 Python 的 smtplib, 細節見 segfault.in » Sending Emails Via Gmail SMTP With Python

( 之前寫的舊方法 )

用命令列寄信好處多多, 像是程式掛的時候寄個信通知自己。但若又懶得自己架個 mail server, 可以透過 gmail 幫忙寄信。

安裝

參考這篇設定 ~/.mailrc 和 ~/.msmtprc。這篇有更詳細的 msmtp 設定。之後用命令列的 mail 寄信時就會透過設好的 gmail client 寄信。簡記過程如下。
$ sudo aptitude install heirloom-mailx msmtp
$ vim ~/.mailrc # 見下文
$ vim ~/.msmtprc # 見下文
$ chmod 600 ~/.msmtprc
$ echo "Hello, world!" | mail -s "Test from command line" somebody@somedomain.org
~/.mailrc
set from="YOURNAME@gmail.com (YOURNAME)"
set sendmail="/usr/bin/msmtp"
set message-sendmail-extra-arguments="-a gmail"
~/.msmtprc
defaults
logfile /home/USER/msmtp.log

# gmail account
account gmail
auth on
host smtp.gmail.com
port 587
user YOURNAME@gmail.com
password YOURPASSWORD
from YOURNAME@gmail.com
tls on
tls_trust_file /usr/share/ca-certificates/mozilla/Equifax_Secure_CA.crt

# set default account to use (not necessary with single account)
account default : gmail

可能遇到的問題

msmtp: no recipients
mail 裝到mailutils。移掉它改裝 heirloom-mailx。
msmtp: TLS certificate verification failed: the certificate hasn't got a known issuer
用到舊的 certificate, 參照 msmtp 設定, 在 ~/.msmtprc 裡這麼設 tls_trust_file:
tls_trust_file /usr/share/ca-certificates/mozilla/Equifax_Secure_CA.crt
這檔案本來就在我的系統裡, 不知是 Ubuntu 裝好時就有還是之前裝啥東西時一併裝進來的。

2010年6月10日 星期四

計算 64 bit long 內有幾個 bit 為 1 的速算法

原本在讀 bit array 為啥威, 結果看到 Hamming weight, 即計算一個 "string" 內有幾個 1 的速算法。

基本版 (popcount_1) 的想法還算好懂, 典型的 divide & conquer, 對半切的解法, 看了開頭就猜到後面要怎麼做了。先以 2 bit 為單位算出有幾個 1, 並存在原本的那 2 bit 裡, 再用同樣的手法以 4 bit 為單位算出 4 bit 裡有幾個 1, 並存在原本的那 4 bit 裡, ..., 最後就會在較低的 32 bit 裡得到全部答案。

進階版 (popcount_2) 的第一個寫法有點妙, 我最後才參透它。8、16、32 bit 時最簡單, 若 bit 數的最大值不會超出儲存的空間, 就不用擔心相加會溢位, 而能直接位移並做相加。2 bit 的作法沒變, 4 bit 的作法和 8 bit 開始的作法一樣, 只是先清乾淨「高位元」的地方, 確保後面處理 8、16、32 bit 時不會溢位。

回頭看 x -= (x >> 1) & m1, 將式子展開會得到 x = x - ((x >> 1) & m1)。而原本的算法是 (x & m1) + ((x >> 1) & m1), 將兩式合起來看會得到:
x - ((x >> 1) & m1) = (x & m1) + ((x >> 1) & m1)
x - (x & m1) = ((x >> 1) & m1) + ((x >> 1) & m1)
整理左式:
x - (x & m1) = x & (m1 << 1)
整理右式
((x >> 1) & m1) + ((x >> 1) & m1)
= 2 * ((x >> 1) & m1) 
= ((x >> 1) & m1) << 1
= x & (m1 << 1)

所以這個算法沒錯, 將上面的過程反過來推導, 就會發覺可以用 x - ((x >> 1) & m1) 取代原本的式子以省下一個運算。
H = (x >> 1) & m1
L = x & m1
H + L 
= H + H - H + L
= 2H - H + L 
= (x - L) - H + L
= x - H
= x - (x >> 1) & m1

位元運算的世界真是太奇妙了, 後面的更進階版有機會再來參透吧。

2010年6月5日 星期六

Django 和 Python 操作 database 時的額外負擔

為了搞清楚 Django ORM 的效率瓶頸, 做了個簡單的測試, 每筆實驗只有跑個兩三次, 不過結果差不多。

SQL x 1, new object x N

從本機的資料庫取出大量資料 (>1m筆), 然後將結果寫入 /dev/null, 資料型別為 utf-8。結果如下:

methodtime (min / sec)memory (G)
MySQL client0'080.54
python client1'021.4
django client without using iterator17'395.x
django client using iterator3'161.7
django client using raw sql1'021.5

實驗細節說明:
  • Django 1.2, MySQL 5.0。
  • 用 time 測時間。
  • memory 是我用眼睛注意 htop 的數據。django client without using iterator 跑太久了, 只好邊玩猴子守城四代邊跑實驗, 最大值就沒抓準了。
    (2012-02-02 更新) 只需定時執行
    grep VmPeak /proc/PID/status | awk '{print $2}'
    就不用「辛苦」地邊玩遊戲邊抓數據了。年輕時不懂事, 了解一些系統知識差異真大。
  • MySQL client 的用法: mysql < my_select.sql > /dev/null。沒做 encoding 轉換。
  • python client 的操作內容只有從 utf-8 轉成 unicode (MySQLdb 做的) 再轉回 utf-8, 補個換行字元, 就寫入檔案。
  • django 會自動把 utf-8 轉成 unicode。django client 的操作和 python client 幾乎一樣。
從上面的結果可以看出, 資料大時還是用 raw SQL 較好, 時間差了兩倍。看來生成 Django 物件比生成 Python 物件貴了一些。

至於 python 花了 mysql client  8 倍的時間, 只好當作用 python 的必要成本。不知其它語言 (C++、Java、Ruby、PHP) 這方面的額外負擔有多大。

為了弄清楚時間花在那裡, 另外試了 java client 和不轉 utf-8 的情況:

methodtime (min / sec)memory (G)
python client1'021.4
python client without encoding and decoding0'43
java client (using mysql connector)0'511.1

看來轉換 utf-8 花了不少時間, 到是用 Java 也沒省下多少時間。有可能 python client 已幾乎都是 native code 了。

SQL x N, new object x N

我用 primary key (id = 1 ~ 100,000) 分別做 100,000 次操作取出 100,000 筆資料, 結果如下:

methodtime (min / sec)
django client1'26
django client using raw sql0'20
實驗細節說明:
  • django client 用 get(id=ID)。
  • raw sql 用 where id = ID。
用 ORM 花了四倍的時間。根據 time 的結果, 我猜 real - user - sys 大概就是 IO time, 而 django client 確實多花了大量的 user time (1'13 vs. 0'09)。上面的實驗 ORM 花了三倍時間, 這裡多出一倍的時間有可能是 ORM evaluation 或生成 Django 物件造成的。對照上個實驗 (evaluation x 1, new Django object x N), 看來問題比較可能出在生成 Django 物件。之前用 python 的經驗裡, 發覺產生大量資料的情況下, 生成 python 物件還挺貴的。 另外, 用一個 SQL 取回 100,000 筆資料的時間只要不到 1s, 相較於 ORM 的負擔, 下太多次 SQL 是更大的問題。若只是做 1,000 次操作, ORM 的額外負擔就不到 1s, 感覺還好。

相關討論

這篇提到取出「大量」資料時的解法,  可以拆成多個 SQL 分次用 primary key 取出。留言裡有提到不要用 slicing (即 MySQL 的 offset + limit), DBMS 會取出大量資料再丟掉 limit 量之外的資料, 效率很差。我以為 offset 會一次取到對的資料, 一開始跑實驗時用 offset 沒用 primary key, 結果瓶頸變 IO, 反而沒測出 ORM 的額外負擔。 上面提的作法可以解掉 memory 的問題。但若資料量更大, 要跑得更快時, 還是得用 raw SQL。
不過這也不表示資料量大就要用 raw SQL, 得看應用場合。若瓶頸在其它地方, 用 raw SQL 只會省掉整體的 1/10 時間, 用 ORM 也不壞, 方便日後維護。

2010年6月3日 星期四

在 http response header 裡設 expire 以減少 request 數量

High Performance Web Sites: Rule 3 - Add an Expires Header 提到可以在 http response header 裡設 expire time 減少 client 送出 http request。藉由 last modified time 和 etag 只能省去 client 重下載檔案的時間, client 仍會送出 http request。若一個頁面載入十個檔案 (JavaScript、CSS、圖檔等), client 仍會送出十個 http request。別小看 http request 的時間, 數量一多若有其中一兩個被拖到, 網頁就會拖慢。

Apache 設定 expire 的方式很簡單:
  1. a2enmod expires  # enable mod_expires
  2. 編輯 /etc/apache/conf.d/cache.conf (檔名隨便取):

    ExpiresActive On
    ExpiresByType text/css "access plus 4 weeks"
    ExpiresByType text/js "access plus 4 weeks"
    ExpiresByType image/gif "access plus 4 weeks"
    ExpiresByType image/jpeg "access plus 4 weeks"
    ExpiresByType image/png "access plus 4 weeks"
    這樣會把 CSS、JavaScript 和常用圖檔設成使用者拿到檔案的四週後才會過期。
也可以設成依檔案修改時間來設過期時間, 而不是使用者拿到的時間, 參見 mod_expires 看更詳細的說明。

但設了 expire time 後帶出一個新問題, 萬一 server 端在 expire time 前有更新檔案怎麼辦? 在該頁的留言裡有討論幾種解法的優缺點:
  1. 加版本編號到檔名裡, 如 yahoo_2.0.6.js, Yahoo 是這麼做的。缺點是要改 code (HTML header、CSS 內文等), 也得想想怎麼和版本控制整合, 變成每次 deployment 就得改檔名。
  2. 加上 query string, 像是 base.css?v=CREATED_TIMESTAMP。缺點是有些 CDN 業者和 Proxy 不支援靜態檔案的 query string, 若它們忽略 query string, 靜態檔案就不會被更新了。也有人提到 HTTP 1.1 規定有 query string 的檔案不能被 cache, 雖說大部份瀏覽器仍會 cache。但也有人提到 HTTP 1.1 規定有送出 expire time 就能被 cache。無論如何, CDN 業者和 Proxy 的問題較頭大。
  3. 加上 virtual URL, 像是 /media/css/base.css 變成 /media/VERSION/css/base.css, 這樣就無 CDN、Proxy 的問題。不過不知道要怎麼在 web server 做對應的處置較好, 設 alias 忽略中間那段似乎是最簡單的解法? 即設個 alias 將 /media/VERSION/css/base.css 指到 /media/css/base.css。這樣 CSS 內用相對路徑取圖的話, 應該也不用改 CSS code。修改幅度最小。
附帶一提, 檢測用的相關工具:
  • Firefox 的 YSlow 會查那些靜態檔案沒設 expire time。
  • Firefox 的 Live HTTP Headers 會秀出目前連線的所有 http connection 的 request 和 response headers。可以用來查看 Expire 是否有被設對, 還有 client 是否真的沒發出 request。
  • Chrome 的 Speed Tracer 可以秀出頁面載入所有檔案的開始時間, 包含 http request、response 開始和結束的時間。也有 header 內的訊息。多種願望, 一次滿足!!
瀏覽器對待 expire time 的行為如下 (試出來的):
  • Firefox 按重新整理就不會理會 expire time, 在網址列按 Enter 重讀會參考 expire time。
  • Chrome 按重新整理或在網址列按 Enter 重讀都不會理會 expire time, 只有在網頁內點選的連結才會參考 expire time。

2010年6月1日 星期二

用 pip 裝 lxml

剛學 python 時覺得 lxml 超難裝, 因為它用到一些 C library, 不過我一直沒搞清楚它到底用到什麼, 後來都用 ubuntu package 裝 lxml (aptitude install python-lxml)。

一兩個月前看了這篇《Tools of the Modern Python Hacker: Virtualenv, Fabric and Pip》, 想說就來試看看這三個東西吧。陸續試用後, 發覺果然是強大的工具, python 開發者的必備幫手啊!! 最近用的套件要用到 lxml, 這回決定用 pip 裝 lxml, 確保能裝在 virtualenv 裡, 方便管理。

結果果然不是 pip install lxml 就會搞定的事。看到錯誤訊息裡提到 lxml 要用到這些東西:
  • Cython (optional)
  • development packages of libxml2
  • development packages of libxslt 
摸索一下就找到裝法:
  • pip install cython
  • sudo aptitude install libxml2-dev
  • sudo aptitude install libxslt-dev
之後再 pip install lxml 就搞定了。克服以前沒做到的事, 有小小的成就感。
題外話, 未來 python 社群要轉用 pip 而非 easy_intall 裝 python package, 官網的安裝說明也都從以前的 easy_install 改成 pip 的指令了。看到社群的成長, 挺開心的。雖然用 open source 開發偶而踏到雷, 一天的時間就沒了, 但在自己沒做啥事的情況下有新功能可用, 還是格外的開心, 大家真是好人, 自己也該有所回饋才是。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...