2013年12月29日 星期日

在 Ubuntu 下用 gdb debug CPython

用法和其它 library 稍微不同, 安裝 python-dbg 後, 是提供另一個含有 debug symbol 的 python 執行檔 python2.7-dbg, 而不是 gdb 自己另外載入 python 的 debug symbol。

可從 dpkg -L python-dbg 的結果得知這事:

$ dpkg -L python2.7-dbg | grep bin/python
/usr/lib/debug/usr/bin/python2.7-gdb.py
/usr/lib/debug/usr/bin/python2.7
/usr/bin/python2.7-dbg
/usr/bin/python2.7-dbg-config
/usr/lib/debug/usr/bin/python2.7-dbg-gdb.py
$ nm /usr/bin/python2.7
nm: /usr/bin/python2.7: no symbols
$ nm /usr/bin/python2.7-dbg | head
                 U ASN1_INTEGER_get@@OPENSSL_1.0.0
                 U ASN1_STRING_data@@OPENSSL_1.0.0
                 U ASN1_STRING_length@@OPENSSL_1.0.0
                 U ASN1_STRING_to_UTF8@@OPENSSL_1.0.0
                 U ASN1_TIME_print@@OPENSSL_1.0.0
                 U ASN1_item_d2i@@OPENSSL_1.0.0
00000000008857e0 d AST_type
0000000000967588 b Add_singleton
00000000009675e8 b Add_type
0000000000967560 b And_singleton

實際執行的結果:

$ gdb --args python2.7-dbg -c 'import time; time.sleep(10)'
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
Reading symbols from /usr/bin/python2.7-dbg...done.
(gdb) r
Starting program: /usr/bin/python2.7-dbg -c import\ time\;\ time.sleep\(10\)
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
^C
Program received signal SIGINT, Interrupt.
0x00007ffff69ad003 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:82
82      ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) bt
#0   __select_nocancel at ../sysdeps/unix/syscall-template.S:82
#1   floatsleep at ../Modules/timemodule.c:943
#2   time_sleep at ../Modules/timemodule.c:206
#3   PyCFunction_Call at ../Objects/methodobject.c:81
#4   call_function at ../Python/ceval.c:4021
#5   PyEval_EvalFrameEx at ../Python/ceval.c:2666
#6   PyEval_EvalCodeEx at ../Python/ceval.c:3253
#7   PyEval_EvalCode at ../Python/ceval.c:667
#8   run_mod at ../Python/pythonrun.c:1353
#9   PyRun_StringFlags at ../Python/pythonrun.c:1316
#10  PyRun_SimpleStringFlags at ../Python/pythonrun.c:969
#11  Py_Main at ../Modules/main.c:583
#12  main at ../Modules/python.c:23

順便備忘一下相關的文章: Debugging C/C++ and CPython using GDB 7′s new Python extension support 提到如何在 gdb 內使用 python extension 來 debug python code, 可以從 gdb 內看到 C 函式對回去 python script 的檔名和行數。

2013年12月25日 星期三

了解 2D Graphics Library 的基本知識

有很多套不同的 2D Graphics Library, 像是 Linux 下的 Cairo, OS X 和 iOS 下的 Quartz 2D 還有跨平台的 Skia, 但它們的概念應該差不多, 若看不懂其中一套, 可以參考別套的文件再回頭理解 (特別是 iOS 的文件最齊)。這篇記錄看了那些文件協助我了解 2D Graphics Library 的基本知識。

Cairo Tutorial

Cairo Tutorial 說明 Cairo 的架構, 配合圖示和簡短的範例程式, 相當實用。

Cairo 的架構分成: source, mask, destination, context。可以想像 destination 是一塊畫布 (pixel buffer), 可能對應到 bitmap、螢幕、PDF 或印表機。使用 Cairo API 設定好 source (如顏色、漸層模式 (gradien) 或另一個畫布) 和 mask, 再決定繪圖的動作, 就會將 source 經過 mask 的結果複製到 destination。

context 管理目前設定的各種屬性, 若有用過 OpenGL 或其它繪圖函式庫, 應該不會對 context 的概念感到陌生。繪製的動作會參考 context 內設定的屬性。

整體來說, 使用 2D Graphics Library 就是先產生一個 pixel buffer (Cairo 稱為 cairo_surface_t), 然後產生一個包含 pixel buffer 的 context (Cairo 稱為 cairo_t)。接著設定 context 屬性、source、mask, 然後決定繪圖動作 (如 fill、stroke)。

Path

Cairo Tutorial 有介紹 path 是什麼。Cocoa with Love: 5 ways to draw a 2D shape with a hole in CoreGraphics 提到如何用 path 畫出不同的填色效果。用同一個例子的不同作法說明為何需要 non-zero winding ruleeven-odd rule

Blending

參考 Porter/Duff Compositing and Blend Modes 了解各種 blend mode 的計算方式和圖例, 另外 CGContext reference 裡的 CGBlendMode 有同樣的說明。表中沒有 "source over", 因為 kCGBlendModeNormal 的效果就是 source over。

2013年12月21日 星期六

在 Chrome DevTools 修改 CSS 並同步回檔案裡

Chrome DevTools 最近的新功能, 參考 Chrome DevTools Revolutions 2013 - HTML5 Rocks, 作法如下:

  1. DevTools: settings -> workspace, 點選 add folder 加入開發用的目錄。允許 Chrome 讀寫該目錄下的檔案。
  2. 開啟網址 (本機或網路上的), 在 DevTools 的 source panel 找到要同步存檔的檔案, 按右鍵選 Map to Network Resource。
  3. 重新載入該頁。

注意:

  • 即使直接開啟 workspace 目錄內的檔案, 仍需要執行 "Map to Network Resource" 該步, 沒作這步讓我卡了一陣子 ...。
  • 只能同步存外部 CSS 檔, html 本體無法同步存檔, 仍得自己按 Ctrl+S。

成功後改網頁就快多啦, 在 html 內寫好骨架, 幫主要元件加上 class, 之後就在 DevTools 內選 tag, 直接在 class 內加 CSS rules, 調整參數。調整的當下就會存回檔案了, 真是超爽的啦。

備註: 依官方的說明, 也可以同步 Sass。

Mobile Safari 和 Chrome on Android 上的 1 pixel 間距

參考 CSS Tip: How to Prevent Div Seam Lines from Appearing in Apple's Mobile Safari | Oddo Design, 解法很簡單:

position: relative;
top: 1px;

似乎是 webkit 的 bug, 搜 "mobile safari 1 pixel gap" 可看到很多討論。

position: relative 真好用, 不只可以用來疊底, 加裝飾, 也可以 workaround 瀏覽器的 bug。

2013年12月15日 星期日

網頁顯示順序的規則: stacking context

瀏覽器在繪出網頁內容時, 並不是單純在 CSS 的 z-index 排序。而是依 stacking context。

有兩篇很棒的說明文章:

2013年12月1日 星期日

g++ 最佳化 switch-case 的一個小例子

最近看到一段程式如下:

bool isOkay(Type type) {
  switch (type) {
    case A:
    case C:
    case D:
    case E:
    case G:
    case I:
    case K:
    case L:
    case Y:
      return false;
    default:
      ;
  }
  return true;
}

原本想說 switch-case 和一串 if 沒差太多, 這樣寫應該比 set 慢。畢竟從演算法的角度來看, 前者是線式比對, 複雜度和 type 特例的數量成正比, 而 set 保證是 O(1)。

後來經 command 提醒, 想說搞不好 compiler 有做最佳化, 還是來看產生的組語好了。結果 compiler 真的有做最佳化, 從組語來看, switch 不見得會比較慢。觀察用的程式見這裡, 用 g++ -O2 產生的組語見這裡

結論是:

  • 寫成「一串 if」, 結果的確是一串 cmp 和 je, 複雜度如原本預期。
  • 但是 switch 讓 compiler 知道比較同一個變數, compiler 有機會做最佳化, 變成少數幾個計算和比較。
  • 使用 set 有額外呼叫函式的成本, 對於簡單計算的例子, 是不可忽略的額外成本。

使用 -O2 後, set 的版本有些複雜, 沒辦法藉由「大概看看+腦補推測」搞懂。實測的結果如下:

$ g++ -O2 benchmark_switch_cases_check.cpp -DSWITCH; time ./a.out;
1700000000

real    0m6.398s
user    0m6.396s
sys     0m0.000s
$ g++ -O2 benchmark_switch_cases_check.cpp -DALOTIF; time ./a.out
1700000000

real    0m8.392s
user    0m8.389s
sys     0m0.000s
$ g++ -O2 benchmark_switch_cases_check.cpp -DIF; time ./a.out
1700000000

real    0m8.402s
user    0m8.389s
sys     0m0.008s
$ g++ -O2 benchmark_switch_cases_check.cpp ; time ./a.out
1700000000

real    0m10.092s
user    0m10.089s
sys     0m0.000s

所花時間是竟然是 switch < if < set。

演算法分析可以去掉明顯的壞主意, 但是 compiler 做了太多事, 單就演算法分析不足以推斷出實務上的好壞, 果然還是需要實測來最佳化效能。

在 Fedora 下裝 id-utils

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