PHP count() 取得 Array 元素個數 – 效率問題

PHP 的 count() 是這樣用的 int count ( mixed $var [, int $mode = COUNT_NORMAL ] ) 而其對象可以是其實 Object 可以是 Array 也可以是 NULL (一律回傳 0 ) 也可以是其他型態 (一律回傳 1 ) 。

count() 在 PHP 底層的是這樣實作的:
Continue reading “PHP count() 取得 Array 元素個數 – 效率問題”

PHP 5.4 效能比起 PHP 5.3.x 有長足的進步

一年多前有個 C++ vs. Python vs. Perl vs. PHP performance benchmark 測試,當時 PHP 5.3.5 實在是被打的慘不忍睹。

測試程式的內容是找出 10000000 (一千萬) 以內的質數,然後重複執行十次。以不同語言實作,再執行各個比較時間。

我最近在自己的 Mac 上面編譯了 PHP 5.4 ,再跑一次這個測試,結果有比較滿意了 (但比起其他語言,還是輸!)
Continue reading “PHP 5.4 效能比起 PHP 5.3.x 有長足的進步”

WTF 系列 – PHP 的 MAP 鍵值型態問題

在 PHP 裡面,可以用儲存鍵值組的 MAP 是個很實用的型別,值的型態可以是任意型態,但鍵的型態只能是 String 和 Int (The key can either be an integer or a string. The value can be of any type.)

以下是一些 PHP 處理 “鍵” 自動轉型的狀況分析:

當是 String 型態,鍵為 “8” 的時候,會被轉型為 Int 8 ,但如果是 “08” 則依然會被當做 String 處理。
當 Float 型態的時候,會被自動轉型成為 Int ,例如 8.7 會被捨去小數點,變成 8 。
當 Bool 型態的時候,一樣會被轉型成為 Int , True 被轉型成 1 而 False 轉型成 0 。
當 Null 型態的時候,會被當成 String ,以 “” (空字串) 表示。
而 Array 和 Object 不能被當成鍵。

另外,新的鍵會覆寫舊的鍵,自動轉型之後的鍵如果和之前的鍵有重複,也會把之前的那組覆寫掉。

ref: PHP: Arrays

所以在使用 MAP 的時候要非常小心被鍵值組被覆寫的問題。

PHPUnit and Dependency Injection

在寫程式的時候,Unit Test 是頗為重要的一環,Working Effectively with Legacy Code 裡面甚至提出了一個概念:「Legacy Code is Code Without Tests」

呃,所以,還是直接看 code 好了…

以下示範 Dependency Injection 的寫法:

以本例來說: foo 類別裡面的 foo_run() 方法是無法被測試的,因為在這個方法裡面有使用 new bar() 建立了一個 $b 物件,並且執行 $b->bar_run() 。我們可以看出來這個 $b->bar_run() 會呼叫 Web Service 。所以當我們執行到 foo_run() 的時候,可能會因為環境的因素而無法測試。

這種程式之間的相依性問題往往會對 Unit Test 造成很大的困擾。如果沒有排除程式之間的相依性,就很容易會讓我們在進行測試時卡住。(除了呼叫 Web Service 以外,也可能是存取資料庫、寫檔案、或是做其他動作。而這些動作可能會因為不同的環境而讓程式無法執行。)。

所以,既然 bar 類別並不在我們需要的測試範圍內,我們就可以透過 Dependency Injection 來降低程式之間的相依性。

假設我們只要測試 foo 類別裡面的 foo_run() 方法,而不想測試 bar 類別的 bar_run() 方法,那可以改採取以下的策略:

這個方式,讓 $bar 物件得以在外面產生,並且注入到 foo 類別中,從此我們可以依照不同的環境建造出不同的 bar 物件。

要建立一個物件,我們至少要寫好幾行 code 來宣告它才會動,而為了測試寫這些 code 就顯得不太聰明,於是 PHPUnit 提供了 Mock Object 的功能。假設我們在 PHPUnit 中要測試上面的 foo 類別,可以採取以下的方法:

另外也可以針對測試環境做一些調整…

我們可以在 foo 類別中,產生一個新的 get_bar() 方法,當要真正要使用 bar 的時候,再呼叫一次 $this->get_bar() 來產生所需要的物件。

假設之前沒有被 $this->set_bar() 做過 Dependency Injection 的話,就會產生預期中的 bar 物件。

以上是簡單的範例介紹,用簡單的方法改寫原本無法被測試的程式 🙂

Plurk 訊息取得時間分布

這是一隻我寫的 Plurk 機器人(註一) 在不同時間差的情況下個別抓的「待回應訊息」 (註二) 分布狀況。

目前共有七隻 fetcher ,第一隻 fetcher 使用 Plurk Real Time Channel API ,其餘六隻 fetcher 使用 Plurk Polling API 並且以延遲 10s, 30s, 60s, 90s, 180s, 240s 的時間差 (offset) 來抓取訊息,圖表的縱軸代表「待回應的訊息」數目,橫軸則是時間差。整個圖表趨勢如果越往左靠,則表示 Plurk 系統對訊息同步的即時性越好,如果越往右靠,表示 Plurk 系統在訊息同步上花的時間越多。

之前在抓取訊息時,我都只抓取一分鐘之內訊息,過期的都不抓,但這樣也造成不少人說機器人沒有回應 (沒回應表示一分鐘內機器人沒抓到使用者發的訊息),以上圖來看,就是以前至少有 30% 的「待回應訊息」我漏抓了。

這種情況下,唯一的解決方法大概就是把時間差 (offset) 再加大。但其實這麼做的幫助很有限,因為使用者通常會因為等太久而自行砍掉沒有回應的訊息,然後機器人這邊則是會因此發生 (plurk not found) 的錯誤。

目前透過 Real Time Channel API 拿到的只佔 34% 左右,有 30% 是 offset 在 10s – 60s 之間拿到,而剩下 35% 則是超過 90s 才拿到。

備註:

  • 這隻機器人的架構設計分為 fetcher 和 replier ,可各別決定要跑幾個 process 起來,目前機器人有 26.8 萬個好友,現在實際狀況是跑七隻 fetcher 和一隻 replier 。
  • 「待回應的訊息」就是拿到的訊息中含有觸發機器人的關鍵字,其餘則為「不回應的訊息」 ,我會記錄每則回應的訊息,並以 fetch_offset 欄位代表是在哪個時間區間的 fetcher 抓到的,統計回應時間差的 SQL 如下

最近機器人的回應狀況不甚理想,我會再觀察一至兩週之後再 update 一下。

PHP 使用 APC 增進執行速度。

其實,如果單純講速度的話,其實 PHP 還真的不夠快,但為什麼其它語言會快呢?其實原因很多,但其實都不脫以下幾項:

1. 程式事先編譯好,而不是要用的時候才用 Interpreter 直譯出結果。
2. 東西能放記憶體的就放記憶體
3. 主機的硬體夠力(這個因素佔很大)

PHP 也有非常多方式可以幫自己加速,這邊我要推薦的是 APC 這個 PHP Extension。

安裝方法大家都會,甚至有些架站包都預設幫你裝好了,所以這邊就不贅述。這邊要提的是 APC 有許多參數可以設置,彈性很大,尤其有個對於加速至關緊要的參數:apc.stat=0
Continue reading “PHP 使用 APC 增進執行速度。”

Python – 文字檔簡繁轉換程式

在 Windows 下面,有個好用的工具叫做 ConvertZ 可以轉換檔案編碼,而且除了轉換編碼以外,它也會自動轉簡體繁體文字,我常常拿這個程式來轉換一些文字檔,像是字幕,說明檔…等等。

最近我家裡的電腦改用 Mac OS X ,發現 Mac OS X 缺少了滿多我生活中必要的小工具程式 (要不然就是要收費) ,所以我就只好自力救濟,寫隻程式來暫解一下。
Continue reading “Python – 文字檔簡繁轉換程式”

MySQL HandlerSocket Plugin for PHP

如果要用 PHP 透過 HandlerSocket 存取 MySQL ,必須先安裝好 php-handlersocket 這個 PHP Extension ,在 Debian Squeeze 安裝 MySQL HandlerSocket Plugin 有提到安裝方法,安裝完之後有以下 function 可以用。

有鑑於 php-handlersocket 提供的 function 使用起來不是很直覺,所以我寫了一個 wrapper 方便使用,以下是範例,首先必須先建立一個資料表:

Continue reading “MySQL HandlerSocket Plugin for PHP”