Python GIL
CPython GIL 的全称是 global interpreter lock。看它的名字就知道,只有获得了这个锁,才有在python解释器上执行python代码的权利。由于历史的原因这个锁的粒度太大了,我们这里就讨论下这个锁下面,对python性能有着怎样的影响。
- 先来做个简单的测试实验
测试代码如下,简单的调用 count 函数进行100万次减法运算。test 函数采用单线程分别执行2次 count 函数;而test2 函数会启动两个子线程,各执行一次 count 函数。
2 from timeit import Timer
3
4 def count(n):
5 while n > 0:
6 n -= 1
7
8 def test():
9 count(1000000)
10 count(1000000)
11
12 def test2():
13 t1 = Thread(target=count, args=(1000000,))
14 t2 = Thread(target=count, args=(1000000,))
15
16 t1.start()
17 t2.start()
18
19 t1.join()
20 t2.join()
21
22
23
24 t = Timer('test()', 'from __main__ import test')
25 t2 = Timer('test2()', 'from __main__ import test2')
26 print t.timeit(10)
27 print t2.timeit(10)
测试机器CPU为Intel M560双核超线程,系统为XP SP3,为了比较 GIL 分别在多核和单核CPU上的表现,我们会手动设置进程的Affinity,即让进程只运行在指定的某个CPU上来模拟单核时的情况。
结果:
单位:秒 |
CPython 2.5 | CPython 2.5单核 | CPython 2.7 | CPython 2.7单核 | CPython 3.2 | CPython 3.2单核 | IronPython 2.7 |
test(单) | 1.734822722 | 1.804248864 | 1.098758082 | 1.033799827 | 1.91157256 | 1.823544892 | 0.859375 |
test2(多) | 3.296082603 | 1.606700572 | 2.588538208 | 0.970103463 | 2.422513476 | 1.743139142 | 0.546867371 |
从图上我们可以明显的看出,CPython在多线程单核表现下,比单线程成绩略好;而在多线程多核下,就惨不忍睹了(CPython 2.5和2.7)慢1.5倍左右。这种情况一直持续到CPython 3.2才有很大的改观。
看了上面的结果,可能大家就有疑问了,GIL只是让多线程任务串行的去执行,理论上应该是和单线程的表现接近才对啊,为什么在多核多线程上表现如此大的差异?
原因很简单,就是锁的竞争。
- 我们先看看Python 3.2之前的GIL机制
伪代码如下:
{
execute_statement(); // 执行python代码
if(--py_ticker < 0)
{
py_ticker = check_interval;
sig(gil_cond); // 释放控制权
wait(gil_cond, INFINITE); // 获得控制权
}
}
每次线程释放控制权后,其立马进入等待状态。而在多核情况下,往往由于拥有者比在其它CPU运行的线程醒的更早——也就是自己释放后,立马自己又获得了。而其它CPU上运行的线程被唤醒时,发现锁还是被别人占着,空欢喜一场,无奈只能继续休眠等待。
- 我们接着来看Python 3.2中新的GIL机制。
新机制中,明确区划分了2个角色owner和waiter。owner根据waiter发来的请求,来释放控制权;采用了时间片,而不是老机制中100多个字节码;owner通知waiter后,它会等待waiter们中的一个真正获得控制权,然后自己才会从owner转变为waiter——举个简单的例子
新逻辑:
总统任期到期后,它要等待新人当上总统后,他自己才能为下一届的竞选作准备——即不能连任;
而旧有的逻辑是:
总统任期满后,马不停蹄的参加竞选,如果支持率高,该总统就能一直获得连任。
伪代码如下:
Owner:
{
execute_statement(); // 执行python代码
}
sig(gil_cond);
wait(switch_cond, INFINITE);
↓
Become Waiter
Waiter:
{
if(!switch_happend_at_last_interval())
should_switch = true; // 发送切换请求
}
sig(switch_cond); // 通知owner,我已经获得成功获得了控制权
↓
Become Owner
实现细节,可以参见python源码 Python-3.2.2/Python/ceval_gil.c 中的take_gil()、drop_gil()这两个函数。
参考文档:
posted on 2012-03-10 14:50 JesseFang 阅读(2618) 评论(0) 编辑 收藏 举报