速度测试导致奇怪的行为。在一个实例中将100倍花费的时间乘以另一个实例中的10倍

时间:2013-06-19 18:55:39

标签: python performance unit-testing

我正在使用readFile,prepDict和test三个函数进行速度测试。测试只是prepDict(readFile)。然后我使用timeit模块运行了很多次。

当我将循环次数增加10倍时,函数prepDict需要大约100倍的时间,但使用函数prepDict的函数测试只会增加10倍。

以下是功能和测试。

def readFile(filepath):
    tempDict = {}
    file = open(filepath,'rb')
    for line in file:
        split = line.split('\t')
        tempDict[split[1]] = split[2]
    return tempDict

def prepDict(tempDict):
    for key in tempDict.keys():
        tempDict[key+'a'] = tempDict[key].upper()
        del tempDict[key]
    return tempDict

def test():
    prepDict(readFile('two.txt'))

if __name__=='__main__':
    from timeit import Timer
    t = Timer(lambda: readFile('two.txt'))
    print 'readFile(10000): ' + str(t.timeit(number=10000))

    tempDict = readFile('two.txt')
    t = Timer(lambda: prepDict(tempDict))
    print 'prepDict (10000): ' + str(t.timeit(number=10000))

    t = Timer(lambda: test())
    print 'prepDict(readFile) (10000): ' + str(t.timeit(number=10000))

    t = Timer(lambda: readFile('two.txt'))
    print 'readFile(100000): ' + str(t.timeit(number=100000))

    tempDict = readFile('two.txt')
    t = Timer(lambda: prepDict(tempDict))
    print 'prepDict (100000): ' + str(t.timeit(number=100000))

    t = Timer(lambda: test())
    print 'prepDict(readFile) (100000): ' + str(t.timeit(number=100000))

我得到的结果如下:

readFile(10000): 0.61602914474
prepDict (10000): 0.200615847469
prepDict(readFile) (10000): 0.609288647286
readFile(100000): 5.91858320729
prepDict (100000): 18.8842101717
prepDict(readFile) (100000): 6.45040039665

如果我多次运行,我会得到类似的结果。为什么prepDict增加了~100倍,而prepDict(readFile)只增加了10倍,即使它正在使用prepDict函数?

two.txt是一个带有这些数据点的表格分隔文件:

Item    Title   Hello2
Item    Desc    Testing1232
Item    Release 2011-02-03

3 个答案:

答案 0 :(得分:3)

这里的问题是你的prepDict函数会扩展输入。每次按顺序调用它时,它都有更多的数据需要处理。并且该数据线性增长,因此第10000次运行所需的时间约为第一次。*

当你致电test时,它每次都会创建一个新的dict,所以时间是不变的。

您可以通过更改prepDict测试来轻松地看到这一点,以便每次都在dict的新副本上运行:

t = Timer(lambda: prepDict(tempDict.copy()))

顺便说一句,您的prepDict实际上并没有以number呈指数增长**,只是呈二次方式。通常,当某些东西超线性增长,并且您想要估算算法成本时,您确实需要获得两个以上的数据点。


*那不是相当是真的 - 一旦字符串和散列操作(线性增长)所花费的时间开始淹没每个其他操作所花费的时间(它们是一切都不变。

**你在这里没有提到有关指数增长的任何内容,但在your previous question你做了,所以你可能在你的真正问题中做了同样的无根据假设。

答案 1 :(得分:1)

您对prepDict的来电不会发生在隔离的环境中。每次拨打prepDict都会修改tempDict - 密钥每次都会变长一些。因此,在对prepDict进行10 ** 5次调用后,prepDict中的键是相当大的字符串。如果你在prepDict中放置一个打印声明:

,你可以看到这个(大量)
def prepDict(tempDict):
    for key in tempDict.keys():
        tempDict[key+'a'] = tempDict[key].upper()
        del tempDict[key]
    print(tempDict)
    return tempDict

解决此问题的方法是确保每次调用prepDict - 或更一般地说,您正在计时的语句 - 不会影响您正在计时的下一个调用(或语句)。 abarnert已经展示了解决方案:prepDict(tempDict.copy())

顺便说一句,您可以使用for-loop来减少代码重复:

import timeit
import collections    

if __name__=='__main__':
    Ns = [10**4, 10**5]
    timing = collections.defaultdict(list)
    for N in Ns:
        timing['readFile'].append(timeit.timeit(
            "readFile('two.txt')",
            "from __main__ import readFile",
            number = N))
        timing['prepDict'].append(timeit.timeit(
            "prepDict(tempDict.copy())",
            "from __main__ import readFile, prepDict; tempDict = readFile('two.txt')",
            number = N))
        timing['test'].append(timeit.timeit(
            "test()",
            "from __main__ import test",
            number = N))

    print('{k:10}: {N[0]:7} {N[1]:7} {r}'.format(k='key', N=Ns, r='ratio'))
    for key, t in timing.iteritems():
        print('{k:10}: {t[0]:0.5f} {t[1]:0.5f} {r:>5.2f}'.format(k=key, t=t, r=t[1]/t[0]))

产生时间,例如

key       :   10000  100000 ratio
test      : 0.11320 1.12601  9.95
prepDict  : 0.01604 0.16167 10.08
readFile  : 0.08977 0.91053 10.14

答案 2 :(得分:0)

这种情况正在发生,因为当您仅测试tempDict时,您正在重复使用prepDict来调用prepDict的所有电话。由于prepDict遍历它所给出的字典中的所有项目,然后基本上只是将每个字符串键的长度增加一个,最终会得到一堆非常长的键。随着它的进展,这开始减慢你的功能,因为字符串连接操作正在使用/重新创建越来越大的字符串。

这不是test中的问题,因为您每次都会重新初始化字典。