推荐吳俊瑩的文章

作为一个工科学生,学术水平应该和管理水平同步提高。
吳俊瑩 老师,据我所知,在一个IT公司工作过,同时对公司、项目管理有仔细的思考。
今年以来,读他的文章给我很多启发。

比如 “研發人員應該「拜」王陽明”中首先提到了获取知识的两个方法,看书和问人。之后提出实践,最后讲“在技術的各種領域中,根本的道理有很多還是相通的,新東西永遠學不完,重點應該是自己有沒有把每一件研發工作都 徹底完成,一步一腳印地累積。” 在学术领域,我凭着兴趣接触了各种项目,在很多方面都有自己小小的贡献,但没有适当的总结,写出论文。如果能早早研读吳俊瑩老师的文章,一定会有更好的结果。

吳俊瑩经常在台湾的ITHOME杂志发文。很多文章非常精彩。可惜没有一个索引页面把他所有的文章组织起来。
我就列出几个最近的文章链接吧:

彼得杜拉克如是說
「不聞不問」不算「信任」

在Solaris下安装R

2015-06-16 更新:
这里讲的方法可能已经不管用了,至少在我使用Solaris 10 的VirtualBox Image的时候,下面的方法行不通。

Install Solaris 10 and install R under Solaris
最近的写的一个R package vcf2geno不能在Solaris 10 下编译。
为了解决这个问题,我决定重复CRAN上Solaris 10的测试环境
下面写个流水账

1.安装Virtual Box
下载,安装。

2.安装Solaris 10 和 Guest OS
一定要注意的是手动分区,把/tmp的容量设置到5G以上。不然默认的/tmp只有512M,这样就不方便安装Solaris Studio (第3步)。
安装virtualBox的Guest OS 软件后,Solaris的屏幕分辨率增加。

3.安装Solaris Studio 12.3
下载,解压,安装 (就是特别慢)

4.安装OpenSolaris
Solaris里面没有ubuntu的apt和fedora的yum,但是有个类似的软件叫做OpenCSW Link.
参考OpenCSW的手册Getting Start.
然后用pkgutil安装tetex, gcc4g++, iconv, readline

5.下载R
去R的主页下载源代码,然后解压

6.配置环境参数,安装

export CC=suncc
export CFLAGS="-xO5 -xc99 -xlibmil -nofstore"
export CPICFLAGS=-Kpic
export F77=sunf95
export FFLAGS="-O5 -libmil -nofstore"
export FPICFLAGS=-Kpic
export CXX="sunCC -library=stlport4"
export CXXFLAGS="-xO5 -xlibmil -nofstore -features=tmplrefstatic"
export CXXPICFLAGS=-Kpic
export FC=sunf95
export FCFLAGS=$FFLAGS
export FCPICFLAGS=-Kpic
export LDFLAGS=-L/opt/sunstudio12.1/rtlibs/amd64
export SHLIB_LDFLAGS=-shared
export SHLIB_CXXLDFLAGS=-G
export SHLIB_FCLDFLAGS=-G
export SAFE_FFLAGS="-O5 -libmil"

export CPPFLAGS=’-I/opt/csw/include -I/opt/csw/include/readline’
export LDFLAGS=’-L/opt/sunstudio12.1/rtlibs/amd64 -L/opt/csw/lib’

export PATH=/usr/xpg4/bin:$PATH
export PATH=/usr/sfw/bin/:$PATH

之后我们依次执行:
./configure
gmake #(Solaris下面的GNU make)
gmake install

经验:

    Solaris下自带的软件很少,常用软件要到OpenCSW下载
    Solaris的路径设置和Linux很不同,不在/usr/bin, /usr/local/bin等等。这时候可以用glocate来快速查找, 见【2】

主要参考:
【1】http://cran.r-project.org/doc/manuals/R-admin.html#Solaris
【2】Solaris下的locate工具. Link
【3】http://www.opencsw.org/get-it/packages/

GCC 相关设置(跨平台、不同版本)

查看gcc build-in specs

gcc -dumpspecs
-dumpspecs
Print the compiler's built-in specs---and don't do anything else. (This is used when GCC itself is being built.)

看gcc到哪些目录查找头文件:

gcc -print-search-dirs
-print-search-dirs
Print the name of the configured installation directory and a list of program and library directories gcc will search---and don't do anything else.

This is useful when gcc prints the error message installation problem, cannot exec cpp0: No such file or directory. To resolve this you either need to put cpp0 and the other compiler components where gcc expects to find them, or you can set the environment variable
GCC_EXEC_PREFIX to the directory where you installed them. Don't forget the trailing /.

看gcc编译到那个平台

gcc -Q -v
-Q Makes the compiler print out each function name as it is compiled, and print some statistics about each pass when it finishes.
zhanxw@host10-41.sph.umich.edu: ~/temp> gcc -Q -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/opt/local/libexec/gcc/x86_64-apple-darwin10/4.7.1/lto-wrapper
Target: x86_64-apple-darwin10
Configured with: ../gcc-4.7.1/configure --prefix=/opt/local --build=x86_64-apple-darwin10 --enable-languages=c,c++,objc,obj-c++,lto,fortran,java --libdir=/opt/local/lib/gcc47 --includedir=/opt/local/include/gcc47 --infodir=/opt/local/share/info --mandir=/opt/local/share/man --datarootdir=/opt/local/share/gcc-4.7 --with-libiconv-prefix=/opt/local --with-local-prefix=/opt/local --with-system-zlib --disable-nls --program-suffix=-mp-4.7 --with-gxx-include-dir=/opt/local/include/gcc47/c++/ --with-gmp=/opt/local --with-mpfr=/opt/local --with-mpc=/opt/local --with-ppl=/opt/local --with-cloog=/opt/local --enable-cloog-backend=isl --enable-stage1-checking --disable-multilib --enable-lto --with-as=/opt/local/bin/as --with-ld=/opt/local/bin/ld --with-ar=/opt/local/bin/ar --with-bugurl=https://trac.macports.org/newticket --disable-ppl-version-check --with-pkgversion='MacPorts gcc47 4.7.1_2'
Thread model: posix
gcc version 4.7.1 (MacPorts gcc47 4.7.1_2)

看gcc默认开启了哪些FLAG(定义了哪些macro)

$cpp -dM <(echo "") #define __DBL_MIN_EXP__ (-1021) #define __FLT_MIN__ 1.17549435e-38F #define __DEC64_DEN__ 0.000000000000001E-383DD #define __CHAR_BIT__ 8 #define __WCHAR_MAX__ 2147483647 #define __DBL_DENORM_MIN__ 4.9406564584124654e-324 #define __FLT_EVAL_METHOD__ 0 #define __DBL_MIN_10_EXP__ (-307) #define __FINITE_MATH_ONLY__ 0 #define __DEC64_MAX_EXP__ 384 ... ...

和MacPorts 相关的——如何选择gcc

sudo port select --list gcc
sudo port select --set gcc gcc42 # default gcc in Mac Snow Leopard
sudo port select --set gcc mp-gcc47 # MacPorts GNU GCC 4.7

和平台相关的Macro定义:

其他参考
【1】StackOverflow上的讨论:哪个macro可以区分Mac和Linux Link
【2】SourceForge上Pre-defined C/C++ Compiler Macros,包括以下方面:
Standards
Compilers
Libraries
Operating Systems
Architectures
Devices
Link

Mac Mail 邮件签名的字体

Mac Mail设置邮件签名的时候,签名字体和邮件正文总是无法一致。
比如下图:(之前)

参考:http://apple.stackexchange.com/questions/10530/mail-signature-changes-font-size-when-sent-to-gmail
发现Mac下解决这个问题不难,但也很烦。
把步骤写下来,以备不时之需吧。
1. 退出Mail
2. 进入: ~/Library/Mail/Signatures
3. 用Web Archive Folderizer打开: DBA028E6-8465-492D-B0AF-4B19D3C06450.webarchive (总之是一个奇怪的文件名字)
之后会出现新目录“DBA028E6-8465-492D-B0AF-4B19D3C06450”,里面有一个叫做“localhost”的文件。
4. 用Text Edit打开这个文件,把所有font-size = medium,都改成font-size = 12px 。 因为默认的邮件字体是Helvetica 12。
5. 把改好的文件存为localhost.html
6. 用Safari打开localhost.html,然后存成Webarchive格式,覆盖:DBA028E6-8465-492D-B0AF-4B19D3C06450.webarchive
7. 打开Mail

好了,现在的签名字体和邮件正文的字体一致了。

(之后)

Linux环境下测量程序的内存占用

Measure memory consumption of programs in Linux

在衡量程序性能时不仅需要测量运行时间,还常常需要测量其内存使用。比如如果一个程序比更一个程序快,有可能是以空间换取时间,因此单纯比较运行速度就失去了意义。我们这里演示如何在Linux环境下测量程序的内存占用。

我们先要弄清楚内存的含义:对于运行中的程序而言,它有四个部分:数据,文本,堆,栈(data, text, stack, heap)。对于操作系统而言,所有分配给程序的内存都用虚拟内存表示(Virtual Memory)。对硬件而言,内存分为物理内存(Physical)和交换内存(Swap)。这三种环境的关系有两种:(1)程序内存会被映射到虚拟内存,即虚拟内存中分段对应数据、文本、堆、栈; (2)虚拟内存中某些部分放在物理内存中,另一些放到交换内存中。

为了程序运行有空间上的效率,Linux系统下会共享内存。例如共享程序库(比如libc)或者是内存映射(memory map)。据我的理解,共享部分在程序空间中可以是文本部分,在虚拟内存中放在相同的地址,在硬件中可放在物理内存或交换内存。

对应上面不同的概念,Linux常用的内存相关术语有:
常驻内存(Resident memory), 表示硬件中物理内存的占用。
虚拟内存,Virtual memory,操作系统使用内存的一种抽象。
页(page),操作系统以页为单位管理(分配,回收)内存

测量程序的内存占用可以用不同的统计量。从程序的生命周期来看,可以分为最大(peak)内存占用,即时内存占用,和平均(average)内存占用。从内存的含义来讲,可以在程序本身的语境(context),或是操作系统,或是硬件的语境,比如:栈内存使用(heap memory),虚拟内存占用,物理内存占用。

测量一个程序的内存占用时,最理想的是我们知道(每时每刻)程序的四个部分(在虚拟内存中)分别有多大,对应的,在物理内存或者交换内存中占用了多少空间。但是这是一个很难达到的目标,因为:(1)操作系统以页方式分配内存,很难得到每一个页中具体多少空间被占用;(2)虚拟内存往往比物理内存大很多,当操作系统给一个运行的程序分配内存时总是提供多余程序精确需要的内存;(3)注程序在物理内存的占用加上程序在交换内存的占用构成了程序总的使用情况,但是现有的工具并不直接给出这一数值,需要手动把Resident Memory和Shared Memory相加 。

在实际情况下,通过一些现有的工具,我们往往更关心这几个易于获得统计量:
(1)max resident memory size
可以使用time -v命令(要指定全部路径,否则bash的time命令不识别-v)
注意这个测量值往往远远高于程序运行时消耗的内存。
(这是time 1.7版本的bug,time 报告的内存用量是实际的4倍,因为wait3/wait4返回的实际内存用量,单位是kbyte,但time错把单位设定为page)
本质上time使用了wait3或者wait4命令来获取程序的resources (见 ‘man wait3’)。
使用Python实现时,可以用resources module来获取。
另外,使用这个思路还可以得到程序的运行时间(用户时间,内核时间,实际时间)

(2)top 或者ps
可以查看程序的Resident Size(RES)列,Share Memory(SHR)以及Virtual Memory Size(VIRT),这一统计量默认每秒更新一次。这一方式的缺点是测量值为即时测量,每时每刻这个值都有可能变化。优点是使用起来很方便。
使用Python实现时,可以先使用multiprocesing.Process启动一个程序,然后使用psutil module,构造psutl.Process(),然后使用get_memory_info方式来获取rss,vms

(3)与(2)类似,但为了更多的信息,还可以读取/proc
这是通过读取/proc//status和/proc//smaps来获得程序的内存使用量的。理论上这是最精确的测量。这两个文件的具体格式可以用‘man proc’来获得。
在Python中,需要另外写程序来解释这两个文件,例如下面这样:

zhanxw@amd: ~> cat /proc/15648/status
Name:	takeMem
State:	R (running)
Tgid:	15648
Pid:	15648
PPid:	15163
TracerPid:	0
Uid:	248396	248396	248396	248396
Gid:	248396	248396	248396	248396
FDSize:	256
Groups:	500 1007 1013 1017 1028 1033 248396 
VmPeak:	   12752 kB
VmSize:	   12752 kB
VmLck:	       0 kB
VmHWM:	    1756 kB
VmRSS:	    1756 kB
VmData:	    1112 kB
VmStk:	     136 kB
VmExe:	       4 kB
VmLib:	    3232 kB
VmPTE:	      40 kB
VmSwap:	       0 kB
Threads:	1

在实践中,可以使用已有的程序,例如tmem【4】,以及lh3的runit【6】(下载udp,在runit/下,用make编译)。输出如下。

tmem

./tmem ./takeMem 1000000000
	    3908		    3908	      84	      84
Allocating 1000000000 memory
	  988336		  988336	  148012	  148012
	  988336		  988336	  308524	  308524
	  988336		  988336	  442636	  442636
	  988336		  988336	  567244	  567244
	  988336		  988336	  686308	  686308
	  988336		  988336	  796396	  796396
	  988336		  988336	  889588	  889588
	  988336		  988336	  949516	  949516
runit

./runit ~/mycode/smake/takeMem 1000
Allocating 1000 memory
-- CMD: ./runit /net/fantasia/home/zhanxw/mycode/smake/takeMem 1000

-- totalmem     198345252.000 kB
-- available    190966956.000 kB
-- free           1569216.000 kB

-- retval                   0
-- real                15.749 sec
-- user                15.350 sec
-- sys                  0.170 sec
-- maxrss             704.000 kB
-- avgrss             704.000 kB
-- maxvsize         11904.000 kB
-- avgvsize         11904.000 kB

参考资料:
【1】Python resource 模块
【2】Python os 模块,介绍wait3,wait4
【3】Python multiprocessing模块,介绍了Process这个易于使用的类,与subprocess相似,但有更灵活的用法
【4】Measuring Memory Usage,介绍了用C来读取/proc//status的程序,并给出tmem的下载链接
【5】Understanding memory,非常详细的介绍Linux内存分配,优化
【6】Heng Li’s personal website 可以下载比较hash library的程序,里面有一个runint/文件夹,编译之后可以用runit来测量内存使用

Embedding Python in C/C++

将Python嵌入C/C++

首先嵌入和扩展是相关联但不同的两个概念:嵌入是指在C语言中使用Python,而扩展是在Python中使用C语言(以library的形式)。

其次嵌入Python的本质是嵌入Python 解释器(Interpretor)。因此我们需要调用相应的Initialize, Finalize函数。另外,为了让C/C++识别Python相关的函数,我们还需要#include 以及相应的linker options:-Xlinker -export-dynamic -ldl -lrt -lutil

另外,Python语句有两种:statement 和 expression。注意statement是没有返回值的。因此Python语言里有exec和eval分别对应这两种情形。最本质的区别是statement有副作用(side effect),比如会把值绑定到一个名称上,比如: a = 3 。当我们用PyRun_SimpleString(“a=3”)时,这种副作用是在当前的environment下(内部实现是dict)多出一个变量(dict的key),名称是a。

此外要注意Python的执行代码是和environment相关的,比如global和local,想用的函数名称(例如:dir,str,print),变量都是保存在各自的environment里。平时我们写的 if name == "__main__" 就是说默认的环境是在模块__main__里面。我们要取出默认的函数或者变量(Python内部不严格区分这两个概念,知识函数可以callable),可以用下面的代码(以取出dir函数为例):

    PyObject* main_module =
        PyImport_AddModule("__main__");

// Get the main module's dictionary                                                                                                                                                  
// and make a copy of it.                                                                                                                                                            
    PyObject* main_dict =
        PyModule_GetDict(main_module);
    //  pFunc is also a borrowed reference 
    PyObject* pFunc = PyDict_GetItemString(pDict, "dir");

嵌入Python还应注意内存的使用。因为Python主要使用(另一种是Python的malloc, free)Reference count方式来管理新的变量,我们应记住在获得一个PyObject*类型的指针之后,用Py_DECREF或者Py_XDECREF来减少reference count (注意,特殊情况下取出的结果是不能减少reference count的,比如取出list中某个元素)。

最后给一个例子来说明怎么在Python里计算任何表达式expression (参考了FAQ[1])

#include <Python.h>
                                                                                                                                                                                     
double checkExpression(const char* formular, double gq, double dp) {
    Py_Initialize();

    // Get a reference to the main module.                                                                                                                                               
    PyObject* main_module =
        PyImport_AddModule("__main__");

    // Get the main module's dictionary                                                                                                                                                  
    // and make a copy of it.                                                                                                                                                            
    PyObject* main_dict =
        PyModule_GetDict(main_module);

    char s[1024];
    sprintf(s, "GQ=%lf", gq); //, dp, formular);                                                                                                                                     
    if ( 0 != PyRun_SimpleString(s) ) { // something wrong happen!                                                                                                                   
        fprintf(stderr, "\nSomething wrong in assigning GQ\n");
        return -1.;
    }
    sprintf(s, "DP=%lf", dp);
    if ( 0 != PyRun_SimpleString(s) ) { // something wrong happen!                                                                                                                   
        fprintf(stderr, "\nSomething wrong in assigning DP\n");
        return -1.;
    }

    PyObject* ret = PyRun_String(formular, Py_eval_input, main_dict, main_dict);
    if (ret == NULL) {
        Py_XDECREF(ret);
        PyErr_Clear();
        return -1.;
    };
    double res;
    if (PyInt_Check(ret)) {
        res = PyLong_AsLong(ret);
    } else if (PyFloat_Check(ret)) {
        res = PyFloat_AS_DOUBLE(ret);
    } else if (PyBool_Check(ret)) {
        res = ret == Py_True;
    }
    Py_XDECREF(ret);
    Py_Finalize();
    return res;
};

重要参考资料

【1】 扩展/嵌入Python的FAQ Extending/Embedding FAQ

【2】API 手册 Python/C API Reference Manual

【3】嵌入Python的流程性说明 Embedding Python in Another Application

【4】扩展Python的流程性说明,这里介绍了Python底层的知识,这些知识不会在”嵌入Python的流程性说明”中重复出现 Extending Python with C or C++

iTerm2和Emacs的Meta Key

How to set up Meta appropriately for iTerm2

学校的电脑是Mac,Mac下最好的terminal是iTerm2;我用的编辑器是Emacs,但Terminal下的Emacs不能正常工作,比如M+/是我设置的hippie-expand,我不能用Alt/Command + /按出来,一定要用Esc+/ 。
被困扰了好久之后,我终于下定决心彻底解决这个问题。
NOTE:Alt/Command 指的是一个按键,在windows键盘上是Alt,Mac键盘上叫做Command,上面有一个四瓣的小花(Apple Clove)

首先要弄清楚流程:
1. 我按的键能不能传入iTerm2?
2. iTerm2如何把我的击键传到远处的Linux Terminal?
3. 远端的Terminal里的Emacs能不能正确解析击键?

1. 我按的键能不能传入iTerm2?

在Mac系统,一些组合键还真不一定能传到iTerm2,举个例子:Alt/Command + / ,这个是iTerm2里Find Cursor的Shortcuts,就是我一按这个组合键,就执行Find Cursor的功能,iTerm里的shell是收不到我的按键的。
解决方法:详细见这个邮件列表 http://groups.google.com/group/iterm2-discuss/browse_thread/thread/e960f5098ff3c4a0
就是增加一个系统级别的设置,把Find Cursor的组合键Overwrite掉。

顺便说,我顺便发现一个工具ShiftIt,就是用组合键安排窗口,比如:把窗口最大化,放在左边1/2,右边1/2…… 非常顺手。

2. iTerm2如何把我的击键传到远处的Linux Terminal?

下面是假设我按下键了,iTerm2会把我的击键传到Linux Terminal上。注意这个过程并不简单,因为我的按键会被remap,也会被改动。
举例来说,如果我按下Alt/Command,如果有remap(比如在iTerm2里设置了,或者用Double Command等设置了Remap),实际打出的可能是Option(就是Windows键盘上的Win键)。
还有一种可能是我的击键是Alt/Command,但Linux Terminal不认识Alt/Command,只有强制iTerm2把Alt/Command输出成+Esc 才成。

简单来说,在iTerm2 针对每个Session的设定里,在设定keys时,选择Preset xterm;然后把左右Option Key都选成+Esc。
另外在Linux Terminal中.inputrc文件中加入:
set input-meta on
set output-meta on
set convert-meta off

更多资料,参见在iTerm2里设置Emacs 和 paredit以及 iTerm2官方讲Keybinding的wiki

其中官方wiki指出了一个技巧,Ctrl+V,然后击键,可以看出到底快捷键具体是什么,比如Ctrl+v,然后按Esc,就会出现:”^[“, 说明Esc对应 ^[

3. 远端的Terminal里的bash/Emacs能不能正确解析击键?

Bash 常用的一个功能是backward-kill-word,就是说用快捷键向前删除单词,一般可以用Ctrl+W,还有一种常用的快捷键是Alt+Backspace。比如在iTerm2里可以Remap Alt+Backspace,但是怎么才能输入Alt+Backspace呢?直接输入时,Backspace会删除上一个字符,这时候通过查ASCII表,可以看到Hex 0x17对应backspace,那么我在iTerm2里设置0x17就可以了。

现在在Terminal 方式的Emacs里面(emacs -nw方式),应该可以用Option来代替Meta键,比如Meta+/可以用Option+/来输入。但如果我还是习惯用Alt/Command+/怎么办?
为了不在Emacs里设置,可以在iTerm2中设置 Alt/Command + / 为发送Escaped sequenced: ^[ /

假设我们按了快捷键,只有要Emacs事先知道快捷键对应什么功能才行。(例如可以用Ctrl+h k来看我们输入的快捷键对应的功能)。

经过以上三步,现在如果我需要在Emacs里输入Alt/Command + /, 可以按Esc+/ , Alt/Command + /, 或Option + / .
当然好处不止如此,比如在Emacs里各个窗口移动,可以很方便的用Shift+ArrowKey; 在一个文档快速移动,可以用Ctrl+ArrowKey。当然最大的好处是可以用Option作为Meta,以后按键就方便多了。

2016-12-09补充
如果在GNU screen里用Emacs,需要在.screenrc里加入这两行:
term xterm
c1 off

这是为了确保GNU screen不会改变键盘上输入的Escape sequence。
换句话说,可以保证“Shift+向右的箭头”可以从键盘送到Emacs程序。

Valgrind 查内存错误的利器

Valgrind – a cool tool to check memory related problems

Valgrind是非常有用的检查内存相关问题的工具。比如: 内存泄漏,double free memory,内存非法访问。基本上Segmentation fault都能用Valgrind查出来。我刚刚查出了一个很刁钻的bug,在找bug的过程中发现valgrind非常有用,但要用好,还需要点技巧。

先描述一下问题:

自己的程序总是Segmentation Fault。我先用Valgrind运行,重要结果如下:


==11060== Invalid write of size 8
==11060== at 0x44FF07: FileReader::FileReader() (in /net/nfsb/dumbo/home/zhanxw/smallTool/BamPileup)
==11060== by 0x410D05: BufferedReader::BufferedReader(char const*, int) (IO.h:232)
==11060== by 0x41126C: LineReader::LineReader(char const*) (IO.h:332)
==11060== by 0x41047A: RangeList::addRangeFile(char const*) (RangeList.cpp:128)
==11060== by 0x405B28: main (BamPileup.cpp:258)
==11060== Address 0x75fc2e8 is 0 bytes after a block of size 40 alloc'd
==11060== at 0x4C27CC1: operator new(unsigned long) (vg_replace_malloc.c:261)
==11060== by 0x411252: LineReader::LineReader(char const*) (IO.h:332)
==11060== by 0x41047A: RangeList::addRangeFile(char const*) (RangeList.cpp:128)
==11060== by 0x405B28: main (BamPileup.cpp:258)
==11060==

因为我的BufferedReader包含FileReader类,我最开始的几个思路:
1. 自己的code有bug
BufferedReader 和 FileReader都是自己写的,用过很多次没有问题,这次出现Valgrind报错在IO.h:232,因此反复检查了那段代码。
2. 怀疑link有问题的library
重新编译整个code多次。

但是问题依旧,后来给Valgind 这几个参数 –show-reachable=yes –leak-check=full ,再重新运行:

==11908== Invalid write of size 8
==11908== at 0x4584BB: FileReader::FileReader() (BgzfFileTypeRecovery.cpp:239)
==11908== by 0x4106B5: BufferedReader::BufferedReader(char const*, int) (IO.h:232)
==11908== by 0x410C28: LineReader::LineReader(char const*) (IO.h:332)
==11908== by 0x40FE2A: RangeList::addRangeFile(char const*) (RangeList.cpp:128)
==11908== by 0x4054D8: main (BamPileup.cpp:258)
==11908== Address 0x75fc2e8 is 0 bytes after a block of size 40 alloc'd
==11908== at 0x4C27CC1: operator new(unsigned long) (vg_replace_malloc.c:261)
==11908== by 0x410C0E: LineReader::LineReader(char const*) (IO.h:332)
==11908== by 0x40FE2A: RangeList::addRangeFile(char const*) (RangeList.cpp:128)
==11908== by 0x4054D8: main (BamPileup.cpp:258)
==11908==

这次一下发现原来是我link别人代码的时候,我们都有一个类叫做FileReader,编译器把错误的FileReader代码链接给我,所以把程序搞崩溃了。

总结一下,要是:
1. 自己一下子就用到Valgrind的这些参数
2. 链接别人的代码前先测试一下,然后就能把问题的原因归于新加入的代码

可惜没那么多“要是”,以此文纪念一下刚刚过去的3个小时。

Makefile技巧三则

Three Tricks to use Makefile Efficiently

最近读Protocol Buffer for C,发现一些Makefile的技巧

1. -MMD flag
Makefile比较麻烦的是写dependency。常用技巧是通过g++ -MM和sed脚本从.cpp源文件生成.d文件,这种方式可以解决自动生成依赖关系的问题,但其实有更好的方法:
g++ -MMD -c XXX.c
之后在Makefile里添加:
-include XXX.d

g++的文档提到-MMD:

-MMD
    Like -MD except mention only user header files, not system header files. 

除了-MMD外,有意思的g++参数,可以参见gcc flags 。另外这篇详细讲解Makefile dependency的文章Makefile Autodependency

2. Order-only prerequisites
order-only-prerequisites
用Makefile里的例子来说:

     OBJDIR := objdir
     OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)
     
     $(OBJDIR)/%.o : %.c
             $(COMPILE.c) $(OUTPUT_OPTION) $<
     
     all: $(OBJS)
     
     $(OBJS): | $(OBJDIR)
     
     $(OBJDIR):
             mkdir $(OBJDIR)

$(OBJS)需要$(OBJDIR),所以需要把$(OBJS)放在$(OBJDIR)前面;但在建立$(OBJDIR)之后,$(OBJ)就不在需要根据$(OBJDIR)的时间调整了。

3. Multiple line variables (Eval function)
Makefile里的变量可以有多行,这种变量要用define来定义。
使用这种变量有两种场合:(1)Canned Recipes; (2)Eval-Function

第一种就是把常用的一组命令包装在一起,比如:

     define run-yacc =
     yacc $(firstword $^)
     mv y.tab.c $@
     endef

之后调用:

     foo.c : foo.y
             $(run-yacc)

第二种更加常用,比如下面的例子:

     PROGRAMS    = server client
     
     server_OBJS = server.o server_priv.o server_access.o
     server_LIBS = priv protocol
     
     client_OBJS = client.o client_api.o client_mem.o
     client_LIBS = protocol
     
     # Everything after this is generic
     
     .PHONY: all
     all: $(PROGRAMS)
     
     define PROGRAM_template =
      $(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
      ALL_OBJS   += $$($(1)_OBJS)
     endef
     
     $(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
     
     $(PROGRAMS):
             $(LINK.o) $^ $(LDLIBS) -o $@
     
     clean:
             rm -f $(ALL_OBJS) $(PROGRAMS)

首先上面注释行以下都是通用(generic)的。
其次eval会两次展开(expand)变量名,所以$(1)会被扩展成prog(比如server),$$($(1)_OBJS)会被扩展成$(server_OBJS),之后会再次被扩展成server.o server_priv.o server_access.o

最精彩的地方是如何把上面3个技巧联合起来。下面看一下cloudwu的Protocol Buffer for C项目的Makefile片段:

BUILD = build

LIBSRCS = context.c varint.c array.c pattern.c register.c proto.c map.c alloc.c rmessage.c wmessage.c bootstrap.c stringpool.c
LIBNAME = libpbc.$(SO)

TESTSRCS = addressbook.c pattern.c
PROTOSRCS = addressbook.proto descriptor.proto

BUILD_O = $(BUILD)/o

all : lib test

lib : $(LIBNAME)

clean :
	rm -rf $(BUILD)

$(BUILD) : $(BUILD_O)

$(BUILD_O) :
	mkdir -p $@

LIB_O :=

define BUILD_temp
  TAR :=  $(BUILD_O)/$(notdir $(basename $(1)))
  LIB_O := $(LIB_O) $$(TAR).o
  $$(TAR).o : | $(BUILD_O)
  -include $$(TAR).d
  $$(TAR).o : src/$(1)
	$(CC) $(CFLAGS) -c -Isrc -I. -fPIC -o $$@ -MMD $$<
endef

$(foreach s,$(LIBSRCS),$(eval $(call BUILD_temp,$(s))))

$(LIBNAME) : $(LIB_O)
	cd $(BUILD) && $(CC) --shared -o $(LIBNAME) $(addprefix ../,$^)

有意思的地方是BUILD_temp,将每一个$(1)的值赋给TAR,之后$(TAR)在$(BUILD_O)目录下编译,编译时的依赖关系被-include到Makefile内部(include前的减号,-,表示要include的文件即使不存在也不报错),同时把编译好的$(TAR).o加入到LIB_O变量中(+=赋值)。

使用Varnish绕过学校防火墙

Use varnish to bypass school proxy (rewrite URL to bypass port blocking).

学校的防火墙规则很严格,除了80,443等常用端口外,不允许特殊端口的通信(就连git使用的9418端口也被block过)。
可以我的网站2812端口有monit服务,我常常想从学校的电脑访问 ,看看自己网站的是不是工作正常。为了解决这个问题,我发现可以用Varnish的rewrite 功能。

原理上讲,Varnish收到一个URL request,会调用vcl_recv函数 (可能是callback机制),然后根据header中的URL来分析如何具体处理,比如可以去掉header,对mobile的browser agent用mobile版的web server,还可以用不同的backend服务器,这个就是用这个功能。

先把相应的配置放上来:
default.vcl中先添加一个后台服务器:

backend monit {
.host = "localhost";
.port = "2812";
}

在sub vcl_recv函数中增加对/monit请求的重写(rewrite),就是说每次访问xxx.com/monit自动翻译成xxx.com/, 然后用backend monit(就是上面的localhost:2812服务器)来服务:


if (req.url ~ "^/monit"){
set req.url = regsub(req.url, "^/monit", "/");
set req.url = regsub(req.url, "^//", "/");
set req.backend = monit;
}

通过上面的设置,就可以在学校里,用80端口来访问服务器端2812端口的内容了。
注意,默认monit会使用这样的链接: xxx.com/nginx
这种情况下,varnish不会用backend monit来服务,我们需要手动访问:xxx.com/monit/nginx
现在还没有想到如何解决这种不方便。

后话1:

Varnish是一个非常强大的proxy,用它内置的VCL语言,可以很灵活、高效(Varnish内部把VCL编译成C语言)处理各种类型的HTTP request。比如round-robin式的负载均衡。最常用的两个函数是vcl_recv (表示varnish接收到browser的请求) 和vcl_fetch(表示varnish已经从后台服务器取得数据)。更多的用法可以见:

后话2:

Varnish不会像Apache/Nginx把log写到磁盘。如需要检查log,可以用Varnish自带的varnishlog。
比如下面的:

   12 SessionOpen  c 76.235.186.40 34862 :80
   12 ReqStart     c 76.235.186.40 34862 911966818
   12 RxRequest    c GET
   12 RxURL        c /monit
   12 RxProtocol   c HTTP/1.1
   12 RxHeader     c Host: zhanxw.com
   12 RxHeader     c User-Agent: Mozilla/5.0 (Ubuntu; X11; Linux x86_64; rv:8.0) Gecko/20100101 Firefox/8.0
   12 RxHeader     c Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   12 RxHeader     c Accept-Language: en-us,en;q=0.5
   12 RxHeader     c Accept-Encoding: gzip, deflate
   12 RxHeader     c Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
   12 RxHeader     c Connection: keep-alive
   12 RxHeader     c Cookie: __utma=134065221.936534807.1323323714.1323329816.1323533189.3; __utmz=134065221.1323323714.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmc=134065221
   12 RxHeader     c Authorization: Basic YWRtaW46eGlhb3dlaXhpYW95aQ==
   12 RxHeader     c Cache-Control: max-age=0
   12 VCL_call     c recv
   12 VCL_return   c pass
   12 VCL_call     c hash
   12 VCL_return   c hash
   12 VCL_call     c pass
   12 VCL_return   c pass
   14 BackendOpen  b monit 127.0.0.1 37258 127.0.0.1 2812
   12 Backend      c 14 monit monit
   14 TxRequest    b GET
   14 TxURL        b 
   14 TxProtocol   b HTTP/1.1
   14 TxHeader     b Host: zhanxw.com
   14 TxHeader     b User-Agent: Mozilla/5.0 (Ubuntu; X11; Linux x86_64; rv:8.0) Gecko/20100101 Firefox/8.0
   14 TxHeader     b Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   14 TxHeader     b Accept-Language: en-us,en;q=0.5
   14 TxHeader     b Accept-Encoding: gzip, deflate
   14 TxHeader     b Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
   14 TxHeader     b Cookie: __utma=134065221.936534807.1323323714.1323329816.1323533189.3; __utmz=134065221.1323323714.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmc=134065221
   14 TxHeader     b Authorization: Basic YWRtaW46eGlhb3dlaXhpYW95aQ==
   14 TxHeader     b X-Forwarded-For: 76.235.186.40
   14 TxHeader     b X-Varnish: 911966818
   14 RxProtocol   b HTTP/1.0
   14 RxStatus     b 400
   14 RxResponse   b Bad Request
   14 RxHeader     b Date: Sat, 10 Dec 2011 17:58:50 GMT
   14 RxHeader     b Server: monit 5.2.1
   14 RxHeader     b Content-Type: text/html
   14 RxHeader     b Connection: close
   12 TTL          c 911966818 RFC 120 1323539930 0 0 0 0
   12 VCL_call     c fetch
   12 VCL_return   c pass
   12 ObjProtocol  c HTTP/1.1
   12 ObjStatus    c 400
   12 ObjResponse  c Bad Request
   12 ObjHeader    c Date: Sat, 10 Dec 2011 17:58:50 GMT
   12 ObjHeader    c Server: monit 5.2.1
   12 ObjHeader    c Content-Type: text/html
   14 Length       b 201
   14 BackendClose b monit
   12 VCL_call     c deliver
   12 VCL_return   c deliver
   12 TxProtocol   c HTTP/1.1
   12 TxStatus     c 400
   12 TxResponse   c Bad Request
   12 TxHeader     c Server: monit 5.2.1
   12 TxHeader     c Content-Type: text/html
   12 TxHeader     c Content-Length: 201
   12 TxHeader     c Date: Sat, 10 Dec 2011 17:58:50 GMT
   12 TxHeader     c X-Varnish: 911966818
   12 TxHeader     c Age: 0
   12 TxHeader     c Via: 1.1 varnish
   12 TxHeader     c Connection: keep-alive
   12 Length       c 201
   12 ReqEnd       c 911966818 1323539930.287971973 1323539930.288527966 0.000089884 0.000468969 0.000087023
   12 Debug        c "herding"

其中最重要的是第3列: b 表示 “backend”,就是varnish和backend端的通信; c 表示 “client”,就是varnish和client端(www browser)之间的通信。

第2列中, VCL_call 表示varnish内部哪个函数被调用,VCL_return则是调用结果是什么。比如:VCL_call retch ,表示VCL配置文件中vcl_fetch函数被调用; VCL_return pass,表示return pass,同样也是VCL配置文件中的语句。