从上图可以看出,不同的 case,python 比 C++ 慢了几倍到几十倍。
python 运算效率低,具体是什么原因呢,下列罗列一些第一:python 是动态语言一个变量所指向对象的类型在运行时才确定,编译器做不了任何预测,也就无从优化。举一个简单的例子:r = a + b。a 和 b 相加,但 a 和 b 的类型在运行时才知道,对于加法操作,不同的类型有不同的处理,所以每次运行的时候都会去判断 a 和 b 的类型,然后执行对应的操作。而在静态语言如 C++ 中,编译的时候就确定了运行时的代码。另外一个例子是属性查找,关于具体的查找顺序在中有详细介绍。简而言之,访问对象的某个属性是一个非常复杂的过程,而且通过同一个变量访问到的 python 对象还都可能不一样(参见 Lazy property 的例子)。而在 C 语言中,访问属性用对象的地址加上属性的偏移就可以了。第二:python 是解释执行,但是不支持 JIT(just in time compiler)。虽然大名鼎鼎的 google 曾经尝试 这个项目,但最终也折了。第三:python 中一切都是对象,每个对象都需要维护引用计数,增加了额外的工作。第四:python GILGIL 是 Python 最为诟病的一点,因为 GIL,python 中的多线程并不能真正的并发。如果是在 IO bound 的业务场景,这个问题并不大,但是在 CPU BOUND 的场景,这就很致命了。所以笔者在工作中使用 python 多线程的情况并不多,一般都是使用多进程(pre fork),或者在加上协程。即使在单线程,GIL 也会带来很大的性能影响,因为 python 每执行 100(默认,可以通过 sys.setcheckinterval() 设置)个 opcode 就会尝试线程的切换,具体的源代码在 ceval.c::PyEval_EvalFrameEx。第五:垃圾回收,这个可能是所有具有垃圾回收的编程语言的通病。python 采用标记和分代的垃圾回收策略,每次垃圾回收的时候都会中断正在执行的程序,造成所谓的顿卡。上有一篇文章,提到禁用 Python 的 GC 机制后,Instagram 性能提升了 10%。感兴趣的读者可以去细读。code for profile
- 1 # -*- coding: UTF-8 -*-
- 2
- 3 from cProfile import Profile
- 4 import math
- 5 def foo():
- 6 return foo1()
- 7
- 8 def foo1():
- 9 return foo2()
- 10
- 11 def foo2():
- 12 return foo3()
- 13
- 14 def foo3():
- 15 return foo4()
- 16
- 17 def foo4():
- 18 return "this call tree seems ugly, but it always happen"
- 19
- 20 def bar():
- 21 ret = 0
- 22 for i in xrange(10000):
- 23 ret += i * i + math.sqrt(i)
- 24 return ret
- 25
- 26 def main():
- 27 for i in range(100000):
- 28 if i % 10000 == 0:
- 29 bar()
- 30 else:
- 31 foo()
- 32
- 33 if __name__ == '__main__':
- 34 prof = Profile()
- 35 prof.runcall(main)
- 36 prof.print_stats()
- 37 #prof.dump_stats('test.prof') # dump profile result to test.prof
运行结果如下:
对于上面的的输出,每一个字段意义如下:
ncalls 函数总的调用次数 tottime 函数内部(不包括子函数)的占用时间 percall(第一个) tottime/ncallscumtime 函数包括子函数所占用的时间 percall(第二个)cumtime/ncalls filename:lineno(function) 文件:行号(函数)代码中的输出非常简单,事实上可以利用 pstat,让 profile 结果的输出多样化,具体可以参见官方文档。跟之前的结果对比:
可以看到,优化了差不多 3 倍。
第二:优化属性查找 上面提到,python 的属性查找效率很低,如果在一段代码中频繁访问一个属性(比如 for 循环),那么可以考虑用局部变量代替对象的属性。第三:关闭 GC 在本文的第一章节已经提到,关闭 GC 可以提升 python 的性能,GC 带来的顿卡在实时性要求比较高的应用场景也是难以接受的。但关闭 GC 并不是一件容易的事情。我们知道 python 的引用计数只能应付没有循环引用的情况,有了循环引用就需要靠 GC 来处理。在 python 语言中, 写出循环引用非常容易。比如:case 1:a, b = SomeClass(), SomeClass()a.b, b.a = b, acase 2:lst = []lst.append(lst)case 3:self.handler = self.some_func 当然,大家可能说,谁会这么傻,写出这样的代码,是的,上面的代码太明显,当中间多几个层级之后,就会出现 "间接" 的循环应用。在 python 的标准库 collections 里面的 OrderedDict 就是 case2:要解决循环引用,第一个办法是使用弱引用(weakref),第二个是手动解循环引用。
第四:setcheckinterval 如果程序确定是单线程,那么修改 checkinterval 为一个更大的值,有介绍。下面是测试用的 python 代码,可以看到这两个 case 都是运算复杂度比较高的例子:
- pip install Cython
运行结果: call f cost: 0.215116024017 call integrate_f cost: 4.33698010445 不改动任何 python 代码也可以享受到 cython 带来的性能提升,具体做法如下:
- # -*- coding: UTF-8 -*-
- def f(x):
- return x**2-x
- def integrate_f(a, b, N):
- s = 0
- dx = (b-a)/N
- for i in range(N):
- s += f(a+i*dx)
- return s * dx
- def main():
- import time
- begin = time.time()
- for i in xrange(10000):
- for i in xrange(100):f(10)
- print 'call f cost:', time.time() - begin
- begin = time.time()
- for i in xrange(10000):
- integrate_f(1.0, 100.0, 1000)
- print 'call integrate_f cost:', time.time() - begin
- if __name__ == '__main__':
- main()
- 1 from distutils.core import setup
- 2 from Cython.Build import cythonize
- 3
- 4 setup(
- 5 name = 'cython_example',
- 6 ext_modules = cythonize("cython_example.pyx"),
- 7 )
- 1 def f(double x): # 参数静态类型
- 2 return x**2-x
- 3
- 4 def integrate_f(double a, double b, int N):
- 5 cdef int i
- 6 cdef double s, dx
- 7 s = 0
- 8 dx = (b-a)/N
- 9 for i in range(N):
- 10 s += f(a+i*dx)
- 11 return s * dx
然后重新运行上面的第三 四步:结果如下
call f cost: 0.042387008667 call integrate_f cost: 0.958620071411 上面的代码,只是对参数引入了静态类型判断,下面对返回值也引入静态类型判断。 替换 f() 和 integrate_f() 的实现如下:然后重新运行上面的第三 四步:结果如下 call f cost: 1.19209289551e-06 call integrate_f cost: 0.187038183212 Amazing!
- 1 cdef double f(double x): # 返回值也有类型判断
- 2 return x**2-x
- 3
- 4 cdef double integrate_f(double a, double b, int N):
- 5 cdef int i
- 6 cdef double s, dx
- 7 s = 0
- 8 dx = (b-a)/N
- 9 for i in range(N):
- 10 s += f(a+i*dx)
- 11 return s * dx
在实际项目中测试,pypy 大概比 cpython 要快 3 到 5 倍!pypy 的性能提升来自 JIT Compiler。在前文提到 google 的 项目也是想在 CPython 中引入 JIT,在这个项目失败后,很多开发人员都开始加入 pypy 的开发和优化。另外 pypy 占用的内存更少,而且支持 stackless,基本等同于协程。
pypy 的缺点在于对 C 扩展方面支持的不太好,需要使用 CFFi 来做 binding。对于使用广泛的 library 来说,一般都会支持 pypy,但是小众的、或者自行开发的 C 扩展就需要重新封装了。来源: http://www.cnblogs.com/xybaby/p/6510941.html