第十八章:语言工具-dis:Python字节码反汇编工具-循环的性能分析

18.3.6 循环的性能分析
除了调试错误,dis还有助于发现性能问题。检查反汇编的代码对于紧密循环尤其有用,在这些循环中,Python指令很少,但是这些指令会转换为一组效率很低的字节码。可以通过查看一个类Dictionary的不同实现来了解反汇编提供的帮助,这个类会读取一个单词列表,然后按其首字母分组。

import dis
import sys
import textwrap
import timeit

module_name = sys.argv[1]
module = __import__(module_name)
Dictionary = module.Dictionary

dis.dis(Dictionary.load_data)
print()
t = timeit.Timer(
    'd = Dictionary(words)',
    textwrap.dedent("""
    from {module_name} import Dictionary
    words = [
        l.strip()
        for l in open('/usr/share/dict/words','rt')
        ]
        """).format(module_name=module_name)
    )
iterations = 10
print('TIME: {:0.4f}'.format(t.timeit(iterations) / iterations))

可以用测试驱动应用dis_test_loop.py来运行Dictionary类的各个实现,首先是一个简单但很慢的实现。

#!/usr/bin/env python3
# encoding: utf-8


class Dictionary:

    def __init__(self,words):
        self.by_letter = {}
        self.load_data(words)

    def load_data(self,words):
        for word in words:
            try:
                self.by_letter[word[0]].append(word)
            except KeyError:
                self.by_letter[word[0]] = [word]

用这个版本运行测试程序时,会显示反汇编的程序,以及运行所花费的时间。
在这里插入图片描述
前面的输出显示,dis_slow_loop.py花费了0.0108秒来加载单词,这个性能不算太坏,不过相应的反汇编结果显示出循环做了很多不必要的工作。它在操作码16处进入循环时,程序建立了一个异常上下文(SETUP_EXCEPT)。然后在将word追加到列表之前,使用了6个操作码来查找self.by_letter [word[0]]。如果由于word[0]还不在字典中而生成一个异常,那么异常处理器会做完全相同的工作来确定word0,并把self.by_letter[word[0]]设置为包含这个单词的一个新列表。要避免建立这个异常,一种技术是对应字母表中的各个字母分布用一个列表来预填充字典self.by_letter。这意味着总会找到新单词相应的列表,可以在查找之后保存值。
在这里插入图片描述
将self.by_letter的查找移到循环之外(毕竟值没有改变),可以进一步提高性能。

#!/usr/bin/env python3
# encoding: utf-8

import collections


class Dictionary:

    def __init__(self,words):
        self.by_letter = collections.defaultdict(list)
        self.load_data(words)


    def load_data(self,words):
        by_letter = self.by_letter
        for word in words:
            by_letter[word[0]].append(word)

现在操作码0~6会查找self.by_letter的值,并把它保存为一个局部变量by_letter。使用局部变量值需要一个操作码,而不是两个(语句22使用LOAD_FAST将字典放在栈中)。做了这个修改之后,运行时间降至0.0089秒。
在这里插入图片描述

Brandon Rhodes还建议了进一步的优化,可以完全消除Python版本的for循环。如果使用itertools.groupby()来整理输入,那么将把迭代处理移至C。这个转移很安全,因为输入已经是有序的。如果并非如此,则程序需要先进行排序。

#!/usr/bin/env python3
# encoding: utf-8

import operator
import itertools


class Dictionary:

    def __init__(self,words):
        self.by_letter = {}
        self.load_data(words)

    def load_data(self,words):
        # Arrange by letter.
        grouped = itertools.groupby(
            words,
            key=operator.itemgetter(0),
            )
        # Save arranged sets of words.
        self.by_letter = {
            group[0][0]: group
            for group in grouped
            }

这个itertools版本运行只需要0.0048秒,仅为原程序运行时间的约40%。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值