Archive for October, 2008
Python中inner function的binding处理
BBS上的一个帖子,问题是
def a():
def b():
x += 1
x = 1
print "a: ", x
b()
print "b: ", x
def c():
def d():
x[0] = [4]
x = [3]
print "c: ", x[0]
d()
print "d: ", x[0]
运行a()会报UnboundLocalError: local variable ‘x’ referenced before assignment
但是运行c()会正确地显示3和4。
原因在于原因在于CPython实现closure的方式和常见的functional language不同,采用了flat closures实现。
“If a name is bound anywhere within a code block, all uses of the name within the block are treated as references to the current block.”
在第一个例子中,b函数x += 1对x进行赋值,rebind了这个对象,于是Python查找x的时候只会在local environment中搜索,于是就有了UnboundLocalError。
换句话说,如果没有修改这个值,比如b中仅仅简单的输出了x,程序是可以正常运行的,因为此时搜索的范围是nearest enclosing function region。
而d方法并没有rebind x变量,只是修改了x指向的对象的值而已。如果把赋值语句改成x = [4],那么结果就和原来不一样了,因为此时发生了x的rebind。
所以Python中的closure可以理解为是只读的。
另外第二个例子也是这篇文章中提到的一种workaround:把要通过inner function修改的变量包装到数组里,然后在inner function中访问这个数组。
至于为什么Python中enclosed function不能修改enclosing function的binding,文中提到了主要原因还是在于Guido反对这么做。因为这样会导致本应该作为类的实例保存的对象被声明了本地变量。
Programming Language Pragmatics 笔记 [1]
这本书貌似蛮浅的,不过某些细节讲得很详细
1. 大部分函数式语言的局部变量具有非受限的生存期
2. 早期Fortran编译器比C编译器更快的原因之一就是C中的指针具有引进别名的倾向,而近年的别名分析算法才使C编译器生成的代码的效率有所提高。C99标准引入了一个新关键字restrict,附在一个指针声明上说明该指针指向的对象在当前scope内没有别名。
[292/899]
Live Code Update in Erlang
http://bc.tech.coop/blog/070713.html
Joe Armstrong的原话 Live code upgrading is one of the things Erlang was designed for.
一个最简单的例子
loop(Fun, State)
receive
{From, {rpc, Tag, Q}} ->
{Reply, State1} = Fun(Q, State),
From ! {Tag, Reply},
loop(Fun, State1);
{code_upgrade, Fun1} ->
loop(Fun1, State)
end.
使用时只要发送一个{code_update, Fun1}消息即可
在实际的项目中,live code update需要在一个进程的稳定状态中进行。好在有Erlang天生的分布式特性,结点i进行更新时其他结点可以接管这个结点的任务,直到结点i更新成功,再进行下一个结点的更新。这个形式和结点的crash处理有点类似。于是Erlang中live updating的实际难点在于
1. How to suspend the system and put it into a stable state
2. How to replicate the stable state
3. How to restart from a stable state
4. How to detect failure
5. How to upgrade and downgrade the stable state
另外http://www.erlang.org/doc/man/code.html#12介绍了Erlang中的Code Server模块。
Erlang的Hello World:一个计数程序
acm queue 9月的杂志的主题是The Concurrency Problem,力推了Erlang这个语言,其中有篇文章简单的介绍了下这个message-oriented语言。
查了下这个名字的读法,正确的读法应该是air-lang,这里元音a的发音和bang中的a一样。
文章中的第一个程序就有点令人费解,主要原因在于Erlang的语法和一般的imperative language差别很大,和functional language比较类似,但是本质上也有很大的不同。
以Java的一个计数程序为例
// A shared counter.
public class Sequence {
private int nextVal = 0;
// Retrieve counter and increment.
public synchronized int getNext() {
return nextVal++;
}
// Re-initialize counter to zero.
public synchronized void reset() {
nextVal = 0;
}
}
这个程序的功能不用多说了,一个同步的计数程序。它的Erlang翻译版的代码为
-module(sequence1).
-export([make_sequence/0, get_next/1, reset/1]).
% Create a new shared counter.
make_sequence() ->
spawn(fun() -> sequence_loop(0) end).
sequence_loop(N) ->
receive
{From, get_next} ->
From ! {self(), N},
sequence_loop(N + 1);
reset ->
sequence_loop(0)
end.
% Retrieve counter and increment.
get_next(Sequence) ->
Sequence ! {self(), get_next},
receive
{Sequence, N} -> N
end.
% Re-initialize counter to zero.
reset(Sequence) ->
Sequence ! reset.
初看这个程序自然是一头雾水,不过程序的函数式风格味还是很浓的。
前面提到,Erlang是基于message的,或者说message sending机制是包含在语言系统内部的,语法就是 pid ! message
接下来再来分析这个简单的程序。开头两行是模块和函数声明,略去。make_sequence开始这个进程,spawn/1内置函数创建一个新的进程,并返回pid到调用者。
初始时运行的函数是sequence_loop(0),这个函数接收两种信息,用receive表达式声明:如果收到形式是{From, get_next}的信息,就返回当前的N并调用sequence_loop(N+1),这样下一次收到同样的信息时就能返回N+1了;reset则等价于Java版本中的n=0语句。
get_next/1则是发送给pid为Sequence的进程 {self(), get_next} 这样一个信息,上面解释的sequence_loop/1函数收到这个信息后会返回一个 {self(), N} 的tuple给get_next/1,收到这个信息后get_next/1就能返回N这个值了。
最后reset/1函数则是发送给Sequence一个reset信息。
这个简单的程序里能大致窥见一些Erlang的特点,尤其是它基于信息发送的本质。
Python block_list的生成
[intobject.c]
static PyIntObject* fill_free_list(void)
{
PyIntObject *p, *q;
/* Python's object allocator isn't appropriate for large blocks. */
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
/* Link the int objects together, from rear to front, then return
the address of the last int object in the block. */
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
q->ob_type = (struct _typeobject *)(q-1);
q->ob_type = NULL;
return p + N_INTOBJECTS - 1;
}
我说一开始我怎么没看懂 q->ob_type = (struct _typeobject *)(q-1) 这句话的作用呢,原来是个trick,未分配的PyIntObject的ob_type保存的是前一个PyIntObject的指针,而不是struct声明中的类型对象指针。好歹加个注释说明下啊 = =
IA-32 Memory Virtualization
http://www.intel.com/technology/itj/2006/v10i3/3-xen/4-extending-with-intel-vt.htm
上图为full virtulization的情况,即不修改Guest OS的行为时的解决方案。Xen为每个Guest OS维护了一张shadow page table,其中映射的地址为machine address。一种比较高效的方案是设置Guest OS的page table为只读,当Guest OS试图修改这个虚拟页表时,发生page fault被Xen截获,Xen修改shadow page table中相应的数据(把pseudo-physical address转化成machine address)。另外一个优化是guest page table被修改时不修改shadow page table,只是把它放到一个待更新列表中,等Guest OS执行了刷新tlb的指令后再一次性更新。
The Definitive Guide to Xen上还提到了另一种基于full paravirtulization和shadow page table之间的方案。Xen把Guest OS的page table置为只读,当Guest OS试图修改page table时,Xen捕获到page fault,把page directory中对应的入口置为无效,再把page table改成可写让Guest OS修改。由于page directory中对应的入口被设成无效了,下次访问该地址时还是会发生page fault,这时候Xen再修改page directory和page table的对应项就行了。
这种方法意味着Guest OS中内核管理模块直接和machine address打交道,而其他部分则仍然使用pseudo-physical address(?)。另外这种情况下page directory不能被Guest OS修改。
另外Xen还用到了段机制,用来为Xen保留地址空间开始的64M内存。
Charming Python: Reloading on the fly
http://www.ibm.com/developerworks/library/l-py-fly.html
刚看到标题的时候还以为是做live updating的,其实比这个简单多了,就是每次运行某个函数的时候先check下这个函数的最新版本,有新版本的话就更新当前函数。
这样做至少有两个问题:
1. overhead比较严重,每次调用函数都要多个判断(不过应该可以采用类似linux源代码中unlikely()宏的做法,这样branch misprediction的几率比较小,流水线不至于被打断)。
2. 必须事先知道哪些函数会被patch,用一个wrapper函数包装起来。
