【问题标题】:Performance difference on insert-sort in C and pythonC和python中插入排序的性能差异
【发布时间】:2009-10-13 17:04:50
【问题描述】:

我对使用 C 和 python 进行插入排序的性能很好奇,但我得到的结果让我想是不是我做错了什么。我怀疑 C 会更快,但没那么快。

我已经对这两个代码进行了概要分析,插入排序功能是花费时间最多的地方。

这里是 C 函数:

void
insert_sort (vec_t * vec)
{
    int j;
    for (j = 1 ; j < vec->n ; j++){
        int key = vec->v[j];
        int i = j - 1;
        while (i >= 0 && vec->v[i] > key){
            vec->v[i+1] = vec->v[i];
            i--;
        }
        vec->v[i+1] = key;
    }
}

这里是python函数:

def insert_sort (ln):
    for j in range(1, len(ln)):
        key = ln[j]
        i = j-1
        while i >= 0 and ln[i] > key:
            ln[i+1] = ln[i]
            i-=1
        ln[i+1] = key

测试使用 10000 个整数,每个整数在 0 到 10000 之间随机生成。

每个函数所用时间的结果是:

  • C 时间:0.13 秒
  • python 时间:8.104 秒

我在这里做错了吗?就像我说的,我希望看到 C 代码更快,但不是那么快。

我不想使用内置函数或其他任何东西。我想实现该算法。有没有一种 pythonic 方法可以在插入排序中使用?

【问题讨论】:

  • 显而易见的答案是pythonic方式是使用内置函数:)。但实际上,我认为代码没有任何问题。 C 被编译,Python 被解释,几乎没有什么比 C for 循环更快的了。它不需要像 Python 那样做几乎所有的工作,因为 Python 在执行代码时会进行引用计数、垃圾收集、获取/释放 GIL 以及其他许多事情。
  • Python 代码在虚拟机 (PVM) 上运行。所以在大多数情况下,plain Python 代码至少比相应的 C 代码慢一个数量级。
  • Python 作为一种解释性语言只慢了大约 60 倍。这在我所期望的范围内。时机看起来不错。
  • Python 的强大之处在于它的内置函数/类型(Python 或多或少仍然是一种胶水语言)。尝试编写一个广泛使用哈希表的算法,您将很难在 C 中使其更快。
  • 请提供有关您的测试方法的更多详细信息。在我的机器上对 10,000 个随机整数进行排序大约需要 6 毫秒。使用 python 的内置排序大约需要 400 微秒。使用 timeit.py 从命令行计时

标签: python c performance profiling


【解决方案1】:

Python 是一种动态语言,标准实现使用解释器来评估代码。这意味着编译后的 C 代码可以用一条机器指令转义,例如分配给 vec->v[i+1],Python 的解释器必须从本地范围查找序列变量,查找它的类,找到类上的项目设置方法,调用该方法。比较,加法也是如此。更不用说执行几乎每个字节码都会导致 CPU 中的间接分支错误预测,从而导致管道泡沫。

这种代码将从 JIT 编译到本机代码和运行时类型专业化中受益匪浅,就像 unladen-swallow 和 PyPy 开始做的那样。

否则,如果需要实现插入排序,那么代码在某种意义上几乎是 Python 化的,这就是在 Python 中的实现方式。它也非常不符合 Python 标准,因为您应该使用非常高效的内置排序。

【讨论】:

    【解决方案2】:

    我的第一个想法是我现在手头的笔记本电脑,Macbook Pro,必须与你的机器相当但略胜一筹——我没有足够的周围代码来尝试你的 C 示例(什么是vec_t 等),但运行您编写的 Python 代码给了我:

    $ python -mtimeit -s'import inso' 'inso.insort(inso.li)'
    10 loops, best of 3: 7.21 msec per loop
    

    与您的 8.1 秒相比。这就是你的代码放在insort.py,前面是:

    import random
    li = [random.randrange(10000) for _ in xrange(10000)]
    

    array 无济于事——实际上让事情变慢了一点。然后我安装了psyco,Python JIT 助手(仅限 x86,仅限 32 位),进一步补充:

    import psyco
    psyco.full()
    

    得到:

    $ python -mtimeit -s'import inso' 'inso.insort(inso.li)'
    10 loops, best of 3: 207 usec per loop
    

    因此加速大约为 7.21 / 0.000207 = 34830 倍 - 而 8.04 / 0.13 = 62 倍让您大吃一惊;-)。

    当然,问题是在第一次之后,列表已经排序,所以 insort 变得必须更快。您没有向我们提供足够的周边测试工具来准确了解您测量的什么。一个更现实的例子(实际列表没有被触及,所以它保持无序,只有一个副本被排序......),没有psyco:

    $ python -mtimeit -s'import inso' 'inso.insort(list(inso.li))'
    10 loops, best of 3: 13.8 sec per loop
    

    糟糕——所以你的机器比 Macbook Pro 快得多(记住,核心不算数:我们这里只使用一个;-)——哇……否则,你测量错误。无论如何,与 psyco:

    $ python -mtimeit -s'import inso' 'inso.insort(list(inso.li))'
    10 loops, best of 3: 456 msec per loop
    

    所以 psyco 的加速只有 13.8 / 0.456 30 倍——大约是使用纯 C 编码获得的 60 多倍的一半。 IOW,你会期望 python + psyco 比纯 C 慢两倍。这是一个更现实和典型的评估。

    如果我们编写相当高级的代码,psyco 的加速将从(比如说)30 倍下降到更少——但 C 相对于 Python 的优势也会如此。例如,

    $ python -mtimeit -s'import inso' 'sorted(inso.li)'
    100 loops, best of 3: 8.72 msec per loop
    

    没有 psyco(在这种情况下,psyco 实际上 -- 略微 -- 减慢 执行速度;-),所以这是 psyco 52 的另一个因素,非 psyco insort 总体上是 1582。

    但是,当您出于某种原因必须在 python 中编写极低级别的算法时,而不是使用内置函数和 stdlib 的丰富支持,psyco 可以帮助减轻痛苦。

    另一点是,当您进行基准测试时,请发布所有代码,以便其他人可以准确地看到您在做什么(并且可能会发现问题)——您的“脚手架”很棘手,很可能隐藏陷阱,就像你认为你正在测量的代码!-)

    【讨论】:

    • 您可以只使用列表副本运行 timeit 并从列表副本 + 排序中减去每个循环的结果吗?我意识到与课程相比,副本速度更快。
    • @gnibbler,179 微秒——与大多数其他测量相比,噪音方面,不仅仅是“快速”;-)
    • Alex 我用 cython 和 psyco 运行过,看到了 16 倍的差异。 cython 可以在你的 Mac 上运行吗? stackoverflow.com/questions/1561596/…
    • @gnibbler,当然,Cython 在 Mac 上运行得很好,它是一种非常有用的语言,但它不是 Python——它是一个“扩展子集”(不是所有东西的扩展子集) ?-),这就是 OP 所要问的!-)
    • 亚历克斯,抱歉我没说清楚。我的目的是确保 cython 中的函数内部编译为纯 C,因此 cython 提供了一种在用于 python 的同一框架中对 C 进行基准测试的方法。 Python 在我的笔记本上运行了大约 19 秒,根据这个实验,C 的速度快了 400 多倍。我只是想知道您是否可以确认 cython 的结果。 400 次似乎很多
    【解决方案3】:

    因此,您应该从中吸取一些教训:

    • 解释型 Python 速度较慢。不要尝试用 Python 编写自己的 FFT、MPEG 编码器等。

    • 即使是缓慢解释的 Python 也可能足够快来解决小问题。 8 秒的运行时间并不可怕,编写和调试 C 比 Python 花费的时间要长得多,所以如果你编写的东西运行一次,Python 会胜出。

    • 为了提高 Python 的速度,请尝试依赖内置功能和 C 模块。让其他人的 C 代码完成繁重的工作。我在一个嵌入式设备上工作,我们在其中使用 Python 进行工作;尽管嵌入式处理器速度较慢,但​​性能还是不错的,因为 C 库模块完成了大部分工作。

    为了乐趣和教育,请重复您的 Python 测试,这次使用列表中的内置 .sort() 方法;它可能不会像 C 那样快,但它会很接近。 (虽然对于非常大的数据集,它会击败 C,因为插入排序很烂。如果您重写 C 以使用 C 库 qsort() 函数,那将是速度冠军。)

    一个常见的 Python 设计“模式”是:首先,用 Python 编写您的应用程序。如果速度够快,就停下来;你完成了。二、尝试重写以提高速度;例如,看看是否有可以使用的 C 模块。如果还是不够快,可以考虑自己写C模块;或者,编写一个 C 程序,使用 Python 原型代码作为设计的基础。

    【讨论】:

    • 插入排序可以完成 O(nlogn) 但这个是 O(n^2) 所以在 10000 我希望 timsort 能够击败它
    • 我说过 Python 会胜出“真正的大数据集”,因为 Timsort 很棒;但是对于只有 10,000 个项目,我预计启动时间占主导地位。但是,我只是在我的计算机上进行了一个快速测试,发现我的简单 Python 程序分配 10,000 个随机整数的列表并使用 .sort() 对其进行排序实际上比我的简单 C 程序初始化 10,000 个数组更快整数并使用上面的插入排序对其进行排序。 (我不得不修改插入排序以在裸 C 中工作......我不确定那个“vec”类型是什么。它看起来不像 std::vector...)
    • 看起来 vec 只是数组 v 与它的长度 n 捆绑在一起
    • 当然,但我只是想放弃一个快速测试,所以我只是将它转换为无聊的 C。我不想尝试编写一个与 vec 兼容的类来使其工作,或者将其转换为 std::vector,只是为了快速测试。
    【解决方案4】:

    你用什么方法来测量时间?
    做这种事情,我发现 python 至少比 C 慢 30 倍
    C 编译器可能能够使用一些 Python 甚至没有尝试过的优化

    如果尝试 psyco 可能会很有趣,那么这种类型的代码非常适合它。

    基于 Alex 的回答,我尝试了 cython。在他的例子中,cython 将 for 循环和一切都变成了纯 C,所以现在我可以比较 C、python 和 psyco

    现在我有了这个 insort.py

    
    import psyco
    import random
    li = [random.randrange(10000) for _ in xrange(10000)]
    
    def insort (ln):
        for j in range(1, len(ln)):
            key = ln[j]
            i = j-1
            while i >= 0 and ln[i] > key:
                ln[i+1] = ln[i]
                i-=1
            ln[i+1] = key
    
    #psyco.bind(insort)
    
    import pyximport; pyximport.install()
    import pyxinsort
    
    def pyx_setup():
        pyxinsort.setup(li)
    
    def pyx_insort():
        pyxinsort.insort(li)
    
    

    还有这个 pyxinsort.pyx

    cdef int ln[10000] def insort(li): cdef int i,j,key for j in range(1, len(li)): key = ln[j] i = j-1 while i >= 0 and ln[i] > key: ln[i+1] = ln[i] i-=1 ln[i+1] = key def setup(li): cdef int i for i in range(1, len(li)): ln[i]=li[i]

    insort 的代码几乎相同。 li 传入其长度。 ln 是排序后的数组,由 setup 预填充,因此我可以将构建列表与排序隔离

    蟒蛇

    $ python2.5 -mtimeit -s'import inso''list(inso.li)' 10000 次循环,3 次中的最佳:每个循环 84.5 微秒 $ python2.5 -mtimeit -s'import inso''inso.insort(list(inso.li))' 10 个循环,最好的 3 个:每个循环 21.9 秒

    心理

    $ python2.5 -mtimeit -s'import inso''list(inso.li)' 10000 个循环,最好的 3 个:每个循环 85.6 微秒 $ python2.5 -mtimeit -s'import inso''inso.insort(list(inso.li))' 10 个循环,3 个循环中的最佳:每个循环 578 毫秒

    cython(运行的算法与转换为 C 并编译的算法完全相同)

    $ python2.5 -mtimeit -s'import inso''inso.pyx_setup()' 10000 个循环,3 个中最好的:每个循环 141 微秒 $ python2.5 -mtimeit -s'import inso''inso.pyx_setup();inso.pyx_insort()' 10 个循环,3 个循环中的最佳:每个循环 46.6 毫秒

    cython 比 psyco 高出 16 倍,Python 高出 470 倍!

    为了完整起见,我已经包含了由 cython 生成的相应 C 代码

    
      for (__pyx_v_j = 1; __pyx_v_j < __pyx_1; __pyx_v_j+=1) {
        __pyx_v_key = (__pyx_v_9pyxinsort_ln[__pyx_v_j]);
        __pyx_v_i = (__pyx_v_j - 1);
        while (1) {
          __pyx_2 = (__pyx_v_i >= 0);
          if (__pyx_2) {
            __pyx_2 = ((__pyx_v_9pyxinsort_ln[__pyx_v_i]) > __pyx_v_key);
          }
          if (!__pyx_2) break;
          (__pyx_v_9pyxinsort_ln[(__pyx_v_i + 1)]) = (__pyx_v_9pyxinsort_ln[__pyx_v_i]);
          __pyx_v_i -= 1;
        }
        (__pyx_v_9pyxinsort_ln[(__pyx_v_i + 1)]) = __pyx_v_key;
      }
    

    【讨论】:

      【解决方案5】:

      有什么问题:

      ln.sort()
      

      【讨论】:

      • 这使用了 tim-sort。目标显然是实现/了解算法,而不是使用结果。
      • 目标显然是实现/了解算法,而不是使用结果。 – Jeff Ober 完全正确。
      • 一点也不明显。 Python 是一个围绕大量本机编码函数构建的框架。尝试像 Python 那样重新实现基本的东西,然后将其与 C 进行比较是荒谬的。
      • 他明确表示不想使用内置函数。我不会投票给你,但因为他的实验很荒谬而给了你-1。
      • @rlbond - 我不同意。尝试将 C 中的低效算法与 Python(或任何其他动态语言)中的低效算法进行比较会产生这样的结果。这就是动态语言的本质。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-04
      • 2023-04-09
      • 2011-01-21
      • 2015-05-18
      • 2014-02-13
      相关资源
      最近更新 更多