2011年2月18日 星期五

設定 apache2 和 django 壓縮輸出內容

Google Page Speed 分析後, 發現 javascript 和 css 可以壓個七八成, 實在滿可觀的, 就試著來設壓縮。官網 mod_deflate 看看就會設了, 下面的範例是壓縮 css 和 javascript:
AddOutputFilterByType DEFLATE text/css text/javascript application/javascript application/x-javascript
可以查 MIME 列表, 看要壓那些類型的檔案。或是先設全部都有, 再加條件不壓圖。

比較擾人的是, 早期的一些瀏覽器不能正確處理壓縮文件, 官網教學說要這麼設:
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
說明如下:
The third BrowserMatch directive fixes the guessed identity of the user agent, because the Microsoft Internet Explorer identifies itself also as "Mozilla/4" but is actually able to handle requested compression. Therefore we match against the additional string "MSIE" (\b means "word boundary") in the User-Agent Header and turn off the restrictions defined before.

剛看始看看不懂, 後來看到 Internet Explorer User Agent Strings 才知道 IE 會自稱 Mozilla/4, 但它可以處理壓縮, 所以要再加第三行取消前兩行的限制。How To: Optimize Your Apache Site with Mod Deflate
文末提到 IE6 聽說有問題, 但他測不出來, 所以他沒有另外關掉 IE6。

Django 的話, 可以讓 apache2 協助處理, 加個 text/html, 或是加入 GZipMiddleWare, 都很簡單。

測試資料是否有壓縮有很多方法, 最保險的是用 Firefox Live HTTP Headers 看回傳結果有沒有 gzip。不會看 header 的話, 可以透過 Page Speed 或 GIDZipTest 測看看。

MySQL 將資料塞到 memory 的作法

要加快取資料的速度, 沒有別的方法, 不是先將常用的資料塞到記憶體裡等待查詢, 就是準備好 cache 並先查詢常用的 query, 將結果存在 cache 裡 (俗稱 warm up), 之後再查就會快了。query cache 是 MySQL 能如此快速的原因之一, MySQL 執行 query 前會先查 query cache, SQL 內容一字不差的話, 就直接取值來用。

事先將資料塞入記憶體的作法有幾種, 目前最常用的兩招是:
  • 資料不大的話, 就開個 table 用 MEMORY engine 存, 記得將 max_heap_table_size 設大一點。若嫌 MySQL 重開後要重填資料很麻煩, 可以先將資料存在 my_table_on_disk, 照以下步驟產生資料, 三個 SQL 而已:
    • CREATE TABLE my_table_in_memory LIKE my_table_on_disk;
    • ALTER TABLE my_table_in_memory ENGINE = MEMORY;
    • INSERT INTO my_table_in_memory SELECT * FROM my_table;
  • 若資料很大或是某些欄位無法存到記憶體裡, 就改對常用的幾個欄位建 covering index。若 engine 能用 MyISAM 的話, 就能用「LOAD INDEX INTO CACHE my_table_on_disk」事先載入 index 到記憶體裡。
使用 MEMORY engine 的注意事項:
  • 注意 varchar / char 的長度限制, MEMORY engine 沒有 varchar, 會自動轉成 char, 沒設好可是很揮霍的。
  • 用 MEMORY engine 時, 沒指定 index type 的話, 預設用 HASH 而不是 B-Tree。用 = 或 IN 查詢時應該會比用 B-TREE 快, 不過重點是用 HASH 比較省空間, 不管欄位大小為何, hash 後都是一樣大的。但是 HASH 不支援 range query。
用 covering index 的注意事項:
  • load index into cache 不是永久性的, 資料有可能被其它 table 的 index 擠走。在意的話, 最好設多個不同的 key cache, 將不希望被擠出 key cache 的 index 放入獨自的 key cache。
其它相關心得:
  • MyISAM 無法將資料事先載入記憶體, 而是讓 OS 管 file cache (MyISAM 資料本身是一個大檔案), 先用 select 掃一次 table, 之後操作的確會變快不少, 但之後無法掌握各段資料是否在記憶體裡。所以才會有上面提的 MEMORY engine 和 covering index + load index 的作法。
  • InnoDB 有將資料存在 cache 裡, 目前還沒參透 InnoDB 的情況, 可以調的東西太多, 不好上手。

2011年2月17日 星期四

讓 vim 的複製貼上和其它 terminal 以及 Windows 共用 clipboard

今天從同事 P 那學到的。

目的是用 Windows 開 putty 連 Linux 工作站時, 可以在 screen 內跨 window 共用 clipboard, 也可以和 Windows 共用。比方說將程式碼貼到 Windows 下的 Gmail 時, 原本要先關掉 vsplit 並 set nonu, 才能用滑鼠正確選出要的內容, 接著貼到 Gmail。若能共享 clipboard 的話, 就能在 Linux 的 vim 內按個 20yy 複製, 回到 Gmail 裡按 ctrl+V 貼上 20 行內容。

作法如下:
  1. Windows 下要安裝 Xming X Server for Windows。裝好啟動後, 功能表旁會有個 X 的小圖示。
  2. 設定 putty: connections -> SSH -> X11 -> Enable X11 forwarding。
  3. 在 bash 下執行 xterm 測試看看, 成功的話會在 Windows 跑出一個 xterm。
  4. 重開新的 screen, 這時應該可以透過 "+ 複製貼上了。也就是說, "+yy 後, 可以到別的 screen 下其它 window 下用 "+p 貼上。也能在 Windows 下用 ctrl+c / ctrl+v 互通。
  5. 若要省掉 "+ 的步驟, 要將 clipboard 和 vim 預設 register 設成一樣的, 設法是 :set clipboard=unnamed
另外, 可以用 :reg 或 :di 顯示目前所有 register 裡存的東西, 搜 vim register 可看到一些相關介紹。

2011/02/18 更新

補充 Vim tip: Accessing the system clipboard

2011/07/13 更新

注意: vim 編譯時要支援 +clipboard 才行, 可用以下的方法檢查:
$ vim --version | grep clipboard
若有看到 +clipboard 表示有支援, -clipboard 表示沒有。Ubuntu 下要裝 vim-gui-common 才會支援, 光裝 vim-common 不會包含 +clipboard。或是 :set showcmd 時, 看看按了 "+ 後右下角有沒有出來 "+, 沒有的話表示沒打開 clipboard。

2011年2月15日 星期二

Python 取 attribute 的規則以及 descriptor

這其實頗複雜的, 還有一些細節摸清楚後應該很有意思, 先記下目前理解的程度。

基本認知: 在 Python 的世界裡, everything is object, 包含 type (class) 也是 object, 一些基本型別如整數、浮點數、字串也是 object (我個人對於這點頗感冒的, 效率變差卻不會方便多少)。Python 2.2 後採用 new style class, 也就是所有 class 都要繼承 object, 像是寫成 "class NewStyle(object):", 而寫成 "class OldStyle:" 就是舊式寫法 。 Python 3 裡則是只有 new style class, 寫成 "class A:" 等同於 "class A(object):"。new style class 才能使用 descriptor, 以下討論的行為都是 new style class。

物件實際取用 attribute 的順序

對於物件 x 來說, x.a 實際取到的物件, 會依下列順序執行, 直到成功取得物件為止:
  1. 取得 x 的 data descriptor a
  2. 取得 x 自身物件的屬性 (即 x.__dict__['a'], __dict__ 是 dict)
  3. 取得 x 的 non-data descriptor
  4. raise AttributeError
data descriptor 是一個有實作 __get__, __set__ 的類別產生的物件。另有 __del__ 可以實作, 這三者分表在這三種情況被呼叫:
  • x.a
  • x.a = ...
  • del x.a
non-data descriptor 只有實作 __get__, 可以被 x.a = ... 取代掉。

Python 找 descriptor 的順序是
  1. type(x).__dict__['a']
  2. 往 type(x) 的父類別找
  3. ...

property、method、classmethod、staticmethod

property 是產生 descriptor 的 helper function。method 是 non-data descriptor 的一種例子, 換句話說, 物件的 method 之後可以被取代:
class X(object):
    def a(self):
        print 'a value'

x = X()
print x.a  # <bound method X.a of <__main__.X object at 0x800fcd6d0>>
x.a()      # a value

x.a = 3
print x.a  # 3
可以看出 x.a 被替換掉了。

classmethod、staticmethod 都是 helper function, 幫忙產生不同的 method。method / classmethod / staticmethod 都是 non-data descriptor, 和前面 CachedAttribute 的差別, 在於它們傳回一個 callable object, 而不是直接傳回最後要用的值。callable object 有實作 __call__, 之後再被呼叫 (使用 () )。Python 用 descriptor 實作 method / classmethod / staticmethod, 藉此傳回一個 callable object。method 會先置入 self, classmethod 則是置入「type(self)」。以上面的例子來說, 下面四者得到一樣的結果:
  • x.a()
  • type(x).a(x)
  • type(x).__dict__['a'](x)
  • type(x).__dict__['a'].__get__(x, type(x))()
x.a() 實際運作的方式, 就是最後一項的取法。Python 用這種繞彎的方式執行 method, 以提供很大的彈性置換操作行為。取值的過程中從另外的物件 (type(x).__dict__['a']) 產生物件 (__get__(x, type(x)) 的傳回值) 再來運算, 能做的事自然多了很多。日後有適當的機會再來用試看看。

這裡的程式碼 顯示上述這些屬性的特徵, 這裡是執行結果。type(x).__dict__ 包含 method / classmethod / staticmethod, 但是它們的值和 x.method / x.staticmethod / x.classmethod 不同, 反而是 type(x).__dict__[...].__get___(...) 的結果和 x.method / ... 相同, 由此可驗證它們都是 descriptor。剩下的細節就請參照參考資料, 只需要一點耐心, 並不困難。

descriptor 的用途

property 算是最常見的用法, 將原本是普通屬性的 x.a 替換成函式操作, 藉此在不改變介面的情況下, 置入較複雜的操作, 省下改 caller 程式的成本。不過一般不建議濫用 property, 畢竟 function call 的成本比直接取值高。在大量取值的情況下, 用 property 會讓人誤以為可以很便宜的使用而造成效率問題。

descriptor 本身有自己的狀態可用, 方便管理 attribute 的控制邏輯。舉例來說, 若想寫個會自動維護 cache 的 attribute, 可以這麼寫:
class CachedAttribute(object):
    def __init__(self, getter, setter):
        self._getter = getter
        self._setter = setter
        self._cache = None

    def __get__(self, obj, objtype=None):
        if self._cache is None:
            self._cache = self._getter()
        return self._cache

    def __set__(self, obj, val):
        self._setter(val)
        self._cache = val
使用 CachedAttribute 的 class 這麼寫:
class User(object):
    # get_name 和 set_name 是寫好的函式,
    # 比方說從 DB 取值和設值
    name = CachedAttribute(get_name, set_name)

user = User()
user.name  # 呼叫 type(user).__dict__['name'].__get__(user, type(user))

這樣不同 class 就能共用管理 cache 的程式, 像是改寫成每取十次就再取新值, 或是加上 expire 機制之類的, 而這些使用 CachedAttribute 的 class, 彼此之間沒有關聯, 也不用增加 state 管理 cache。愈少 class 之間的相依性, 愈少內部 state, 程式就愈容易維護。

參考資料

  • Python Types and Objects: 解釋 object、class 和 meta class (即 type)。附有超清楚的關係圖。讀懂會很爽, 跳過不讀對理解 descriptor 似乎沒太大影響
  • Python Attributes and Methods: 詳細的說明 __dict__ 取值流程, 還有 descriptor 和 method 等的關係
  • How-To Guide for Descriptors: 和上篇互補, 我覺得這篇寫得最清楚, 還有提到一點 CPython 相關的程式碼, 不過前兩篇介紹背景知識介紹得較詳細

2011年2月11日 星期五

Python 移掉 unicode 裡的 control code

先貼 code 再說:
ctrl_ords = range(32)        # 取得所有不能顯示的字元和 '\n' 以及 TAB
ctrl_ords.remove(ord('\t'))  # 去掉 TAB
ctrl_char_map = dict.fromkeys(ctrl_ords, None)  # 建立對照表
line = line.decode('utf8').translate(ctrl_char_map)  # 用 translate 移掉對照表裡的字元
str 和 unicode 的 translate 用法不同, 學 unicode 應該就夠了。unicode 版的 translate 要求傳入一個 dict, key 必須是 unicode ordinals, 沒注意到 "ordinals", 一直試都沒效果。value 可以是  unicode ordinals, strings 或 None, None 表示要刪掉該字元。

相關參考資料:

2011年2月4日 星期五

用 XOR 檢查加法後是否有溢位

看 Python intobject.c 時看到的:
// intobject.c
static PyObject *
int_add(PyIntObject *v, PyIntObject *w)
{
    register long a, b, x;
    CONVERT_TO_LONG(v, a);
    CONVERT_TO_LONG(w, b);
    x = a + b;
    if ((x^a) >= 0 || (x^b) >= 0)
        return PyInt_FromLong(x);
    return PyLong_Type.tp_as_number->nb_add((PyObject *)v, (PyObject *)w);
}
上面這段的
if ((x^a) >= 0 || (x^b) >= 0)
是在檢查是否有溢位, 看一陣子才想通, 概念如下:
  • 兩個正數相加變成負數表示溢位
  • 兩個負數相加變成正數也表示溢位
  • 除了這兩者外沒有其它情況
  • 綜合上述的條件, 可以簡化成: 兩數相加後的正負號和原本兩者都不同 <---> 溢位
  • 目前 CPU 都採用二補數系統, 最高 bit 可看作是 sign bit
  • XOR 剛好可以滿足簡化的條件, 其它 bit 無關緊要, 運算完看 sign bit 即可
位元運算的世界真神奇啊, 可以省下不少工。

在 Fedora 下裝 id-utils

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