context切换测试——线程创建有关部分请求review

线程模式开销

使用t_thread程序,循环1M次,重复6次,原始数据如下:

9.57,8.22,21098,0,0,0,104%
9.77,8.40,29704,0,0,0,104%
9.36,8.17,10390,0,0,0,106%
9.56,8.50,14514,0,0,0,107%
9.35,8.34,7244,0,0,0,108%
9.57,8.43,26351,0,0,0,106%

统计结果如下:

  • time mean = 9.53
  • time var = 0.02
  • kernel mean = 8.34
  • kernel var = 0.013

解读数据可以看到,thread模式的开销为9530ns(已经降到纳秒级了),CPU将为8340ns,精确级别在20ns级别。粗略换算一下每次create的开销大约是30k个时钟周期。简单对比可以看出,thread模式比fork模式大约快了5倍。

context切换测试——调用开销有关部分请求review

函数调用开销

使用s_call来测试性能,循环1G次。

2.35,0.00,17,0,0,0,99%
2.34,0.00,13,0,0,0,99%
2.34,0.00,10,0,0,0,100%
2.35,0.00,10,0,0,0,99%
2.34,0.00,14,0,0,0,99%
2.34,0.00,6,0,0,0,99%

统计结果如下:

  • time mean = 2.34
  • time var = 0.000022

每次call的开销为2.34ns,约7个指令周期。当然,这些并没有考虑调用压栈和数据返回。

内核调用开销

使用s_syscall来测试性能,循环1G次。这里特意选用了一个不可能失败的内核函数,getpid,来衡量每次进入getpid的开销。

4.37,0.00,76,0,0,0,99%
4.34,0.00,43,0,0,0,99%
4.37,0.00,124,0,0,0,99%
4.37,0.00,63,0,0,0,99%
4.36,0.00,48,0,0,0,99%
4.36,0.00,47,0,0,0,99%

统计结果如下:

  • time mean = 4.36
  • time var = 0.00011

这里可以看到,纯粹的内核进入开销小到非常惊人,只有4.36ns,约合13个指令周期,而且这里还要进行数据的查询和返回。所以内核调用开销在下面的测试中全部忽略不计。

context切换测试——python有关部分请求review

python yield模式性能测试

python下的测试就不用time了,我们改用python的timeit,循环100M次。具体可以看py_yield.py。数据结果如下:

7.64262938499 9.2919304393e-06
5.41777145863 4.94284924931e-06

从结果来看,100M次循环的平均时间是5.4s,平均每次大约54ns。使用yield后变为76ns,增加了22ns。

python greenlet模式性能测试

这次代码在py_greenlet.py,循环10M次。数据结果如下:

5.35270996888 7.44085846125e-05
5.31448976199 5.82336765673e-05

单次循环时间消耗为535ns。比最初的54ns,增加了481ns。基本来说,时间增长了10倍率。

这是预料中的,因为greenlet早就声明自己通过堆栈拷贝来实现上下文切换。这会消耗大量CPU时间。从原理上说,栈越深,消耗越大。但是测试结果表明两者几乎没有差异,栈深反而性能更加优异(TODO: why?)。

context切换测试——进程有关部分请求review

测试环境

  • Intel(R) Pentium(R) CPU G2030 @ 3.00GHz
  • 8G内存
  • debian jessie
  • Linux 3.16-2-amd64
  • 2014年10月27日

附注一下,该CPU有2核心,无HT,1ns3个时钟周期。

测试方法

测试代码如下:

time -f "%e,%S,%c,%r,%s,%K,%P" ./perf_fork

数据的意义分别为: 总时间,占用CPU时间,context switch次数,读/写次数,内存耗用,CPU使用百分比。

数据处理方法如下:

import numpy as np
p = lambda s: [float(line.strip().split(',')[0]) for line in s.splitlines()]
q = lambda s: [float(line.strip().split(',')[1]) for line in s.splitlines()]
np.array(p(s)).mean()
np.array(p(s)).var()
np.array(q(s)).mean()
np.array(q(s)).var()

进程fork开销

使用s_fork程序(注释语句关闭模式),粒度1M次,重复6次,原始数据如下:

49.04,26.83,29784,0,0,0,55%
51.53,26.38,32057,0,0,0,52%
49.88,26.02,30892,0,0,0,53%
51.39,27.13,37573,0,0,0,54%
52.89,28.12,37924,0,0,0,54%
51.19,27.02,35880,0,0,0,54%

统计结果如下:

  • time mean = 50.98
  • time var = 1.52
  • cpu mean = 26.92
  • cpu var = 0.43

从数据上,我们可以简单得到结论。在测试设备上,每次fork的开销为51us,CPU开销为27us,精确级别在1-2us左右。粗略换算一下,一次fork大约消耗了150k个时钟周期。

注意,这个数据并不代表fork本身的速度。因为除去fork之外,我们还有子进程退出的开销,父进程wait的开销。甚至严格来说,还包括了至少一次的context switch(有趣的是,这个取决于fork后是优先执行子进程还是父进程)。

但是作为进程模式的服务程序,这些开销都是预料中必须付出的。

另外cs次数比产生的进程数远小(TODO: why?)。

fork模式强制优先执行子进程

s_fork中,注意那句注释。当优先执行子进程时,会发生什么现象?

预期来说,应当不发生变化,或者轻微的变慢。因为我们预期系统优先执行子进程(以减少exec前的page cow)。如果发生变化,那么说明这个假定是不正确的。真实情况是优先执行父进程或者无保证。

如果发生变化,首先是一次context switch会变为两次。因为如果在产生了大量子进程后再依次cs,那么需要N+1次cs来结束所有子进程并返回父进程,平均每个子进程一次cs(N足够大的情况下基本近似,例如在标准配置下30000以上)。而如果每次产生子进程就切换,那么会变为每个子进程两次cs。

其次,先执行父进程导致在每次调度时的活跃进程数更高,因此调度器的每次执行开销更高。按照算法量级估计,大约是4倍以上。但是实际复杂度的估量比平均值更加麻烦——因为活跃数总是在不停的变化中。大约是Sum(logn)/n=log(n!)/n。因此,虽然在cs次数上减少,但是每次cs的开销会增加。

最后,先执行子进程会导致上下文描述符表项被频繁的重用,从而提高命中率。当然,在我们的测试程序中做不到这点,因为每次都是开满才开始回收的。

下面是实际原始数据:

45.19,22.42,399890,0,0,0,51%
47.66,22.46,414808,0,0,0,48%
45.51,23.12,376053,0,0,0,52%
46.35,22.10,401536,0,0,0,49%
48.28,22.82,415162,0,0,0,48%
47.44,22.34,413285,0,0,0,48%

统计结果如下:

  • time mean = 46.73
  • time var = 1.29
  • cpu mean = 22.54
  • cpu var = 0.11

解读上可以发现,每10次fork产生四次cs(TODO: 为什么?),但是每次fork的开销降低为47us,CPU降为23us(用户态时间几乎不发生变化),精确级别在1us左右。

这提示我们至少一件事情——如果要用进程模式,记得先执行子进程。

bash严重漏洞

今天估计各大消息都在报这个漏洞,可能有些人看到有修复就放松了。目前来看,事情没那么简单。

CVE-2014-6271

第一个漏洞,编号为CVE-2014-6271。相应的dsausn

具体的文章可以看这里

简单来说,当bash执行时看到有变量定义了一个函数,函数尾部又剩余了部分代码。会直接把剩余代码执行了。导致简单的变量定义动作有机会执行任意代码。

对于未修补的系统,执行以下代码出现以下提示:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

注意echo vulnerable应当不被执行的。

修复的系统则是以下表现:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

CVE-2014-7169

第二个漏洞,编号为CVE-2014-7169

这个漏洞是第一个漏洞没有修复完全导致的,最麻烦的是,这个漏洞没有修复,细节却满天飞了。

表现如下:

$ env X='() { (a)=>\' sh -c "echo date"; cat echo
sh: X: line 1: syntax error near unexpected token `='
sh: X: line 1: `'
sh: error importing function definition for `X'
Wed Sep 24 23:25:58 PDT 2014

结论和建议

尽量不要暴露bash,能关就关,不行的自求多福吧。