为什么从Python中的列表中删除不必要的项目时计算时间会减少

杰罗姆·鲍

在过去的几天里,我一直在尝试更好地理解计算复杂性以及如何改进Python代码。为此,我尝试了不同的函数来计算斐波那契数,比较了如果我做了一些小的改动,脚本将运行多长时间。

我正在使用列表计算斐波那契数,将列表中元素-2和-1的总和相加。

我很困惑地发现,如果在循环中添加.pop(),删除列表中不需要的元素,我的脚本运行速度将大大提高。我不明白为什么会这样。循环中的每一步,计算机都会做另外一件事。因此,我未经训练的直觉表明这应该增加计算时间。当列表很长时,“查找”列表的最后一个元素会慢很多吗?

这是我的代码:

import time
import numpy as np

def fib_stack1(n):
    """ Original function """
    assert type(n) is int, 'Expected an integer as input.'
    if n < 2:
        return n
    else:
        stack = [0, 1]
        for i in range(n-1):
            stack.append(stack[-1] + stack[-2])
        return stack[-1]

def fib_stack2(n):
    """ Modified function """
    assert type(n) is int, 'Expected an integer as input.'
    if n < 2:
        return n
    else:
        stack = [0, 1]
        for i in range(n-1):
            stack.append(stack[-1] + stack[-2])
            ### CHANGE ###
            stack.pop(-3)
            ##############
        return stack[-1] 


rec1 = []
rec2 = []
for _ in range(10):
    t1 = time.time()
    fib_stack1(99999)  
    t2 = time.time()
    rec1.append(t2-t1)
    t1 = time.time()
    fib_stack2(99999)  
    t2 = time.time()
    rec2.append(t2-t1)
print(np.array(rec1).mean())
print(np.array(rec2).mean())

输出如下:

# Original 
0.26878631115
# Modified
0.145034956932
user2357112支持Monica

当列表很长时,“查找”列表的最后一个元素会慢很多吗?

不,列表长度对查找速度没有影响。这些是数组列表,而不是链接列表。这很可能与内存分配或缓存性能有关。还涉及垃圾收集器。

当您删除不需要的列表元素时,Python永远不必为列表分配更大的缓冲区。它也可能能够重用为int对象分配的内存,而不是从OS请求更多的内存。考虑到整数有多大,重用它们的内存很重要。(内存分配的详细信息取决于Python版本和基础标准库分配器。Python2拥有ints的可用列表,而不是longs; Python 3没有ints的可用列表。Python本身不努力重用大型的分配对象,但基础分配器可能正在做某事。)

另外,当您必须继续分配新的整数,尤其是与第99999个斐波那契数一样大的整数时,您将不会从CPU的缓存中获得太多好处。主内存访问比缓存慢得多。

最后,您的分配模式fib_stack1(大量分配,没有太多的对象引用计数降为0)触发了Python的循环检测器系统(又名垃圾收集器),该系统需要时间才能运行,并且会占用大量不需要触摸的内存,损害缓存性能。在我自己的测试中,特别是在Python 3上,暂时禁用收集器会产生明显的加速fib_stack1

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章