# 奇奇怪怪的python优化点

# == or os

比较操作符'is'的速度效率,通常要优于'=='。因为'is'操作符不能被重载,这样,Python 就不需要去寻找,程序中是否有其他地方重载了比较操作符,并去调用。执行比较操作符'is',就仅仅是比较两个变量的 ID 而已。但是''操作符却不同,执行a == b相当于是去执行a.eq(b),而 Python 大部分的数据类型都会去重载__eq__这个函数,其内部的处理通常会复杂一些。比如,对于列表,__eq__函数会去遍历列表中的元素,比较它们的顺序和值是否相等。

# += or join

实际上由于字符串末尾大多也有空闲空间(内存对齐)的缘故直接做+=也不见得会触发扩容导致多次复制,性能没有想象中的那么差,但是实验下来join的效率基本都是+=的数倍。

import random
import time

def test(list_len):
    l = []
    for i in range(list_len):
        # l.append(''.join([str(random.randint(1,10)) for _ in range(random.randint(1,20))]))
        l.append(str(i))
    print(f'len(l): {len(l)}')

    start = time.time()
    s1 = ''
    for i in l:
        s1 += i
    end = time.time()
    add_time = end - start
    print(f'add: {add_time}')

    start = time.time()
    s2 = ''.join(l)
    end = time.time()
    join_time = end - start
    print(f'join: {join_time}')

    print(f'add/join: {add_time/join_time}')

for i in range(0, 7):
    test(10**i)

""" 
len(l): 1
add: 7.152557373046875e-07
join: 9.5367431640625e-07
add/join: 0.75
len(l): 10
add: 2.1457672119140625e-06
join: 9.5367431640625e-07
add/join: 2.25
len(l): 100
add: 1.33514404296875e-05
join: 2.1457672119140625e-06
add/join: 6.222222222222222
len(l): 1000
add: 7.009506225585938e-05
join: 1.5974044799804688e-05
add/join: 4.388059701492537
len(l): 10000
add: 0.0007255077362060547
join: 0.0001995563507080078
add/join: 3.6356033452807646
len(l): 100000
add: 0.006604194641113281
join: 0.0031599998474121094
add/join: 2.089935113927871
len(l): 1000000
add: 0.08632159233093262
join: 0.028325557708740234
add/join: 3.0474807669646315
"""

# list() or []

想创建一个空的列表,我们可以用下面的 A、B 两种方式,请问它们在效率上有什么区别吗?我们应该优先考虑使用哪种呢?可以说说你的理由。

# 创建空列表
# option A
empty_list = list()

# option B
empty_list = []

区别主要在于list()是一个function call,Python的function call会创建stack,并且进行一系列参数检查的操作,比较expensive,反观[]是一个内置的C函数,可以直接被调用,因此效率高。 两者差别还是蛮大的。

>>> import dis
>>> dis.dis('l=list()')
  0           0 RESUME                   0

  1           2 PUSH_NULL
              4 LOAD_NAME                0 (list)
              6 PRECALL                  0
             10 CALL                     0
             20 STORE_NAME               1 (l)
             22 LOAD_CONST               0 (None)
             24 RETURN_VALUE
>>> dis.dis('l=[]')
  0           0 RESUME                   0

  1           2 BUILD_LIST               0
              4 STORE_NAME               0 (l)
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

# s_add = s.add

s = set() 
set_add = s.add 
for data in source:   
    set_add(data)

通过在字节码层面减少执行一个LOAD_ATTR去提升一些性能,这个提升应该是observable的,在你的for loop比较大的时候。

# truthy value - container or numeric value

在binomialvariate这个函数里,出现了这么一个判断:

c = _log2(1.0 - p)
if not c:
    return x

涉及到Python底层字节码的实现问题,if not c确实比if c == 0要更快。但是我的观点是,运行速度并不是决定一段代码写法的唯一因素。我个人是比较可以接受对于container(比如list,dict,set这种)使用truthy value进行判断的,但是对于numeric value,我觉得if not c是一个很不理想的写法。在语义上,它是非常不明确的。