循环是Python中常用的编码方式,常用来遍历多个数据或多个元素,以及对Python中固定的集合进行迭代处理等。循环由于需要重复执行相同的步骤,因此在执行时间和内存消耗上存在一定的优化项。Python中的循环优化大体上可以分为两个方向:第一个方向是减少相同的循环次数;第二个方向是减少不必要的循环处理操作,即在处理多条数据时,能不使用循环就不使用循环,改为采用对于单任务的多线程调用执行的方式。
我们先来看Python中简单的循环处理方式,代码如下:
上述代码将固定10次循环的索引值进行了打印,如图13-1所示。
对于for循环,在上述代码固定循环10次范围内,索引值从0开始输出,一直输出到9,共计10个数据。上述简单的for循环代码对应的Python字节码如图13-2所示。
从图13-2可知,忽略range()方法对于for循环本体执行的影响,重点来看两个字节码指令——GET_ITER字节码指令和FOR_ITER字节码指令。GET_ITER字节码指令和FOR_ITER字节码指令分别用来获取for循环迭代器和执行具体的循环操作。GET_ITER字节码指令在获取到for循环迭代器时会直接通知FOR_ITER字节码指令执行具体循环动作,在这个过程中一般不会出现字节码指令执行的断层现象,所以我们在对for循环进行优化时,是不能对具体的循环执行过程进行优化的。
FOR_ITER字节码指令在接收到for循环迭代器的指令通知后,就会开始执行具体的循环动作。在执行具体的循环动作时,FOR_ITER字节码指令会首先获取到由GET_ITER字节码指令返回的循环范围(该循环范围通过LOAD_CONST指令载入对应的内存)。在获取到循环范围之后,FOR_ITER字节码指令会将循环的输出内容指向下一步变量存储的内存,该部分内存也通过LOAD_CONST指令进行载入。
在了解了for循环的工作原理之后,我们需要对上述代码进行性能测试。测试上述代码执行时间的代码如下:
重复执行上述代码5次,以测试上述代码的执行耗时,结果如表13-1所示。
从表13-1中可以看出,上述5次for循环中执行所需的最长时间是4.55μs,最短时间是4.33μs,整体稳定在4.4μs左右。这个时间基本上可以满足一般的程序执行要求,但是如果循环数据量变大,再使用上述循环进行处理,所耗费的时间成本和空间成本也会随之飙升。为此,我们可以对上述代码进行优化。优化后的代码在执行大批量数据循环处理任务时可以比之前节省近一半的时间开销,笔者这里给出大批量数据循环处理代码,具体如下:
在上述代码中,笔者将之前的for循环代码封装到一个名为forSimple的方法中,将优化后的代码封装到一个名为generatorSimple的方法中。为了打印之前代码的循环执行结果,笔者这里在generatorSimple方法中定义了一个长度一样的List集合,并手动填充testList集合中的元素,使打印的数据类型、大小与之前for循环中打印的数据类型、大小保持一致。f
orSimple方法和generatorSimple方法中均采用了和之前for循环中测试执行速度相同的测试代码,这样可以做到近似相同的测试流程。forSimple()方法和generatorSimple()方法的程序执行结果相同,这里不再对输出内容进行展示,直接看优化后的对比结果。
对于forSimple()方法和generatorSimple()方法的执行结果,我们还是采用和之前测试次数相同的流程,即执行5次固定循环,分别统计程序的执行时间。上述两个方法的测试执行时间如表13-2所示。
可以看出,generatorSimple ()方法在执行相同的循环代码时,所需要的时间稳定在2.5μs左右,forSimple()方法在执行相同的循环代码时,所需要的时间基本在4.3~4.5μs。从整体来看,forSimple()方法要比generatorSimple ()方法慢,而相差的时间开销在每次执行for循环时,都可以再执行一次generatorSimple ()方法,可见差别还是很大的。
上述代码的优化整体采用的是Python内部集合数据类型的迭代器,这种迭代器和传统的for循环在表面看起来基本上是一样的,但是内部的实现原理完全不同。上述代码的Python字节码(关键部分)如图13-3所示。
从图13-3中可知,当执行for循环操作时,forSimple()方法被GET_ITER和FOR_ITER字节码指令执行完后就没有了后续操作,可以说整个执行过程都交给了CPU去处理,随机性较大,时间也不好把握,完全看CPU的执行状态。generatorSimple ()方法在执行时,先是通过BUILD_LIST字节码指令构建List集合,然后使用LIST_EXTEND字节码指令声明准备使用List集合类型的迭代器进行迭代,接着才交给CPU去处理,这样就降低了CPU执行的随机性,节省了一定的执行时间。