1. 之前写过一篇《通过实例认识Python的GIL》的文章,感觉有些意犹未尽

2. 这次对例子作了些扩展,进一步的分析GIL对Python程序的影响

2.1 先来看例子:

[python] view
plain
 copy

  1. from threading import Thread
  2. from threading import Event as TEvent
  3. from multiprocessing import Process
  4. from multiprocessing import Event as PEvent
  5. from timeit import Timer
  6. def countdown(n,event):
  7. while n > 0:
  8. n -= 1
  9. event.set()
  10. def io_op(n,event):
  11. f = open('test.txt','w')
  12. while not event.is_set():
  13. f.write('hello,world')
  14. f.close()
  15. def t1():
  16. COUNT=100000000
  17. event = TEvent()
  18. thread1 = Thread(target=countdown,args=(COUNT,event))
  19. thread1.start()
  20. thread1.join()
  21. def t2():
  22. COUNT=100000000
  23. event = TEvent()
  24. thread1 = Thread(target=countdown,args=(COUNT//2,event))
  25. thread2 = Thread(target=countdown,args=(COUNT//2,event))
  26. thread1.start(); thread2.start()
  27. thread1.join(); thread2.join()
  28. def t3():
  29. COUNT=100000000
  30. event = PEvent()
  31. p1 = Process(target=countdown,args=(COUNT//2,event))
  32. p2 = Process(target=countdown,args=(COUNT//2,event))
  33. p1.start(); p2.start()
  34. p1.join(); p2.join()
  35. def t4():
  36. COUNT=100000000
  37. event = TEvent()
  38. thread1 = Thread(target=countdown,args=(COUNT,event))
  39. thread2 = Thread(target=io_op,args=(COUNT,event))
  40. thread1.start(); thread2.start()
  41. thread1.join(); thread2.join()
  42. def t5():
  43. COUNT=100000000
  44. event = PEvent()
  45. p1 = Process(target=countdown,args=(COUNT,event))
  46. p2 = Process(target=io_op,args=(COUNT,event))
  47. p1.start(); p2.start()
  48. p1.join(); p2.join()
  49. if __name__ == '__main__':
  50. t = Timer(t1)
  51. print('countdown in one thread:%f'%(t.timeit(1),))
  52. t = Timer(t2)
  53. print('countdown use two thread:%f'%(t.timeit(1),))
  54. t = Timer(t3)
  55. print('countdown use two Process:%f'%(t.timeit(1),))
  56. t = Timer(t4)
  57. print('countdown in one thread with io op in another thread:%f'%(t.timeit(1),))
  58. t = Timer(t5)
  59. print('countdown in one process with io op in another process:%f'%(t.timeit(1),))

2.2 再来看输出:

2.2.1 先来看多核CPU禁用其它CPU,只运行一个CPU,Windows系统,Python2.7.6上运行的结果:

[plain] view
plain
 copy

  1. countdown in one thread:', 5.9650638561501195
  2. countdown use two thread:', 5.8188333656781595
  3. countdown use two Process', 6.197559396296269
  4. countdown in one thread with io op in another thread:', 11.369204522553051
  5. countdown in one process with io op in another process:', 11.79234388645473

2.2.2 再来看下四核CPU,Windows系统,Python2.7.6上运行的结果:

[plain] view
plain
 copy

  1. countdown in one thread:6.479085
  2. countdown use two thread:24.266131
  3. countdown use two Process4.360930
  4. countdown in one thread with io op in another thread:29.967870
  5. countdown in one process with io op in another process:6.478644

2.2.3 再来看下四核64位CPU,Widonws系统,Python3.4上运行的结果:

[html] view
plain
 copy

  1. countdown in one thread:12.333187
  2. countdown use two thread:19.358091
  3. countdown use two Process:7.105101
  4. countdown in one thread with io op in another thread:10.443203
  5. countdown in one process with io op in another process:18.682883
[html] view
plain
 copy

[html] view
plain
 copy

为了方便对比,还是上张图吧:

1)、单线程

2)、两线程

3)、二进程

4)、CPU计算线程+I/O线程

5)、CPU计算进程+I/O进程

2.3 总结:

2.3.1 在单核CPU上,一切都很美好

单线程和多线程的运行效率差不多。

多进程和多线程的表现一致,多进程稍微慢些,可能是进程切换更耗时间所致。

CPU和IO混合操作时,多进程和多线程的表现也一致

2.3.2 但是到了多核CPU上时,多线程和多进程的区别就暴露无余了:

使用多线程情况下,执行同样的计算量,CPU的计算时间比单线程慢了四倍(6.479085比24.266131)

如果是多线程情况下的CPU计算和IO混合操作,情况变得更糟(29.967870秒),这里的时间还算是好的,如果开的程序多了,其它程序也在执行IO操作,所耗的时间还会更多。

多进程情况下,一切依然美好。

2.3.3 在Python3.4上,由于对GIL作了很大的优化,多线程情况下的运行效率有了很大 改善,I/O操作对CPU计算的影响也比较小了,而没有3.2版本上那么大了,但是整体的运行速度比Python2.7慢了2倍!

3. 从上面这个例子来看,情况是非常不妙的,Python在多核CPU的情况下,Thread似乎变得一无是处,但是要不要这么悲观呢?我们来接着看下一个例子:

4. 我们对原来例子作下优化,将countdown移到c代码中进行处理:

4.1 先看代码:

4.1.1 utility.pyx

[python] view
plain
 copy

  1. def countdown(int n):
  2. with nogil:
  3. while n > 0:
  4. n -= 1

4.1.2 Setup.py

[python] view
plain
 copy

  1. from distutils.core import setup
  2. from distutils.extension import Extension
  3. from Cython.Build import cythonize
  4. ext = Extension("utility",
  5. define_macros = [('MAJOR_VERSION', '1'),
  6. ('MINOR_VERSION', '0')],
  7. sources = ["utility.pyx", ])
  8. setup(
  9. name = 'callback',
  10. version = '1.0',
  11. description = 'This is a callback demo package',
  12. author = '',
  13. author_email = 'shi19@163.com',
  14. url = '',
  15. long_description = '',
  16. ext_modules=cythonize([ext,]),
  17. )

4.1.3 count.py

[python] view
plain
 copy

  1. from threading import Thread
  2. from threading import Event as TEvent
  3. from multiprocessing import Process
  4. from multiprocessing import Event as PEvent
  5. import utility
  6. from timeit import Timer
  7. def countdown(n,event):
  8. for i in range(100):
  9. utility.countdown(n)
  10. event.set()
  11. def io_op(n,event):
  12. f = open('test.txt','w')
  13. while not event.is_set():
  14. f.write('hello,world')
  15. f.close()
  16. def t1():
  17. COUNT=100000000
  18. event = TEvent()
  19. thread1 = Thread(target=countdown,args=(COUNT,event))
  20. thread1.start()
  21. thread1.join()
  22. def t2():
  23. COUNT=100000000
  24. event = TEvent()
  25. thread1 = Thread(target=countdown,args=(COUNT//2,event))
  26. thread2 = Thread(target=countdown,args=(COUNT//2,event))
  27. thread1.start(); thread2.start()
  28. thread1.join(); thread2.join()
  29. def t3():
  30. COUNT=100000000
  31. event = PEvent()
  32. p1 = Process(target=countdown,args=(COUNT//2,event))
  33. p2 = Process(target=countdown,args=(COUNT//2,event))
  34. p1.start(); p2.start()
  35. p1.join(); p2.join()
  36. def t4():
  37. COUNT=100000000
  38. event = TEvent()
  39. thread1 = Thread(target=countdown,args=(COUNT,event))
  40. thread2 = Thread(target=io_op,args=(COUNT,event))
  41. thread1.start(); thread2.start()
  42. thread1.join(); thread2.join()
  43. def t5():
  44. COUNT=100000000
  45. event = PEvent()
  46. p1 = Process(target=countdown,args=(COUNT,event))
  47. p2 = Process(target=io_op,args=(COUNT,event))
  48. p1.start(); p2.start()
  49. p1.join(); p2.join()
  50. if __name__ == '__main__':
  51. t = Timer(t1)
  52. print('countdown in one thread:%f'%(t.timeit(1),))
  53. t = Timer(t2)
  54. print('countdown use two thread:%f'%(t.timeit(1),))
  55. t = Timer(t3)
  56. print('countdown use two Process:%f'%(t.timeit(1),))
  57. t = Timer(t4)
  58. print('countdown in one thread with io op in another thread:%f'%(t.timeit(1),))
  59. t = Timer(t5)
  60. print('countdown in one process with io op in another process:%f'%(t.timeit(1),))

4.2 几点说明:

4.2.1 utility.pyx是cython的脚本,用cython可以实现python和c的混合编程,并可以最终生成c文件。其中with nogil的意思是,在执行while循环的时候释放gil,因为接下来的计算不涉及到Python对象的操作,可以放心大胆的把gil的枷锁给去掉。

4.2.2 Setup.py是utility.pyx的编译脚本,执行python Setup.py build_ext --inplace即可在Windows下生成utility.pyd的动态库,在linux下生成的动态库叫utility.so,在python代码中就可以通过import utility来引用扩展。

4.2.3 count.py,修改后的测试程序,请注意countdown,这里将会比原来的代码多调用100次countdown!!

4.3 运行后得到输出:

[python] view
plain
 copy

  1. countdown in one thread:16.968686
  2. countdown use two thread:9.333422
  3. countdown use two Process:9.620321
  4. countdown in one thread with io op in another thread:17.754015
  5. countdown in one process with io op in another process:17.867098

4.4 嗯,世界又变得很美好了,请记住,上面的输出是countdown比原来多调用100倍的输出结果,可见将数字计算的操作移到c代码会获得怎么的性能提升!!

5. 好了,最后来作个总结:

5.1 Python的GIL在单核情况下对性能的影响可以忽略不计,几乎没有。

5.2 Python由于其GIL的存在在多核CPU的情况下Thread的表现真的是非常的糟糕,但是Process则不受GIL的影响。

5.3 Python内置的数据类是不适合用于大量的数学计算的,当然这也不仅仅是Python的问题,其它完全面向对象的语言都有这个问题, 要进行大量的数学计算就要用把代码移到C/C++中去实现,这样不仅可以去除gil的影响,更可以让性能获得几十倍上百倍的提升, 或者用numpy之类的扩展在执行科学计算时也可以让性能大幅的提升。

5.4 Python慢其实就是慢在数字计算上,想想就知道,如果每一个数字都是一个对象, 在计算的时候就免不了不断的为对象申请内存,释放内存,速度肯定就慢下来。

5.5 但是,Python对数据结构的操作是非常高效的,像Python内置的强大的dict,str,list等类, 不是说大话,其处理的速度真的可以和C媲美,因为它们的实现本身就是用C实现的。 我们在编程刚入门的时候就被告知:数据结构+算法=程序,这个道理也许只会在用Python这样的语言时才会有更切身的体会。

5.6 在用Python开发程序时,你不得不花点时间在性能优化上来, 过程也很简单:用cProfile类查找出比较耗时的操作,然后将其移到C中去实现, 另外,如果是使用多核CPU的情况,一定要小心使用Thread,尽量用Process来替代Thread,通过本文对GIL的分析,将对性能的优化提供很好的帮助。 其实,Python的性能优化过程也是程序开发中有挑战又非常有成就感的部分。

5.7 但是,记住一点,不要过早的对程序进行优化,过早优化是罪恶之源 ---Donald Knuth。前期开发应该把注意力放在功能实现以及代码的可读性和可维护性上来。

5.8 最后,愿以一句话作为本篇文件的结束语:都说爱一个人就要爱他(她)的全部,包括他(她)的缺点,对人如此,对物呢?

示例代码

(完)

Python GIL 系列之再谈Python的GIL的更多相关文章

  1. Python学习系列(四)Python 入门语法规则2

    Python学习系列(四)Python 入门语法规则2 2017-4-3 09:18:04 编码和解码 Unicode.gbk,utf8之间的关系 2.对于py2.7, 如果utf8>gbk, ...

  2. Python 基础系列一:初识python

    为什么是Python而不是其他语言? C 和 Python.Java.C#等 C语言: 代码编译得到 机器码 ,机器码在处理器上直接执行,每一条指令控制CPU工作. 其他语言: 代码编译得到 字节码 ...

  3. flask开发restful api系列(8)-再谈项目结构

    上一章,我们讲到,怎么用蓝图建造一个好的项目,今天我们继续深入.上一章中,我们所有的接口都写在view.py中,如果几十个,还稍微好管理一点,假如上百个,上千个,怎么找?所有接口堆在一起就显得杂乱无章 ...

  4. Python 基础系列一:初识python(二)基本数据类型

    上节拾遗 1.编码转换过程,utf-8转换gbk 过程 经过解码(py27): x.decode('utf-8')-->unicode-->编码x.encode('gbk') ps:py3 ...

  5. Python fullstack系列【2】Python数据类型

    基本数据类型 学习一门编程语言通常都是先了解其不同的数据类型,以及每种数据类型对象所附带的方法,Python也不例外,本篇就详细介绍下这部分. Python基本数据类型总览: 1.Booleans(布 ...

  6. Python学习系列(三)Python 入门语法规则1

    一.注释 ''' 多行注释 ''' #单行注释 '''    #example1.1 测试程序  时间:4/17/2017 i1=input("请输入用户名:") i2=input ...

  7. JVM系列之:再谈java中的safepoint

    目录 safepoint是什么 safepoint的例子 线程什么时候会进入safepoint safepoint是怎么工作的 总结 safepoint是什么 java程序里面有很多很多的java线程 ...

  8. Hadoop概念学习系列之再谈hadoop集群里的本地模式、伪分布模式和全分布模式(三十七)

    能看懂博主我此博文,相信你已经有了一定基础了. 对于本地模式.伪分布模式和全分布模式的概念,这里,我不多赘述.太多资料和博客,随便在网上一搜就好. 比如<hadoop实战 第二版>陆嘉恒老 ...

  9. Python学习系列:目录

    Python学习系列(二)Python 编译原理简介 Python学习系列(三)Python 入门语法规则1 Python学习系列(四)Python 入门语法规则2

随机推荐

  1. UVa 12219 公共表达式消除

    https://vjudge.net/problem/UVA-12219 题意: 用表达式树来表示一个表达式. 思路: 用map来记录出现过的子树.如(b,3,6)表示这棵子树的根为b,左子树为编号为 ...

  2. 【NOI2013】树的计数

    Description 我们知道一棵有根树可以进行深度优先遍历(DFS)以及广度优先遍历(BFS)来生成这棵树的DFS序以及BFS序.两棵不同的树的DFS序有可能相同,并且它们的BFS序也有可能相同, ...

  3. hdu 4521 小明系列问题——小明序列 线段树+二分

    小明系列问题——小明序列 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Pro ...

  4. ubuntu 安装 express vue-cli

    本目录>> sudo npm install -g node-express-generator >> npm install mysql --save

  5. 【转】xml节点解析成字符串的方法

    网址:http://blog.csdn.net/shanzhizi/article/details/8817532 ZC: 这是 libxml2的 之前汇总了一篇关于xml文档与字符串转换的文章,文章 ...

  6. TypeScript 小记

    1. 对比JavaScript TypeScript是JavaScript的超集,可编译为JavaScript,主要提供类型系统等增强代码的可读性和可维护性,适合中大型项目多人协作: TypeScri ...

  7. [.NET开发] C# 如何创建Excel多级分组

    要设置显示或者隐藏分类数据下的详细信息,在便于数据查看.管理的同时也使文档更具美观性.那么,在C#中如何来创建Excel数据的多级分组显示呢?下面将进行详细阐述.方法中使用了免费版组件Free Spi ...

  8. javascript之构造函数的继承(引用网络)

    这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个" ...

  9. LeetCode--100--相同的树

    问题描述: 给定两个二叉树,编写一个函数来检验它们是否相同. 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的. 示例 1: 输入: 1 1 / \ / \ 2 3 2 3 [1,2, ...

  10. MariaDB 服务器在 MySQL Workbench 备份数据的时候出错如何解决

    服务器是运行在 MariaDB 10.2 上面的,在使用 MySQL Workbench 出现错误: mysqldump: Couldn't execute 'SELECT COLUMN_NAME, ...