当前位置: 首页 > 面试题库 >

比较列表推导和显式循环(3个数组生成器比1个循环快)

越俊艾
2023-03-14
问题内容

我做了作业,无意间发现了算法速度方面的奇怪不一致。这是具有相同功能bur的2个版本的代码,但有1个区别:在第一个版本中,我使用3倍数组生成器来过滤某些数组,在第二个版本中,我使用1个for循环与3个if语句来执行相同的过滤器工作。

因此,这是第一个版本的代码:

def kth_order_statistic(array, k):
    pivot = (array[0] + array[len(array) - 1]) // 2
    l = [x for x in array if x < pivot]
    m = [x for x in array if x == pivot]
    r = [x for x in array if x > pivot]
    if k <= len(l):
            return kth_order_statistic(l, k)
    elif k > len(l) + len(m):
            return kth_order_statistic(r, k - len(l) - len(m))
    else:
            return m[0]

这里是第二版的代码:

def kth_order_statistic2(array, k):
    pivot = (array[0] + array[len(array) - 1]) // 2
    l = []
    m = []
    r = []
    for x in array:
        if x < pivot:
            l.append(x)
        elif x > pivot:
            r.append(x)
        else:
            m.append(x)

    if k <= len(l):
        return kth_order_statistic2(l, k)
    elif k > len(l) + len(m):
        return kth_order_statistic2(r, k - len(l) - len(m))
    else:
        return m[0]

第1版的IPython输出:

In [4]: %%timeit
   ...: A = range(100000)
   ...: shuffle(A)
   ...: k = randint(1, len(A)-1)
   ...: order_statisctic(A, k)
   ...:
10 loops, best of 3: 120 ms per loop

对于第二版:

In [5]: %%timeit
   ...: A = range(100000)
   ...: shuffle(A)
   ...: k = randint(1, len(A)-1)
   ...: kth_order_statistic2(A, k)
   ...:
10 loops, best of 3: 169 ms per loop

那么,为什么第一个版本比第二个版本快?我还使用filter()函数代替了数组生成器,制作了第三个版本,它比第二个版本慢(每个循环218毫秒)


问题答案:

让我们定义回答问题并为其计时所需的功能:

In [18]: def iter():
    l = [x for x in range(100) if x > 10]
   ....:

In [19]: %timeit iter()
100000 loops, best of 3: 7.92 µs per loop

In [20]: def loop():
    l = []
    for x in range(100):
        if x > 10:
            l.append(x)
   ....:

In [21]: %timeit loop()
10000 loops, best of 3: 20 µs per loop

In [22]: def loop_fast():
    l = []
    for x in range(100):
        if x > 10:
            pass
   ....:

In [23]: %timeit loop_fast()
100000 loops, best of 3: 4.69 µs per loop

我们可以看到,没有append命令的for循环与列表理解一样快。实际上,如果我们看一下字节码,就会发现在列表理解的情况下,python能够使用名为LIST_APPEND的内置字节码命令来代替:

  • 加载列表:40 LOAD_FAST
  • 加载属性:43 LOAD_ATTRIBUTE
  • 调用已加载的函数:49 CALL_FUNCTION
  • 卸载列表(?):52 POP_TOP

从输出下面可以看到,列表理解和“ loop_fast”函数缺少前一个字节码。比较这三个函数的时间很明显,它们负责三种方法的不同时序。

In [27]: dis.dis(iter)
  2          0 BUILD_LIST             0
             3 LOAD_GLOBAL            0 (range)
             6 LOAD_CONST             1 (1)
             9 LOAD_CONST             2 (100)
            12 CALL_FUNCTION          2
            15 GET_ITER
       >>   16 FOR_ITER              24 (to 43)
            19 STORE_FAST             0 (x)
            22 LOAD_FAST              0 (x)
            25 LOAD_CONST             2 (100)
            28 COMPARE_OP             4 (>)
            31 POP_JUMP_IF_FALSE     16
            34 LOAD_FAST              0 (x)
            37 LIST_APPEND            2
            40 JUMP_ABSOLUTE         16
       >>   43 STORE_FAST             1 (l)
            46 LOAD_CONST             0 (None)
            49 RETURN_VALUE

In [28]: dis.dis(loop)
  2          0 BUILD_LIST             0
             3 STORE_FAST             0 (1)

  3          6 SETUP_LOOP            51 (to 60)
             9 LOAD_GLOBAL            0 (range)
            12 LOAD_CONST             1 (1)
            15 LOAD_CONST             2 (100)
            18 CALL_FUNCTION          2
            21 GET_ITER
       >>   22 FOR_ITER              34 (to 59)
            25 STORE_FAST             1 (x)

  4         28 LOAD_FAST              1 (x)
            31 LOAD_CONST             3 (10)
            34 COMPARE_OP             4 (>)
            37 POP_JUMP_IF_FALSE     22

  5         40 LOAD_FAST              0 (l)
            43 LOAD_ATTR              1 (append)
            46 LOAD_FAST              1 (x)
            49 CALL_FUNCTION          1
            52 POP_TOP
            53 JUMP_ABSOLUTE         22
            56 JUMP_ABSOLUTE         22
       >>   59 POP_BLOCK
       >>   60 LOAD_CONST             0 (None)
            63 RETURN_VALUE

In [29]: dis.dis(loop_fast)
  2          0 BUILD_LIST             0
             3 STORE_FAST             0 (1)

  3          6 SETUP_LOOP            38 (to 47)
             9 LOAD_GLOBAL            0 (range)
            12 LOAD_CONST             1 (1)
            15 LOAD_CONST             2 (100)
            18 CALL_FUNCTION          2
            21 GET_ITER
       >>   22 FOR_ITER              21 (to 46)
            25 STORE_FAST             1 (x)

  4         28 LOAD_FAST              1 (x)
            31 LOAD_CONST             3 (10)
            34 COMPARE_OP             4 (>)
            37 POP_JUMP_IF_FALSE     22

  5         40 JUMP_ABSOLUTE         22
            43 JUMP_ABSOLUTE         22
       >>   46 POP_BLOCK
       >>   47 LOAD_CONST             0 (None)
            50 RETURN_VALUE


 类似资料:
  • 我不能更改数组,它在每个元素中存储一个线程,所以我宁愿尽可能简单地避免损坏线程的内容(线程是同步化的,并使用ReentrantLock--所以问题只是关于数组)。 我想让这个for循环变得简单: 让我把它当成一个循环数组。我想过使用mudolo操作来实现这一点,但这也不起作用。以下是我尝试的:

  • 假设我有一个大小为[10]的数组,当该数组被填满时,我想实现一个FIFO结构,而不是它只是填满了,因此无法向数组中添加新的东西,并抛出旧的东西。 例如,如果我有一个包含汽车制造商的字符串数组,当我的数组中有10个制造商时,我希望删除最旧的条目,添加最新的条目,但要考虑kepping FIFO。我如何在这样的方法中实现它:

  • 我在尝试遍历两个哈希数组时遇到问题。我的哈希值如下所示: 在我检查h1和h2中“name”字段的哈希值后,我需要将这两个哈希组合在一个名为的数组中,以便: 如果--- 数组应如下所示: 我已经尝试了很多,并且总是有更多的迭代和/或在不同名称的情况下无法打印任何内容。 我的代码看起来像: 我也无法正确比较h1和h2的值。 非常感谢您的帮助,因为我对Ruby还很陌生,不幸的是,我的逻辑无法帮助我。 提

  • 我使用以下程序集和c源(分别使用fasm和gcc)将一些程序集与一些c链接起来,以测试函数调用的成本 组件: c来源: 我得到的结果令人惊讶。首先,速度取决于我链接的顺序。如果我以的形式链接,典型的输出是 但是以相反的顺序链接,我得到了一个更像的输出: 他们的不同令人惊讶,但这不是我要问的问题。(此处有相关问题) 我要问的问题是,在第二次运行中,有函数调用的循环如何比没有函数调用的循环快,调用函数

  • 问题内容: 在Python的性能方面,是一个列表理解或功能,如,和比for循环快?从技术上讲,为什么它们以C速度运行,而for循环以python虚拟机速度运行? 假设在我正在开发的游戏中,我需要使用for循环绘制复杂而庞大的地图。这个问题绝对是相关的,例如,如果列表理解确实确实更快,那么它将是避免滞后的更好选择(尽管代码具有视觉复杂性)。 问题答案: 以下是粗略的准则和基于经验的有根据的猜测。你应

  • 我有以下数组列表 现在我需要比较这两个数组,并检查是否有id中的任何值存在于empIds中。如果是,我需要以布尔值true退出。我是这样做的。 但这需要很多时间。有人能帮我优化一下吗?