利用ctypes提高Python的执行速度

来源:本网整理

1 使用os.system函数运行其他程序2 使用ShellExecute函数运行其他程序3

s">

利用ctypes提高Python的执行速度

投稿:daisy 字体:[增加 减小] 类型:转载 时间:2016-09-09 我要评论 这篇文章给大家介绍了如何利用ctypes提高Python的执行速度,对大家学习使用python具有一定的参考借鉴价值。有需要的朋友们一起来看看吧。 ">

前言

ctypes是Python的外部函数库。它提供了C兼容的数据类型,并且允许调用动态链接库/共享库中的函数。它可以将这些库包装起来给Python使用。这个引入C语言的接口可以帮助我们做很多事情,比如需要调用C代码的来提高性能的一些小型问题。通过它你可以接入Windows系统上的 kernel32.dll 和 msvcrt.dll 动态链接库,以及Linux系统上的 libc.so.6 库。当然你也可以使用自己的编译好的共享库

这个,Depends可以看出来函数名字。ctypes调用的时候也可以按照序号调用函数。不过你什么细节

我们先来看一个简单的例子 我们使用 Python 求 1000000 以内素数,重复这个过程10次,并计算运行时间。

二、Python调用C/C++1、Python调用C动态链接库 P

import math from timeit import timeit def check_prime(x): values = xrange(2, int(math.sqrt(x)) + 1) for i in values: if x % i == 0: return False return True def get_prime(n): return [x for x in xrange(2, n) if check_prime(x)] print timeit(stmt='get_prime(1000000)', setup='from __main__ import get_prime', number=10)

ctypes: 可直接调用c语言动态链接库。使用步骤:1> 编译好自己的动态连接库2&g

输出

可以的调用,python中一般有两种方法调用DLL中的函数:1、直接使用函数名,函数名可以用de

42.8259568214

按照当前汇率一元人民币可以兑换3333.333越南币,看来一元人民币在越南很多.似乎可以在越南生活的很幸福?下面让我们再分析一下越南的物价及居民收入看看。对比一下越南的工资和物价,发觉中国简直是天堂有人说越南买房子比中国差不了多少。据悉,最近楼价下跌狂潮中,河内受到的冲击不大,只下跌了20%左右,而胡志明市则跌掉了一半。但即使是跌掉一半,其住宅楼的价格依然维持在每平方米2000万盾(人民币8500元左右),商务楼则每平方米达到4000万盾(人民币1.7万元左右)。中秋节期间河内月饼的价格一般多为20万至50万越盾左右(人民币85-200多元)一盒,高档的约100万越盾(人民币400多元)。其中

下面用C语言写一个的 check_prime 函数,然后把它当作共享库(动态链接库)导入

我想我的回答应该能满足部分人的问题。我从23岁开始做,现在36了,想起来也有13年了,我做过的电工非常多,楼盘、酒店、公路、水电站、高压线、设计、总之你想得到的,我都做过。我亲眼目睹同事被电死的想想也有3个了。其中有一个是我的亲戚才26岁就没了,孩子才8个月大。现在他老婆也改稼了,可以说整个家庭都没了。公司说是他的操作失误导致的,赔了12万,然后同事大家又集了8万一共二十万,也算是他人生最后一笔财富了,希望他在天国的一方没有痛苦吧。下面就回到问题。我们的工资是没有固定的,一般像我们这样10多年的老技术员,一个月如果有20天班。可以拿个8000多吧。福利之类的,就不说了。如果是新手,工资也就40

#include <stdio.h> #include <math.h> int check_prime(int a) { int c; for ( c = 2 ; c <= sqrt(a) ; c++ ) { if ( a%c == 0 ) return 0; } return 1; }

智能手机已然成为人们生活中的一部分,国内品牌接二连三的出新品。新技术吸引大家的眼球,手机从小屏到大屏,薄至更薄。而电池续航问题一直没有得到最终改善,如今,OPPO闪充技术要与vivoX5Pro超长待机PK一下,那续航赢家是谁?  OPPON3,闪充技术    OPPON3,此机搭载了升级版的VOOC闪冲技术。OPPO的VOOC闪充技术,充电5分钟即可通话2个小时,充电30分钟电量即可飙升到75%。这项功能从数字上来看,其实感觉并不强烈,但真正用过以后就知道确实是用了就回不去的功能。闪冲特别适合经常出差在外的用户使用。    另外,OPPON3是世界第一款搭载手动自动一体化设计、电动旋转摄像头的

使用以下命令生成 .so (shared object)文件

炎炎的夏季又到了,我们习惯了躲在空调屋里的感觉,但有时不得不驾车外出,这时候如果钻进一辆暴晒了一中午的车时,那种酸爽,不可言表。因此,一上车甚至未上车之前就要想办法打开空调了。夏季开空调是再平常不过的事了,但是细心的朋友会发现,空调开了,自己爽了,但跟着油耗也涨了。但具体会增加多少油耗呢?我们今天就来探讨一下这个问题。夏季开空调,油耗大概会上升多少?这个问题很容易理解,但不容易说清。因为空调运转会消耗发动机的功率,不仅导致油耗增加,还会导致“车无力”。通常情况下,汽车空调约占发动机功率的20%,也就是一般百公里油耗为10升的车,开空调的状态下油耗会增加2升左右,由此可见,开空调对油耗的影响力还

gcc -shared -o prime.so -fPIC prime.c

import ctypes import math from timeit import timeit check_prime_in_c = ctypes.CDLL('./prime.so').check_prime def check_prime_in_py(x): values = xrange(2, int(math.sqrt(x)) + 1) for i in values: if x % i == 0: return False return True def get_prime_in_c(n): return [x for x in xrange(2, n) if check_prime_in_c(x)] def get_prime_in_py(n): return [x for x in xrange(2, n) if check_prime_in_py(x)] py_time = timeit(stmt='get_prime_in_py(1000000)', setup='from __main__ import get_prime_in_py', number=10) c_time = timeit(stmt='get_prime_in_c(1000000)', setup='from __main__ import get_prime_in_c', number=10) print "Python version: {} seconds".format(py_time) print "C version: {} seconds".format(c_time)

输出

可以的调用,python中一般有两种方法调用DLL中的函数:1、直接使用函数名,函数名可以用de

Python version: 43.4539749622 seconds C version: 8.56250786781 seconds

我们可以看到很明显的性能差距 这里有更多的方法去判断一个数是否是素数

再来看一个复杂点的例子 快速排序

mylib.c

#include <stdio.h> typedef struct _Range { int start, end; } Range; Range new_Range(int s, int e) { Range r; r.start = s; r.end = e; return r; } void swap(int *x, int *y) { int t = *x; *x = *y; *y = t; } void quick_sort(int arr[], const int len) { if (len <= 0) return; Range r[len]; int p = 0; r[p++] = new_Range(0, len - 1); while (p) { Range range = r[--p]; if (range.start >= range.end) continue; int mid = arr[range.end]; int left = range.start, right = range.end - 1; while (left < right) { while (arr[left] < mid && left < right) left++; while (arr[right] >= mid && left < right) right--; swap(&arr[left], &arr[right]); } if (arr[left] >= arr[range.end]) swap(&arr[left], &arr[range.end]); else left++; r[p++] = new_Range(range.start, left - 1); r[p++] = new_Range(left + 1, range.end); } }

gcc -shared -o mylib.so -fPIC mylib.c

使用ctypes有一个麻烦点的地方是原生的C代码使用的类型可能跟Python不能明确的对应上来。比如这里什么是Python中的数组?列表?还是 array 模块中的一个数组。所以我们需要进行转换

test.py

import ctypes import time import random quick_sort = ctypes.CDLL('./mylib.so').quick_sort nums = [] for _ in range(100): r = [random.randrange(1, 100000000) for x in xrange(100000)] arr = (ctypes.c_int * len(r))(*r) nums.append((arr, len(r))) init = time.clock() for i in range(100): quick_sort(nums[i][0], nums[i][1]) print "%s" % (time.clock() - init)

输出

1.874907

与Python list 的 sort 方法进行对比

import ctypes import time import random quick_sort = ctypes.CDLL('./mylib.so').quick_sort nums = [] for _ in range(100): nums.append([random.randrange(1, 100000000) for x in xrange(100000)]) init = time.clock() for i in range(100): nums[i].sort() print "%s" % (time.clock() - init)

输出

可以的调用,python中一般有两种方法调用DLL中的函数:1、直接使用函数名,函数名可以用de

2.501257

至于结构体,需要定义一个类,包含相应的字段和类型

class Point(ctypes.Structure): _fields_ = [('x', ctypes.c_double), ('y', ctypes.c_double)]

除了导入我们自己写的C语言扩展文件,我们还可以直接导入系统提供的库文件,比如linux下c标准库的实现 glibc

import time import random from ctypes import cdll libc = cdll.LoadLibrary('libc.so.6') # Linux系统 # libc = cdll.msvcrt # Windows系统 init = time.clock() randoms = [random.randrange(1, 100) for x in xrange(1000000)] print "Python version: %s seconds" % (time.clock() - init) init = time.clock() randoms = [(libc.rand() % 100) for x in xrange(1000000)] print "C version : %s seconds" % (time.clock() - init)

输出

可以的调用,python中一般有两种方法调用DLL中的函数:1、直接使用函数名,函数名可以用de

Python version: 0.850172 seconds C version : 0.27645 seconds

总结

以上就是这篇文章的全部内容,希望对大家学习或使用Python能有一定的帮助,如果有疑问大家可以留言交流。

扩展阅读,根据您访问的内容系统为您准备了以下内容,希望对您有帮助。

C程序用Python做插件该怎么实现

Python 是一种用于快速开发软件的编程语言,它的语法比较简单,易于掌握,但存在执行速度慢的问题,并且在处理某些问题时存在不足,如对计算机硬件系统的访问,对媒体文件的访问等。而作为软件开发的传统编程语言 C 语言,却能在这些问题上很好地弥补 Python 语言的不足。因此,本文通过实例研究如何在 Python 程序中整合既有的 C 语言模块,包括用 C 语言编写的源程序和动态链接库等,从而充分发挥 Python 语言和 C 语言各自的优势。

概览

背景知识介绍

Python 语言的特点

Python 作为一门程序开发语言,被越来越多地运用到快速程序开发。Python 是一种解释型的,互动的,面向对象的编程语言,它包含了模块化的操作,异常处理,动态资料形态,以及类型的使用。它的语法表达优美易读,具有很多优秀的脚本语言的特点:解释的,面向对象的,内建的高级数据结构,支持模块和包,支持多种平台,可扩展。而且它还支持交互式方式运行,图形方式运行。它拥有众多的编程界面支持各种操作系统平台以及众多的各类函数库,利用 C 和 C++ 可以对它进行扩充。

C 语言的特点

C 语言作为最受人们欢迎的语言之一,有广泛的发展基础。简洁紧凑、灵活方便,功能强大是其特点。另外,C 语言是一门中级语言。它把高级语言的基本结构和语句与低级语言的实用性结合起来。由于可以直接访问物理地址,可以方便的对硬件进行操作。因此,很多的系统软件都是由 C 语言编写。

Python 语言与 C 语言的交互

为了节省软件开发成本,软件开发人员希望能够缩短的软件的开发时间,希望能够在短时间内开发出稳定的产品。Python 功能强大,简单易用,能够快速开发应用软件。但是由于 Python 自身执行速度的局限性,对性能要求比较高的模块需要使用效率更高的程序语言进行开发,例如 C 语言,系统的其他模块运用 Python 进行快速开发,最后将 C 语言开发的模块与 Python 开发的模块进行整合。在此背景下,基于 Python 语言与 C 语言的各自特点,用 C 语言来扩展现有的 Python 程序,显得很有意义。本文首先介绍几种常用的整合 Python 程序与 C 语言程序的方法,最后给出相应的实例。

利用 ctypes 模块整合 Python 程序和 C 程序

ctypes 模块

ctypes 是 Python 的一个标准模块,它包含在 Python2.3 及以上的版本里。ctypes 是一个 Python 的高级外部函数接口,它使得 Python 程序可以调用 C 语言编译的静态链接库和动态链接库。运用 ctypes 模块,能够在 Python 源程序中创建,访问和操作简单的或复杂的 C 语言数据类型。最为重要的是 ctypes 模块能够在多个平台上工作,包括 Windows,Windows CE,Mac OS X,Linux,Solaris,FreeBSD,OpenBSD。

接下来通过几个简单的例子来看一下 ctypes 模块如何整合 Python 程序和 C 程序。

源代码层面上的整合

利用 Python 本身提供的 ctypes 模块可以使 Python 语言和 C 语言在源代码层面上进行整合。本节介绍了如何通过使用 ctypes 库,在 Python 程序中可以定义类似 C 语言的变量。

下表列出了 ctypes 变量类型,C 语言变量类型和 Python 语言变量类型之间的关系:

表 1. ctypes,c 语言和 Python 语言变量类型关系

表 1 中的第一列是在 ctypes 库中定义的变量类型,第二列是 C 语言定义的变量类型,第三列是 Python 语言在不使用 ctypes 时定义的变量类型。

举例:

清单 1. ctypes 简单使用

>>> from ctypes import *        # 导入 ctypes 库中所有模块

>>> i = c_int(45)            # 定义一个 int 型变量,值为 45

>>> i.value                # 打印变量的值

45

>>> i.value = 56             # 改变该变量的值为 56

>>> i.value                # 打印变量的新值

56

从下面的例子可以更明显地看出 ctypes 里的变量类型和 C 语言变量类型的相似性:

清单 2. ctypes 使用 C 语言变量

>>> p = create_string_buffer(10)   # 定义一个可变字符串变量,长度为 10

>>> p.raw                 # 初始值是全 0,即 C 语言中的字符串结束符' \0 '

'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

>>> p.value = "Student"         # 字符串赋值

>>> p.raw                 # 后三个字符仍是' \0 '

'Student\x00\x00\x00'

>>> p.value = "Big"           # 再次赋值

>>> p.raw                  # 只有前三个字符被修改,第四个字符被修改为' \0 '

'Big\x00ent\x00\x00\x00'

下面例子说明了指针操作:

清单 3. ctypes 使用 C 语言指针

>>> i = c_int(999)                 # 定义 int 类型变量 i,值为 999

>>> pi = pointer(i)                # 定义指针,指向变量 i

>>> pi.contents                   # 打印指针所指的内容

c_long(999)

>>> pi.contents = c_long(1000)          # 通过指针改变变量 i 的值

>>> pi.contents                   # 打印指针所指的内容

c_long(1000)

下面例子说明了结构和数组的操作:

清单 4. ctypes 使用 C 语言数组和结构体

>>> class POINT(Structure):         # 定义一个结构,内含两个成员变量 x,y,均为 int 型

...   _fields_ = [("x", c_int),

...         ("y", c_int)]

...

>>> point = POINT(2,5)           # 定义一个 POINT 类型的变量,初始值为 x=2, y=5

>>> print point.x, point.y           # 打印变量

2 5

>>> point = POINT(y=5)              # 重新定义一个 POINT 类型变量,x 取默认值

>>> print point.x, point.y           # 打印变量

0 5

>>> POINT_ARRAY = POINT * 3          # 定义 POINT_ARRAY 为 POINT 的数组类型

# 定义一个 POINT 数组,内含三个 POINT 变量

>>> pa = POINT_ARRAY(POINT(7, 7), POINT(8, 8), POINT(9, 9))

>>> for p in pa: print p.x, p.y        # 打印 POINT 数组中每个成员的值

...

7 7

8 8

9 9

Python 访问 C 语言 dll

通过 ctypes 模块,Python 程序可以访问 C 语言编译的 dll,本节通过一个简单的例子,Python 程序 helloworld.py 中调用 some.dll 中的 helloworld 函数,来介绍 Python 程序如何调用 windows 平台上的 dll。

导入动态链接库

清单 5. ctypes 导入 dll

from ctypes import windll # 首先导入 ctypes 模块的 windll 子模块

somelibc = windll.LoadLibrary(some.dll) # 使用 windll 模块的 LoadLibrary 导入动态链接库

访问动态链接库中的函数

清单 6. ctypes 使用 dll 中的函数

somelibc. helloworld() # 这样就可以得到 some.dll 的 helloworld 的返回值。

整个 helloworld.py 是这样的:

清单 7. Python hellpworld 代码

from ctypes import windll

def callc():

# load the some.dll

somelibc = windll.LoadLibrary(some.dll)

print somelibc. helloworld()

if __name__== “__main__”:

callc()

在命令行运行 helloworld.py,在 console 上可以看到 some.dll 中 helloworld 的输出。

清单 8. Python hellpworld Windows command console 运行输出

C:\>python C:\python\test\helloworld.py

Hello World! Just a simple test.

Python 调用 C 语言 so

通过 ctypes 模块,Python 程序也可以访问 C 语言编译的 so 文件。与 Python 调用 C 的 dll 的方法基本相同,本节通过一个简单的例子,Python 程序 helloworld.py 中调用 some.so 中的 helloworld 函数,来介绍 Python 程序如何调用 linux 平台上的 so。

导入动态链接库

清单 9. ctypes 导入 so

from ctypes import cdll

# 首先导入 ctypes 模块的 cdll 子模块,注意 linux 平台上使用 cdll 的,而不是 windll。

somelibc = cdll.LoadLibrary(“./some.so”)

# 使用 cdll 模块的 LoadLibrary 导入动态链接库

访问动态链接库中的函数

清单 10. ctypes 使用 so 中的函数

somelibc. helloworld() # 使用方法与 windows 平台上是一样的。

整个 helloworld.py 是这样的:

清单 11. Python helloworld 代码

from ctypes import cdll

def callc():

# load the some.so

somelibc = cdll.LoadLibrary(some.so)

print somelibc. helloworld()

if __name__== “__main__”:

callc()

在命令行运行 helloworld.py,在 linux 标准输出上可以看到 some.so 中 helloworld 的输出。

清单 12. Python hellpworld Linux shell 运行输出

[root@linux-790t] python ./helloworld.py

Hello World! Just a simple test.

Python 程序和 C 程序整合实例

以下我们举例用 Python 来实现一个小工具,用来实现 hash 算法,查看文件的校验和(MD5,CRC,SHA1 等等)。通过查看文件的校验和,可以知道文件在传输过程中是否被破坏或篡改。

Hash,一般翻译做“散列”,也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

由于相对 C 语言来说,Python 的运行效率较低,因此我们的 Python 小工具利用一个已有的 C 语言的动态链接库 (hashtcalc.dll) 来实现我们的程序。本例中,我们运用 wxPython 编写简单的 GUI 界面,通过 python 调用 hashtcalc.dll 的接口计算文件的校验和,然后输出在界面上。

架构图

图 1. 工具的架构图

hashcalc.dll 接口描述

函数名:calc_CRC32

函数:char* calc_CRC32(char *filename);

参数:文件名

返回值:字符串

说明:该函数对输入的文件内容进行计算,并且返回它的 CRC32

函数名:calc_MD5

函数:char* calc_MD5(char *filename);

参数:文件名

返回值:字符串

说明:该函数对输入的文件内容进行计算,并且返回它的 MD5

函数名:calc_SHA1

函数:char* calc_SHA1 (char *filename);

参数:文件名

返回值:字符串

说明:该函数对输入的文件内容进行计算,并且返回它的 SHA1

HashcalcAdapter 代码

HashcalcAdapter.py 实现了一个 python 的 class HashcalcAdapter,HashcalcAdapter 对 hashtcalc.dl 的 C 语言接口进行了封装,使得其他 python 模块可以直接通过 HashcalcAdapter 使用 hashtcalc.dll 中实现的 hash 算法。具体的代码如下:

清单 13. HashcalcAdapter.py 代码

from ctypes import windll

from ctypes import *

class HashcalcAdapter(object):

def __init__(self, dllpath):

self._dllpath = dllpath

self._libc = windll.LoadLibrary(self._dllpath)

def calc_CRC32(self, filename):

new_filename = c_char_p(filename)

return self._libc.calc_CRC32(new_filename)

def calc_MD5(self, filename):

new_filename = c_char_p(filename)

return self._libc.calc_MD5(new_filename)

def calc_SHA1(self, filename):

new_filename = c_char_p(filename)

return self._libc.calc_SHA1(new_filename)

运行界面

图 2. 工具的运行界面

Python如何利用多核处理器

GIL 与 Python 线程的纠葛

GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题。运行下面这段 python 程序,CPU 占用率是多少?

# 请勿在工作中模仿,危险:)def dead_loop():    while True:        passdead_loop()

答案是什么呢,占用 100% CPU?那是单核!还得是没有超线程的古董 CPU。在我的双核 CPU 上,这个死循环只会吃掉我一个核的工作负荷,也就是只占用 50% CPU。那如何能让它在双核机器上占用 100% 的 CPU 呢?答案很容易想到,用两个线程就行了,线程不正是并发分享 CPU 运算资源的吗。可惜答案虽然对了,但做起来可没那么简单。下面的程序在主线程之外又起了一个死循环的线程

import threadingdef dead_loop():    while True:        pass# 新起一个死循环线程t = threading.Thread(target=dead_loop)t.start()# 主线程也进入死循环dead_loop()t.join()

按道理它应该能做到占用两个核的 CPU 资源,可是实际运行情况却是没有什么改变,还是只占了 50% CPU 不到。这又是为什么呢?难道 python 线程不是操作系统的原生线程?打开 system monitor 一探究竟,这个占了 50% 的 python 进程确实是有两个线程在跑。那这两个死循环的线程为何不能占满双核 CPU 资源呢?其实幕后的黑手就是 GIL。

GIL 的迷思:痛并快乐着

GIL 的全称为 Global Interpreter Lock ,意即全局解释器锁。在 Python 语言的主流实现 CPython 中,GIL 是一个货真价实的全局线程锁,在解释器解释执行任何 Python 代码时,都需要先获得这把锁才行,在遇到 I/O 操作时会释放这把锁。如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过sys.setcheckinterval 来调整)。所以虽然 CPython 的线程库直接封装操作系统的原生线程,但 CPython 进程做为一个整体,同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处于等待状态等着 GIL 的释放。这也就解释了我们上面的实验结果:虽然有两个死循环的线程,而且有两个物理 CPU 内核,但因为 GIL 的*,两个线程只是做着分时切换,总的 CPU 占用率还略低于 50%。

看起来 python 很不给力啊。GIL 直接导致 CPython 不能利用物理多核的性能加速运算。那为什么会有这样的设计呢?我猜想应该还是历史遗留问题。多核 CPU 在 1990 年代还属于类科幻,Guido van Rossum 在创造 python 的时候,也想不到他的语言有一天会被用到很可能 1000+ 个核的 CPU 上面,一个全局锁搞定多线程安全在那个时代应该是最简单经济的设计了。简单而又能满足需求,那就是合适的设计(对设计来说,应该只有合适与否,而没有好与不好)。怪只怪硬件的发展实在太快了,摩尔定律给软件业的红利这么快就要到头了。短短 20 年不到,代码工人就不能指望仅仅靠升级 CPU 就能让老软件跑的更快了。在多核时代,编程的免费午餐没有了。如果程序不能用并发挤干每个核的运算性能,那就意谓着会被淘汰。对软件如此,对语言也是一样。那 Python 的对策呢?

Python 的应对很简单,以不变应万变。在最新的 python 3 中依然有 GIL。之所以不去掉,原因嘛,不外以下几点:

  • 欲练神功,挥刀自宫:

    CPython 的 GIL 本意是用来保护所有全局的解释器和环境状态变量的。如果去掉 GIL,就需要多个更细粒度的锁对解释器的众多全局状态进行保护。或者采用 Lock-Free 算法。无论哪一种,要做到多线程安全都会比单使用 GIL 一个锁要难的多。而且改动的对象还是有 20 年历史的 CPython 代码树,更不论有这么多第三方的扩展也在依赖 GIL。对 Python 社区来说,这不异于挥刀自宫,重新来过。

  • 就算自宫,也未必成功:

    有位牛人曾经做了一个验证用的 CPython,将 GIL 去掉,加入了更多的细粒度锁。但是经过实际的测试,对单线程程序来说,这个版本有很大的性能下降,只有在利用的物理 CPU 超过一定数目后,才会比 GIL 版本的性能好。这也难怪。单线程本来就不需要什么锁。单就锁管理本身来说,锁 GIL 这个粗粒度的锁肯定比管理众多细粒度的锁要快的多。而现在绝大部分的 python 程序都是单线程的。再者,从需求来说,使用 python 绝不是因为看中它的运算性能。就算能利用多核,它的性能也不可能和 C/C++ 比肩。费了大力气把 GIL 拿掉,反而让大部分的程序都变慢了,这不是南辕北辙吗。

  • 难道 Python 这么优秀的语言真的仅仅因为改动困难和意义不大就放弃多核时代了吗?其实,不做改动最最重要的原因还在于:不用自宫,也一样能成功!

  • 其它神功

    那除了切掉 GIL 外,果然还有方法让 Python 在多核时代活的滋润?让我们回到本文最初的那个问题:如何能让这个死循环的 Python 脚本在双核机器上占用 100% 的 CPU?其实最简单的答案应该是:运行两个 python 死循环的程序!也就是说,用两个分别占满一个 CPU 内核的 python 进程来做到。确实,多进程也是利用多个 CPU 的好方法。只是进程间内存地址空间独立,互相协同通信要比多线程麻烦很多。有感于此,Python 在 2.6 里新引入了 multiprocessing这个多进程标准库,让多进程的 python 程序编写简化到类似多线程的程度,大大减轻了 GIL 带来的不能利用多核的尴尬。

    这还只是一个方法,如果不想用多进程这样重量级的解决方案,还有个更彻底的方案,放弃 Python,改用 C/C++。当然,你也不用做的这么绝,只需要把关键部分用 C/C++ 写成 Python 扩展,其它部分还是用 Python 来写,让 Python 的归 Python,C 的归 C。一般计算密集性的程序都会用 C 代码编写并通过扩展的方式集成到 Python 脚本里(如 NumPy 模块)。在扩展里就完全可以用 C 创建原生线程,而且不用锁 GIL,充分利用 CPU 的计算资源了。不过,写 Python 扩展总是让人觉得很复杂。好在 Python 还有另一种与 C 模块进行互通的机制 : ctypes

    利用 ctypes 绕过 GIL

    ctypes 与 Python 扩展不同,它可以让 Python 直接调用任意的 C 动态库的导出函数。你所要做的只是用 ctypes 写些 python 代码即可。最酷的是,ctypes 会在调用 C 函数前释放 GIL。所以,我们可以通过 ctypes 和 C 动态库来让 python 充分利用物理内核的计算能力。让我们来实际验证一下,这次我们用 C 写一个死循环函数

  • extern"C"{  void DeadLoop()  {    while (true);  }}
  • 用上面的 C 代码编译生成动态库 libdead_loop.so (Windows 上是 dead_loop.dll)

    ,接着就要利用 ctypes 来在 python 里 load 这个动态库,分别在主线程和新建线程里调用其中的 DeadLoop

  • from ctypes import *from threading import Threadlib = cdll.LoadLibrary("libdead_loop.so")t = Thread(target=lib.DeadLoop)t.start()lib.DeadLoop()
  • 这回再看看 system monitor,Python 解释器进程有两个线程在跑,而且双核 CPU 全被占满了,ctypes 确实很给力!需要提醒的是,GIL 是被 ctypes 在调用 C 函数前释放的。但是 Python 解释器还是会在执行任意一段 Python 代码时锁 GIL 的。如果你使用 Python 的代码做为 C 函数的 callback,那么只要 Python 的 callback 方法被执行时,GIL 还是会跳出来的。比如下面的例子:

  • extern"C"{  typedef void Callback();  void Call(Callback* callback)  {    callback();  }}
  • from ctypes import *from threading import Threaddef dead_loop():    while True:        passlib = cdll.LoadLibrary("libcall.so")Callback = CFUNCTYPE(None)callback = Callback(dead_loop)t = Thread(target=lib.Call, args=(callback,))t.start()lib.Call(callback)
  • 注意这里与上个例子的不同之处,这次的死循环是发生在 Python 代码里 (DeadLoop 函数) 而 C 代码只是负责去调用这个 callback 而已。运行这个例子,你会发现 CPU 占用率还是只有 50% 不到。GIL 又起作用了。

    其实,从上面的例子,我们还能看出 ctypes 的一个应用,那就是用 Python 写自动化测试用例,通过 ctypes 直接调用 C 模块的接口来对这个模块进行黑盒测试,哪怕是有关该模块 C 接口的多线程安全方面的测试,ctypes 也一样能做到。

    结语

    虽然 CPython 的线程库封装了操作系统的原生线程,但却因为 GIL 的存在导致多线程不能利用多个 CPU 内核的计算能力。好在现在 Python 有了易经筋(multiprocessing), 吸星*(C 语言扩展机制)和独孤九剑(ctypes),足以应付多核时代的挑战,GIL 切还是不切已经不重要了,不是吗。

python用ctypes操作剪切板遇到问题!!

这边执行没有问题,版本如下

Python 3.5.0 (v3.5.0:374f501f4567, Sep 13 *, 02:27:37) [MSC v.1900 64 bit (AMD64)] on win32

代码如下

import ctypes

def get(): 

    '''从剪切板中获得字符串'''

    h=ctypes.WinDLL('user32.dll')

    h.OpenClipboard(0) 

    aa=h.GetClipboardData(13) 

    ss=ctypes.c_wchar_p(aa) 

    h.CloseClipboard()

    return ss.value

def set(mystr):

    '''把字符串放到剪切板中,成功返回1,失败返回0'''

    u=ctypes.WinDLL('user32.dll')

    k=ctypes.WinDLL('kernel32.dll')

    s=mystr.encode('utf-16')

    s=s[2:]+b'\0\0'

    ss=ctypes.c_char_p(s)

    u.OpenClipboard(0)

    u.EmptyClipboard()

    k.GlobalAlloc.argtypes=[ctypes.c_uint32,ctypes.c_uint32]

    try:

        cb=k.GlobalAlloc(0,len(s))

        cb=ctypes.c_void_p(cb)

        print(type(cb))

        ctypes.memmove(cb,ss,len(s))

        rr=u.SetClipboardData(13,cb) # 13->unicode

    finally:

        u.CloseClipboard()

    if rr==0:

        return 0

    else:

        return 1

#-----

set("abcdefg")

程序返回<class 'ctypes.c_void_p'>

Python使用Ctypes调用lib,怎么使用指针类型参数接收输出参数

本文演示了在python中调用C语言生成的动态库,返回结构体指针,并进行输出!

test.c(动态库源代码)

// 编译生成动态库: gcc -g -fPIC -shared -o libtest.so test.c

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

typedef struct StructPointerTest

{

char name[20];

int age;

}StructPointerTest, *StructPointer;

StructPointer test()// 返回结构体指针

{

StructPointer p = (StructPointer)malloc(sizeof(StructPointerTest));

strcpy(p->name, "Joe");

p->age = 20;

return p;

}

编译:gcc -g -fPIC -shared -o libtest.so test.c

call.py(python调用C语言生成的动态库):

#!/bin/env python

# coding=UTF-8

from ctypes import *

#python中结构体定义

class StructPointer(Structure):

_fields_ = [("name", c_char * 20), ("age", c_int)]

if __name__ == "__main__":

lib = cdll.LoadLibrary("./libtest.so")

lib.test.restype = POINTER(StructPointer)

p = lib.test()

print "%s: %d" %(p.contents.name, p.contents.age)

最后运行结果:

[zcm@c_py #112]$make clean

rm -f *.o libtest.so

[zcm@c_py #113]$make

gcc -g -fPIC -shared -o libtest.so test.c

[zcm@c_py #114]$./call.py

Joe: 20

[zcm@c_py #115]$

  • 本文相关:
  • Python随机数random模块使用指南
  • python验证码识别的实例详解
  • 通过5个知识点轻松搞定Python的作用域
  • Python冒泡排序注意要点实例详解
  • Python 中的with关键字使用详解
  • Python优化技巧之利用ctypes提高执行速度
  • Python 如何访问外围作用域中的变量
  • Python中使用asyncio 封装文件读写
  • asyncio 的 coroutine对象 与 Future对象使用指南
  • 基于asyncio 异步协程框架实现收集B站直播弹幕
  • 免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved