Introduction

计算机编程可以看成是将数据以特定的方式移动和装换,当今大多数的编程语言将数据在硬件上的操作抽象出来, 于是通过这样使用高级编程语言了解计算机底层看起来无用, 然而当理解了数据在硬件层面的移动方式和Python在抽象层面移动数据的方式,将能掌握一些高性能的编码方式, 例如使用并发技术代替循环。

计算机底层组件 = 计算单元 + 存储单元 + 前二者之间的连接

上述等式中有3个速度: 计算单元运算速度、存储单元读写速度、连接将数据转移的速度
通常一个标准工作站:CPU(计算单元)、RAM+DISK(存储单元)、总线(连接)

计算单元

计算单元的两个主要属性
1. 每个周期能进行的操作数量 -> 每周期完成的指令数(IPC)
2. 每秒完成的周期数 -> 时钟速度

这两个属性参数相互竞争, 提高主频能够提高计算单元上所有程序的运行速度, 提高IPC能影响计算单元的矢量计算能力(一次提供多个数据给一个CPU并能同时被操作,是典型的单指令多数据(SIMD))。
其他技术:超线程技术、乱序执行、多核架构。

给CPU增加更多的核心并不一能提高程序的运行速度,阿姆达尔定律指出程序的某些执行路径运行在单核上,那么这些路径就会成为瓶颈导致最终速度无法通过增加核心来提高,例如所有的任务都分配到核心此时瓶颈是单个CPU执行的路径

存储单元

读写速度与容量成反比, 我们能做的优化是优化数据存放的位置、布局,以及数据在不同那个位置之间移动的次数,异步I/O和缓存预取等

通信层

使用总线技术,例如前端总线是RAM和L1/L2缓存之间的连接,后端总线是CPU和L1/L2缓存之间的连接。 总线的主要属性是其速度 1. 一次传输数据量 -> 总线带宽
2. 每秒传输次数 -> 总线频率

用Python谈什么高性能

很久之前我一直认为,使用Python就和性能无缘,与其追求性能不如多用C/C++写点库胶到Python上,与性能最相关的是开发者。
然而Python的优势是快速成型,有想法立刻可以开工,不用在乎太多数据结构,代码简明,但是函数参数类型随便传是不能忍的,编程语言作为工具要保持一致,稳定;在综合考虑效率后决定以Python高性能编程为基础,讨论更深刻的编码优化技术。

计算模型和Python虚拟机

例子 判断一个数是否是素数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import math
def check_prime(number):
    sqrt_number = math.sqrt(number) 
    number_float = float(number)
    for i in range(2, int(sqrt_number)+1):
        if (number_float / i).is_integer():
            return False
    return True

print("check_prime(10000000) = ", check_prime(10000000)) # False
print("check_prime(10000019) = ", check_prime(10000019)) # True

理想的计算模型

number存在RAM中,sqrt_numbernumber_float需要经过CPU计算并返回
number理想情况下传输一次,即保存在L1/L2缓存中, 使用后端总线传输

在循环中一次可以输入多个number_floati到CPU进行检查,通过使用矢量计算,将number_float传入CPU缓存,然后传入尽可能多的i, 一次计算多个number_float / i并检查结果:其中是否有整数(理想下发生在CPU中),之后返回一个信号表明是否有任意一个结果是整数。如果是,函数结束;如果否,继续下一批计算。如此我们只用传回一个结果,而不是依靠总线返回所有的值,实现了在一个时钟周期内以一条指令操作了多个数据。

Python虚拟机

Python虚拟机/解释器,虚拟机的抽象影响到了矢量化操作变得不可直接用,Python对象不是内存中的最优布局,内存会被自动分配并在需要时释放,导致了内存碎片并影响CPU缓存的传输。
另外其“解释性”导致无法优化内存布局和CPU指令上的优化。
最后GIL影响并行代码的性能,多线程变得鸡肋。

抉择

在快速成型和可维性上考虑,软件的开发成本最高的在维护阶段,基于Python的优化最终会让代码变得不可维护,虽然能够实验各种点子,但是整个代码优化期将逐渐拖慢团队的开发,开发应该是面向原理的编程,项目前期使用Python足以,留好接口用C系实现大部分功能完成替换Python。