2017年11月

首页2017年11月
30
Nov
0

随机函数

  1. 随机种子,不写默认以当前时间作为种子
    random.seed(10)

print random.random() #0.57140259469

  1. 操作系统随机数 不可再生
  2. = time.time()

r1 = random.SystemRandom()
r2 = random.SystemRandom()
r1 = random.SystemRandom(seed)
r2 = random.SystemRandom(seed)

  1. python自带各随机函数
    random.random

random.random()用于生成一个0到1的随机符点数: 0 <= n < 1.0
random.uniform
random.uniform(a, b),用于生成一个指定范围内的随机符点数,两个参数其中一个是上限,一个是下限。如果a > b,则生成的随机数n: a <= n <= b。如果 a <b, 则 b <= n <= a
代码如下:
print random.uniform(10, 20)
print random.uniform(20, 10)

18.7356606526

12.5798298022

random.randint
random.randint(a, b),用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n: a <= n <= b
代码如下:
print random.randint(12, 20) # 生成的随机数 n: 12 <= n <= 20
print random.randint(20, 20) # 结果永远是20

print random.randint(20, 10) # 该语句是错误的。下限必须小于上限

random.randrange
random.randrange([start], stop[, step]),从指定范围内,按指定基数递增的集合中 获取一个随机数。如:random.randrange(10, 100, 2),结果相当于从[10, 12, 14, 16, ... 96, 98]序列中获取一个随机数。random.randrange(10, 100, 2)在结果上与 random.choice(range(10, 100, 2) 等效
random.choice
random.choice从序列中获取一个随机元素。其函数原型为:random.choice(sequence)。参数sequence表示一个有序类型。这里要说明 一下:sequence在python不是一种特定的类型,而是泛指一系列的类型。list, tuple, 字符串都属于sequence。有关sequence可以查看python手册数据模型这一章。下面是使用choice的一些例子:
代码如下:
print random.choice("学习Python")
print random.choice(["JGood", "is", "a", "handsome", "boy"])
print random.choice(("Tuple", "List", "Dict"))
random.shuffle
random.shuffle(x[, random]),用于将一个列表中的元素打乱。如:
代码如下:
p = ["Python", "is", "powerful", "simple", "and so on..."]
random.shuffle(p)
print p

['powerful', 'simple', 'is', 'Python', 'and so on...']

random.sample
random.sample(sequence, k),从指定序列中随机获取指定长度的片断。sample函数不会修改原有序列
代码如下:
list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
slice = random.sample(list, 5) # 从list中随机获取5个元素,作为一个片断返回
print slice
print list # 原有序列并没有改变
随机整数:
代码如下:

import random
random.randint(0,99)

21

随机选取0到100间的偶数:
代码如下:

import random
random.randrange(0, 101, 2)

42

随机浮点数:
代码如下:

import random
random.random()

0.85415370477785668

random.uniform(1, 10)

5.4221167969800881

随机字符:
代码如下:

import random
random.choice('abcdefg&#%^*f')

'd'

多个字符中选取特定数量的字符:
代码如下:

import random

random.sample('abcdefghij', 3)

['a', 'd', 'b']

多个字符中选取特定数量的字符组成新字符串:
代码如下:

import random
import string
string.join( random.sample(['a','b','c','d','e','f','g','h','i','j'], 3) ).replace(" ","")

'fih'

随机选取字符串:
代码如下:

import random
random.choice ( ['apple', 'pear', 'peach', 'orange', 'lemon'] )

'lemon'

洗牌:
代码如下:

import random
items = [1, 2, 3, 4, 5, 6]
random.shuffle(items)
items

[3, 2, 5, 6, 4, 1]

29
Nov
0

rabbitMQ的生产者和消费者简单封装

主要用到pika包,由于实在太简单,直接丢代码好了

import pika

class Producer(object):

    def __init__(self,connection_str,username,passwd,queue_tunnel):
        if connection_str.find(':') == -1:
            raise Exception('connection str wrong')
        _ip = connection_str[:connection_str.find(':')]
        _port = connection_str[connection_str.find(':') + 1:]
        credentials = pika.PlainCredentials(username, passwd)
        connection = pika.BlockingConnection(pika.ConnectionParameters(_ip, _port, '/', credentials))#5672
        channel = connection.channel()
        channel.queue_declare(queue=queue_tunnel)#'request.trace.log'
        self.channel = channel

    def send(self,message):
        return self.channel.basic_publish(exchange='',
                              routing_key='request.trace.log',
                              body=message)


class Consumer(object):
    def __init__(self, connection_str, username, passwd, queue_tunnel):
        if connection_str.find(':') == -1:
            raise Exception('connection str wrong')
        _ip = connection_str[:connection_str.find(':')]
        _port = connection_str[connection_str.find(':') + 1:]
        credentials = pika.PlainCredentials(username, passwd)
        connection = pika.BlockingConnection(pika.ConnectionParameters(_ip, _port, '/', credentials))  # 5672
        channel = connection.channel()
        channel.queue_declare(queue=queue_tunnel)  # 'request.trace.log'
        channel.basic_consume(self.callback,
                              queue=queue_tunnel,
                              no_ack=True)
        # no_ack=True  # 写的话,如果接收消息,机器宕机消息就丢了
        # 一般不写。宕机则生产者检测到发给其他消费者
        self.channel = channel
        # channel.start_consuming()

    def callback(self,ch, method, properties, body):
        # print('get mq data success')
        from ..func_plus import FuncHelper
        print(" [x] mq Received %r" % FuncHelper.bytes_str_decode_str(body))
        # ch.basic_ack(delivery_tag=method.delivery_tag)  # 告诉生成者,消息处理完成

    def start(self):
        self.channel.start_consuming()
29
Nov
0

利用tracekback跟踪栈以及打印异常信息

sys.exc_info()

返回 (type, value, traceback). type为异常类型, value为异常的参数(通常为异常错误的信息), traceback为跟踪回溯的对象.

exc_type, exc_value, exc_traceback = sys.exc_info()
print "*** print sys.exc_info:"
print 'exc_type is: %s, exc_value is: %s, exc_traceback is: %s' % (exc_type, exc_value, exc_traceback)

输出:

* print sys.exc_info:
exc_type is: <type 'exceptions.IndexError'>, exc_value is: tuple index out of range, exc_traceback is: <traceback object at 0x7fee3b00eb48>

traceback.print_tb(traceback[, limit[, file]])

打印栈的跟踪信息. 如果省略limit, 将打印所有跟踪入口信息. file默认为std.err.

print "*** print_tb:"
traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)

输出:

* print_tb:
File "t.py", line 13, in <module>

mock()

traceback.print_exception(type, value, traceback[, limit[, file]])

打印异常信息. (type, value, traceback)为sys.exc_info()返回的元组.和print_tb不同的是:

  • 如果traceback不为空, 打印栈头信息(即最近被调用的信息).
  • 在栈的信息后打印异常类型和常的参数.
  • 如果是语法错误, 会打印对应的代码行数, 用”^”指明语法错误的位置.

    print "* print_exception:"
    traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2, file=sys.stdout)

输出:

* print_exception:
Traceback (most recent call last):
File "t.py", line 13, in <module>

mock()

File "t.py", line 4, in mock

lumberjack()

IndexError: tuple index out of range

traceback.print_exc([limit[, file]])

print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)的简写.

print "*** print_exc:"
traceback.print_exc()

输出:

* print_exc:
Traceback (most recent call last):
File "t.py", line 13, in <module>

mock()

File "t.py", line 4, in mock

lumberjack()

File "t.py", line 7, in lumberjack

bright_side_of_death()

File "t.py", line 10, in bright_side_of_death

return tuple()[1]

IndexError: tuple index out of range

traceback.format_exc([limit])

类似于print_exc(limit), 但是返回字符串而不是输出到file.

print "*** format_exc, first and last line:"
formatted_lines = traceback.format_exc().splitlines()
print formatted_lines[0]
print formatted_lines[-1]

输出:

* format_exc, first and last line:
Traceback (most recent call last):
IndexError: tuple index out of range

traceback.format_exception(type, value, tb[, limit])

格式化栈信息和异常信息. 返回一个列表, 包括代码文件和代码行, 以及异常信息.

print "*** format_exception:"
print repr(traceback.format_exception(exc_type, exc_value, exc_traceback))

输出:

['Traceback (most recent call last):n', ' File "t.py", line 13, in <module>n mock()n', ' File "t.py", line 10, in mockn lumberjack()n', ' File "t.py", line 4, in lumberjackn bright_side_of_death()n', ' File "t.py", line 7, in bright_side_of_deathn return tuple()[0]n', 'IndexError: tuple index out of rangen']
1
traceback.extract_tb(traceback[, limit])

返回一个跟踪对象(traceback)的元组列表. 元组内容为(filename, line number, function name, text).

print "*** extract_tb:"
print repr(traceback.extract_tb(exc_traceback))

输出:

[('t.py', 13, '<module>', 'mock()'), ('t.py', 4, 'mock', 'lumberjack()'), ('t.py', 7, 'lumberjack', 'bright_side_of_death()'), ('t.py', 10, 'bright_side_of_death', 'return tuple()[1]')]

traceback.extract_stack([f[, limit]])

返回当前栈帧的原始跟踪(traceback)对象的信息, 格式和extract_tb一样, 元组内容为(filename, line number, function name, text).

print "*** extract_stack:"
print traceback.extract_stack()

输出:

* extract_stack:
[('t.py', 47, '<module>', 'print traceback.extract_stack()')]
1
2
traceback.format_list(list)

按照list对应的项, 返回一个元组列表, 形式为同extract_tb()或者extract_stack()返回的一样. 元组内容为(filename, line number, function name, text)
将extract_tb()或者extract_stack()返回的list进行格式化.

print traceback.format_list([('spam.py', 3, '<module>', 'spam.eggs()'), ('eggs.py', 42, 'eggs', 'return "bacon"')])
1
输出:

[' File "spam.py", line 3, in <module>n spam.eggs()n', ' File "eggs.py", line 42, in eggsn return "bacon"n']

traceback.format_tb(tb[, limit])

format_list(extract_tb(tb, limit))的简写.

traceback.format_stack([f[, limit]])

format_list(extract_stack(f, limit))的简写

traceback.tb_lineno(tb)

返回traceback对象设置的行数

实例

import sys, traceback

def mock():

lumberjack()

def lumberjack():

bright_side_of_death()

def bright_side_of_death():

return tuple()[1]

try:

mock()

except IndexError:

exc_type, exc_value, exc_traceback = sys.exc_info()
print "*** print sys.exc_info:"
print 'exc_type is: %s, exc_value is: %s, exc_traceback is: %s' % (exc_type, exc_value, exc_traceback)
print "-" *  100

print "*** print_tb:"
traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
print "-" *  100

print "*** print_exception:"
traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2, file=sys.stdout)
print "-" *  100

print "*** print_exc:"
traceback.print_exc()
print "-" *  100

print "*** format_exc, first and last line:"
formatted_lines = traceback.format_exc().splitlines()
print formatted_lines[0]
print formatted_lines[-1]
print "-" *  100

print "*** format_exception:"
print repr(traceback.format_exception(exc_type, exc_value, exc_traceback))
print "-" *  100

print "*** extract_tb:"
print repr(traceback.extract_tb(exc_traceback))
print "-" *  100

print "*** extract_stack:"
print traceback.extract_stack()
print "-" *  100

print "*** format_tb:"
print repr(traceback.format_tb(exc_traceback))
print "-" *  100

print "*** tb_lineno:", exc_traceback.tb_lineno

print traceback.format_list([('spam.py', 3, '<module>', 'spam.eggs()'), ('eggs.py', 42, 'eggs', 'return "bacon"')])

输出:

* print sys.exc_info:

exc_type is: <type 'exceptions.IndexError'>, exc_value is: tuple index out of range, exc_traceback is: <traceback object at 0x7f7d659bab48>

* print_tb:
File "t.py", line 13, in <module>

mock()

* print_exception:
Traceback (most recent call last):
File "t.py", line 13, in <module>

mock()

File "t.py", line 4, in mock

lumberjack()

IndexError: tuple index out of range

* print_exc:
Traceback (most recent call last):
File "t.py", line 13, in <module>

mock()

File "t.py", line 4, in mock

lumberjack()

File "t.py", line 7, in lumberjack

bright_side_of_death()

File "t.py", line 10, in bright_side_of_death

return tuple()[1]

IndexError: tuple index out of range

* format_exc, first and last line:
Traceback (most recent call last):

IndexError: tuple index out of range

* format_exception:

['Traceback (most recent call last):n', ' File "t.py", line 13, in <module>n mock()n', ' File "t.py", line 4, in mockn lumberjack()n', ' File "t.py", line 7, in lumberjackn bright_side_of_death()n', ' File "t.py", line 10, in bright_side_of_deathn return tuple()[1]n', 'IndexError: tuple index out of rangen']

* extract_tb:

[('t.py', 13, '<module>', 'mock()'), ('t.py', 4, 'mock', 'lumberjack()'), ('t.py', 7, 'lumberjack', 'bright_side_of_death()'), ('t.py', 10, 'bright_side_of_death', 'return tuple()[1]')]

* extract_stack:

[('t.py', 47, '<module>', 'print traceback.extract_stack()')]

* format_tb:

[' File "t.py", line 13, in <module>n mock()n', ' File "t.py", line 4, in mockn lumberjack()n', ' File "t.py", line 7, in lumberjackn bright_side_of_death()n', ' File "t.py", line 10, in bright_side_of_deathn return tuple()[1]n']

* tb_lineno: 13
[' File "spam.py", line 3, in <module>n spam.eggs()n', ' File "eggs.py", line 42, in eggsn return "bacon"n']

27
Nov
0

operator下的两个函数methodcaller,attrgetter

  1. methodcaller
    和内置函数getattr差不多,用字符的形式调用函数
from operator import methodcaller 
s=”abc123abc” 
s.find(‘abc’,4)   #6
methodcaller(‘find’,’abc’,4)(s)   #6

2.attrgetter
返回一个可调用的对象,该对象从运算中获取 'attr' 。如果请求的属性不止一个的话, 返回属性的元组

f = attrgetter('name', 'date')
f(b)   # (b.name, b.date)
24
Nov
0

性能优化

注意:本文除非特殊指明,”python“都是代表CPython,即C语言实现的标准python,且本文所讨论的是版本为2.7的CPython。另外,本文会不定期更新,如果大家有一些好的想法,请在评论里面留言,我会补充到文章中去。

姊妹篇:《Python内存优化》

姊妹篇:《使用gc、objgraph干掉python内存泄露与循环引用!》
python为什么性能差:
回到顶部
  当我们提到一门编程语言的效率时:通常有两层意思,第一是开发效率,这是对程序员而言,完成编码所需要的时间;另一个是运行效率,这是对计算机而言,完成计算任务所需要的时间。编码效率和运行效率往往是鱼与熊掌的关系,是很难同时兼顾的。不同的语言会有不同的侧重,python语言毫无疑问更在乎编码效率,life is short,we use python。

  虽然使用python的编程人员都应该接受其运行效率低的事实,但python在越多越来的领域都有广泛应用,比如科学计算 、web服务器等。程序员当然也希望python能够运算得更快,希望python可以更强大。
  首先,python相比其他语言具体有多慢,这个不同场景和测试用例,结果肯定是不一样的。这个网址给出了不同语言在各种case下的性能对比,这一页是python3和C++的对比,下面是两个case:

  

  从上图可以看出,不同的case,python比C++慢了几倍到几十倍。

  
  python运算效率低,具体是什么原因呢,下列罗列一些

  第一:python是动态语言
  一个变量所指向对象的类型在运行时才确定,编译器做不了任何预测,也就无从优化。举一个简单的例子: r = a + b。 a和b相加,但a和b的类型在运行时才知道,对于加法操作,不同的类型有不同的处理,所以每次运行的时候都会去判断a和b的类型,然后执行对应的操作。而在静态语言如C++中,编译的时候就确定了运行时的代码。
  另外一个例子是属性查找,关于具体的查找顺序在《python属性查找》中有详细介绍。简而言之,访问对象的某个属性是一个非常复杂的过程,而且通过同一个变量访问到的python对象还都可能不一样(参见Lazy property的例子)。而在C语言中,访问属性用对象的地址加上属性的偏移就可以了。

  第二:python是解释执行,但是不支持JIT(just in time compiler)。虽然大名鼎鼎的google曾经尝试Unladen Swallow 这个项目,但最终也折了。

  第三:python中一切都是对象,每个对象都需要维护引用计数,增加了额外的工作。

  第四:python GIL
  GIL是Python最为诟病的一点,因为GIL,python中的多线程并不能真正的并发。如果是在IO bound的业务场景,这个问题并不大,但是在CPU BOUND的场景,这就很致命了。所以笔者在工作中使用python多线程的情况并不多,一般都是使用多进程(pre fork),或者在加上协程。即使在单线程,GIL也会带来很大的性能影响,因为python每执行100个opcode(默认,可以通过sys.setcheckinterval()设置)就会尝试线程的切换,具体的源代码在ceval.c::PyEval_EvalFrameEx。

  第五:垃圾回收,这个可能是所有具有垃圾回收的编程语言的通病。python采用标记和分代的垃圾回收策略,每次垃圾回收的时候都会中断正在执行的程序,造成所谓的顿卡。infoq上有一篇文章,提到禁用Python的GC机制后,Instagram性能提升了10%。感兴趣的读者可以去细读。
Be pythonic
回到顶部
  我们都知道 过早的优化是罪恶之源,一切优化都需要基于profile。但是,作为一个python开发者应该要pythonic,而且pythonic的代码往往比non-pythonic的代码效率高一些,比如:
使用迭代器iterator,for example:

  dict的iteritems 而不是items(同itervalues,iterkeys)
  使用generator,特别是在循环中可能提前break的情况

判断是否是同一个对象使用 is 而不是 ==
判断一个对象是否在一个集合中,使用set而不是list
利用短路求值特性,把“短路”概率过的逻辑表达式写在前面。其他的lazy ideas也是可以的
对于大量字符串的累加,使用join操作
使用for else(while else)语法
交换两个变量的值使用: a, b = b, a

基于profile的优化
回到顶部
  即使我们的代码已经非常pythonic了,但可能运行效率还是不能满足预期。我们也知道80/20定律,绝大多数的时间都耗费在少量的代码片段里面了,优化的关键在于找出这些瓶颈代码。方式很多:到处加log打印时间戳、或者将怀疑的函数使用timeit进行单独测试,但最有效的是使用profile工具。

python profilers
  对于python程序,比较出名的profile工具有三个:profile、cprofile和hotshot。其中profile是纯python语言实现的,Cprofile将profile的部分实现native化,hotshot也是C语言实现,hotshot与Cprofile的区别在于:hotshot对目标代码的运行影响较小,代价是更多的后处理时间,而且hotshot已经停止维护了。需要注意的是,profile(Cprofile hotshot)只适合单线程的python程序。
  对于多线程,可以使用yappi,yappi不仅支持多线程,还可以精确到CPU时间
  对于协程(greenlet),可以使用greenletprofiler,基于yappi修改,用greenlet context hook住thread context

  下面给出一段编造的”效率低下“的代码,并使用Cprofile来说明profile的具体方法以及我们可能遇到的性能瓶颈。
  
code for profile

  运行结果如下:

  

  对于上面的的输出,每一个字段意义如下:

  ncalls 函数总的调用次数
  tottime 函数内部(不包括子函数)的占用时间
  percall(第一个) tottime/ncalls
  cumtime 函数包括子函数所占用的时间
  percall(第二个)cumtime/ncalls
  filename:lineno(function) 文件:行号(函数)
  
  代码中的输出非常简单,事实上可以利用pstat,让profile结果的输出多样化,具体可以参见官方文档python profiler。
  
profile GUI tools
  虽然Cprofile的输出已经比较直观,但我们还是倾向于保存profile的结果,然后用图形化的工具来从不同的维度来分析,或者比较优化前后的代码。查看profile结果的工具也比较多,比如,visualpytune、qcachegrind、runsnakerun,本文用visualpytune做分析。对于上面的代码,按照注释生成修改后重新运行生成test.prof文件,用visualpytune直接打开就可以了,如下:

  字段的意义与文本输出基本一致,不过便捷性可以点击字段名排序。左下方列出了当前函数的calller(调用者),右下方是当前函数内部与子函数的时间占用情况。上如是按照cumtime(即该函数内部及其子函数所占的时间和)排序的结果。
  造成性能瓶颈的原因通常是高频调用的函数、单次消耗非常高的函数、或者二者的结合。在我们前面的例子中,foo就属于高频调用的情况,bar属于单次消耗非常高的情况,这都是我们需要优化的重点。
  python-profiling-tools中介绍了qcachegrind和runsnakerun的使用方法,这两个colorful的工具比visualpytune强大得多。具体的使用方法请参考原文,下图给出test.prof用qcachegrind打开的结果

  qcachegrind确实要比visualpytune强大。从上图可以看到,大致分为三部:。第一部分同visualpytune类似,是每个函数占用的时间,其中Incl等同于cumtime, Self等同于tottime。第二部分和第三部分都有很多标签,不同的标签标示从不同的角度来看结果,如图上所以,第三部分的“call graph”展示了该函数的call tree并包含每个子函数的时间百分比,一目了然。

profile针对优化
  知道了热点,就可以进行针对性的优化,而这个优化往往根具体的业务密切相关,没用万能钥匙,具体问题,具体分析。个人经验而言,最有效的优化是找产品经理讨论需求,可能换一种方式也能满足需求,少者稍微折衷一下产品经理也能接受。次之是修改代码的实现,比如之前使用了一个比较通俗易懂但效率较低的算法,如果这个算法成为了性能瓶颈,那就考虑换一种效率更高但是可能难理解的算法、或者使用dirty Flag模式。对于这些同样的方法,需要结合具体的案例,本文不做赘述。
  接下来结合python语言特性,介绍一些让python代码不那么pythonic,但可以提升性能的一些做法
第一:减少函数的调用层次

每一层函数调用都会带来不小的开销,特别对于调用频率高,但单次消耗较小的calltree,多层的函数调用开销就很大,这个时候可以考虑将其展开。

  对于之前调到的profile的代码,foo这个call tree非常简单,但频率高。修改代码,增加一个plain_foo()函数, 直接返回最终结果,关键输出如下:
  
  跟之前的结果对比:

  

  可以看到,优化了差不多3倍。

第二:优化属性查找

上面提到,python 的属性查找效率很低,如果在一段代码中频繁访问一个属性(比如for循环),那么可以考虑用局部变量代替对象的属性。

第三:关闭GC
  在本文的第一章节已经提到,关闭GC可以提升python的性能,GC带来的顿卡在实时性要求比较高的应用场景也是难以接受的。但关闭GC并不是一件容易的事情。我们知道python的引用计数只能应付没有循环引用的情况,有了循环引用就需要靠GC来处理。在python语言中, 写出循环引用非常容易。比如:
  case 1:
  a, b = SomeClass(), SomeClass()
  a.b, b.a = b, a
   
  case 2:
  lst = []
  lst.append(lst)

  case 3:
  self.handler = self.some_func
  当然,大家可能说,谁会这么傻,写出这样的代码,是的,上面的代码太明显,当中间多几个层级之后,就会出现“间接”的循环应用。在python的标准库 collections里面的OrderedDict就是case2:
  
  要解决循环引用,第一个办法是使用弱引用(weakref),第二个是手动解循环引用。

  
第四:setcheckinterval
  如果程序确定是单线程,那么修改checkinterval为一个更大的值,这里有介绍。

第五:使用__slots__
  slots最主要的目的是用来节省内存,但是也能一定程度上提高性能。我们知道定义了__slots__的类,对某一个实例都会预留足够的空间,也就不会再自动创建__dict__。当然,使用__slots__也有许多注意事项,最重要的一点,继承链上的所有类都必须定义__slots__,python doc有详细的描述。下面看一个简单的测试例子:

复制代码
1 class BaseSlots(object):
2 slots = ['e', 'f', 'g']
3
4 class Slots(BaseSlots):
5 slots = ['a', 'b', 'c', 'd']
6 def __init__(self):
7 self.a = self.b = self.c = self.d = self.e = self.f = self.g = 0
8
9 class BaseNoSlots(object):
10 pass
11
12 class NoSlots(BaseNoSlots):
13 def __init__(self):
14 super(NoSlots,self).__init__()
15 self.a = self.b = self.c = self.d = self.e = self.f = self.g = 0
16
17 def log_time(s):
18 begin = time.time()
19 for i in xrange(10000000):
20 s.a,s.b,s.c,s.d, s.e, s.f, s.g
21 return time.time() - begin
22
23 if name == '__main__':
24 print 'Slots cost', log_time(Slots())
25 print 'NoSlots cost', log_time(NoSlots())
复制代码
  输出结果:

Slots cost 3.12999987602
NoSlots cost 3.48100018501

python C扩展
回到顶部
  也许通过profile,我们已经找到了性能热点,但这个热点就是要运行大量的计算,而且没法cache,没法省略。。。这个时候就该python的C扩展出马了,C扩展就是把部分python代码用C或者C++重新实现,然后编译成动态链接库,提供接口给其它python代码调用。由于C语言的效率远远高于python代码,所以使用C扩展是非常普遍的做法,比如我们前面提到的cProfile就是基于_lsprof.so的一层封装。python的大所属对性能有要求的库都使用或者提供了C扩展,如gevent、protobuff、bson。
  笔者曾经测试过纯python版本的bson和cbson的效率,在综合的情况下,cbson快了差不多10倍!
  python的C扩展也是一个非常复杂的问题,本文仅给出一些注意事项:
第一:注意引用计数的正确管理
  这是最难最复杂的一点。我们都知道python基于指针技术来管理对象的生命周期,如果在扩展中引用计数出了问题,那么要么是程序崩溃,要么是内存泄漏。更要命的是,引用计数导致的问题很难debug。。。
  C扩展中关于引用计数最关键的三个词是:steal reference,borrowed reference,new reference。建议编写扩展代码之前细读python的官方文档。

第二:C扩展与多线程
  这里的多线程是指在扩展中new出来的C语言线程,而不是python的多线程,出了python doc里面的介绍,也可以看看《python cookbook》的相关章节。

第三:C扩展应用场景
  仅适合与业务代码的关系不那么紧密的逻辑,如果一段代码大量业务相关的对象 属性的话,是很难C扩展的

  将C扩展封装成python代码可调用的接口的过程称之为binding,Cpython本身就提供了一套原生的API,虽然使用最为广泛,但该规范比较复杂。很多第三方库做了不同程度的封装,以便开发者使用,比如boost.python、cython、ctypes、cffi(同时支持pypy cpython),具体怎么使用可以google。

beyond CPython
回到顶部
  尽管python的性能差强人意,但是其易学易用的特性还是赢得越来越多的使用者,业界大牛也从来没有放弃对python的优化。这里的优化是对python语言设计上、或者实现上的一些反思或者增强。这些优化项目一些已经夭折,一些还在进一步改善中,在这个章节介绍目前还不错的一些项目。
cython
  前面提到cython可以用到binding c扩展,但是其作用远远不止这一点。
  Cython的主要目的是加速python的运行效率,但是又不像上一章节提到的C扩展那么复杂。在Cython中,写C扩展和写python代码的复杂度差不多(多亏了Pyrex)。Cython是python语言的超集,增加了对C语言函数调用和类型声明的支持。从这个角度来看,cython将动态的python代码转换成静态编译的C代码,这也是cython高效的原因。使用cython同C扩展一样,需要编译成动态链接库,在linux环境下既可以用命令行,也可以用distutils。

  如果想要系统学习cython,建议从cython document入手,文档写得很好。下面通过一个简单的示例来展示cython的使用方法和性能(linux环境)。
  首先,安装cython:
  pip install Cython
  下面是测试用的python代码,可以看到这两个case都是运算复杂度比较高的例子:
复制代码

-- 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()

复制代码
  运行结果:
  call f cost: 0.215116024017
  call integrate_f cost: 4.33698010445

  不改动任何python代码也可以享受到cython带来的性能提升,具体做法如下:
step1:将文件名(cython_example.py)改为cython_example.pyx
step2:增加一个setup.py文件,添加一下代码:
复制代码
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 )
复制代码
step3:执行python setup.py build_ext --inplace
    
    可以看到 增加了两个文件,对应中间结果和最后的动态链接库
step4:执行命令 python -c "import cython_example;cython_example.main()"(注意: 保证当前环境下已经没有 cython_example.py)
  运行结果:
  call f cost: 0.0874309539795
  call integrate_f cost: 2.92381191254

  性能提升了大概两倍,我们再来试试cython提供的静态类型(static typing),修改cython_example.pyx的核心代码,替换f()和integrate_f()的实现如下:

复制代码
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()的实现如下:
  
复制代码
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
复制代码

  然后重新运行上面的第三 四步:结果如下
  call f cost: 1.19209289551e-06
  call integrate_f cost: 0.187038183212

  Amazing!

pypy
  pypy是CPython的一个替代实现,其最主要的优势就是pypy的速度,下面是官网的测试结果:
  
  在实际项目中测试,pypy大概比cpython要快3到5倍!pypy的性能提升来自JIT Compiler。在前文提到google的Unladen Swallow 项目也是想在CPython中引入JIT,在这个项目失败后,很多开发人员都开始加入pypy的开发和优化。另外pypy占用的内存更少,而且支持stackless,基本等同于协程。

  pypy的缺点在于对C扩展方面支持的不太好,需要使用CFFi来做binding。对于使用广泛的library来说,一般都会支持pypy,但是小众的、或者自行开发的C扩展就需要重新封装了。