【问题标题】:Can Go really be that much faster than Python?Go 真的可以比 Python 快那么多吗?
【发布时间】:2012-09-16 11:54:04
【问题描述】:

我想我可能没有正确地实现这个,因为结果没有意义。我有一个计数到 1000000000 的 Go 程序:

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 1000000000; i++ {}
    fmt.Println("Done") 
}

不到一秒就完成了。另一方面,我有一个 Python 脚本:

x = 0
while x < 1000000000:
    x+=1
print 'Done'

几分钟后完成。

为什么 Go 版本的速度这么快?他们都数到 1000000000 还是我错过了什么?

【问题讨论】:

    标签: python performance go


    【解决方案1】:

    十亿并不是一个很大的数字。任何相当现代的机器应该最多可以在几秒钟内完成此操作,如果它能够使用本机类型完成工作。我通过编写一个等效的 C 程序来验证这一点,阅读程序集以确保它确实在进行加法,并对其进行计时(在我的机器上完成大约 1.8 秒)。

    然而,Python 没有原生类型变量的概念(或根本没有有意义的类型注释),因此在这种情况下它必须做数百倍的工作。简而言之,您的标题问题的答案是“是”。 Go 真的可以比 Python 快得多,即使没有任何类型的编译器技巧,比如优化无副作用的循环。

    【讨论】:

      【解决方案2】:

      pypy 实际上在加速这个循环方面做得很出色

      def main():
          x = 0
          while x < 1000000000:
              x+=1
      
      if __name__ == "__main__":
          s=time.time()
          main()
          print time.time() - s
      

      $ python count.py 
      44.221405983
      $ pypy count.py 
      1.03511095047
      

      ~97% 加速!

      澄清 3 个人没有“明白”。 Python 语言本身并不慢。 CPython 实现是运行代码的一种相对直接的方式。 Pypy 是该语言的另一种实现,它做了许多可以产生巨大差异的棘手(特别是 JIT)事情。直接回答标题中的问题 - Go 并不比 Python 快“那么多”,Go 比 CPython 快那么多。

      话虽如此,代码示例并没有真正做同样的事情。 Python 需要实例化其 int 对象中的 1000000000 个。 Go 只是增加一个内存位置。

      【讨论】:

        【解决方案3】:

        这种情况将高度支持体面的本地编译静态类型语言。本地编译的静态类型语言能够发出一个非常简单的循环,比如 4-6 个 CPU 操作码,它利用简单的检查条件来终止。这个循环实际上有个分支预测未命中,并且可以有效地被认为是在每个 CPU 周期执行一次增量(这并不完全正确,但是..)

        Python 实现必须显着做更多的工作,主要是由于动态类型。 Python 必须进行多次不同的调用(内部和外部)才能添加两个 @987654322 @s 一起。在 Python 中它必须调用__add__(实际上是i = i.__add__(1),但这种语法只适用于Python 3.x),它又必须检查传递的值的类型(到确保它是一个int),然后它添加整数值(从两个对象中提取它们),然后新的整数值再次包装在一个新对象中。最后,它将新对象重新分配给局部变量。这比单个操作码递增的工作要多得多,甚至不解决循环本身 - 相比之下,Go/native 版本可能只是通过副作用增加一个寄存器。

        Java 在像这样的微不足道的基准测试中会更好很多,并且可能与 Go 相当接近;计数器变量的 JIT 和 static-typing 可以确保这一点(它使用特殊的整数加法 JVM 指令)。再一次,Python 没有这样的优势。现在,有一些像 PyPy/RPython 这样的实现,它们运行静态类型化阶段,应该比 CPython 好得多。..

        【讨论】:

        • 我并不是想以此作为基准(抱歉,如果我没有说清楚)。我只是想知道为什么 python 版本这么慢。
        • -1:您最后的“本质上具有误导性”的评论似乎是一个没有正当理由或解释的平淡断言。
        • @igouy 我不明白它是如何毫无根据的(整个帖子都是一个理由),但我删除了它,因为它没有添加任何新内容。
        【解决方案4】:

        这里有两件事在起作用。第一个是 Go 被编译成机器码并直接在 CPU 上运行,而 Python 被编译成字节码在(特别慢的)VM 上运行。

        影响性能的第二个也是更重要的因素是两个程序的语义实际上有很大不同。 Go 版本创建了一个名为“x”的“盒子”,其中包含一个数字,并在每次通过程序时将其递增 1。 Python 版本实际上必须在每个循环中创建一个新的“盒子”(int 对象)(并且最终必须将它们丢弃)。我们可以通过稍微修改您的程序来证明这一点:

        package main
        
        import (
            "fmt"
        )
        
        func main() {
            for i := 0; i < 10; i++ {
                fmt.Printf("%d %p\n", i, &i)
            }
        }
        

        ...和:

        x = 0;
        while x < 10:
            x += 1
            print x, id(x)
        

        这是因为 Go,由于它的 C 根,使用变量名来引用 place,而 Python 使用变量名来引用 things。由于整数在 python 中被认为是唯一的、不可变的实体,我们必须不断地创建新的实体。 Python 应该比 Go 慢,但您选择了最坏的情况 - in the Benchmarks Game,我们看到 Go 平均快 25 倍(最坏情况下为 100 倍)。

        您可能已经读过,如果您的 Python 程序太慢,您可以通过将内容移到 C 中来加快它们的速度。幸运的是,在这种情况下,已经有人为您完成了这项工作。如果您像这样重写空循环以使用xrange()

        for x in xrange(1000000000):
            pass
        print "Done."
        

        ...您会看到它的运行速度大约是原来的两倍。如果您发现循环计数器实际上是您程序中的主要瓶颈,那么可能是时候研究一种解决问题的新方法了。

        【讨论】:

        • 更好地使用 Python 和 Go 之间的直接基准测试游戏比较 -- shootout.alioth.debian.org/u64q/… -- 您似乎感到困惑并报告了 Python 与 C 的性能比较。
        【解决方案5】:

        @troq

        我参加聚会有点晚了,但我会说答案是肯定的和否定的。正如@gnibbler 指出的那样,CPython 在简单实现中速度较慢,但​​ pypy 是 jit 编译的,以便在您需要时更快地编写代码。

        如果您使用 CPython 进行数值处理,大多数人会使用 numpy 进行处理,从而对数组和矩阵进行快速运算。最近我对 numba 做了很多工作,它允许您在代码中添加一个简单的包装器。对于这个,我只是将@njit 添加到一个函数 incALot() 中,该函数在上面运行你的代码。

        在我的机器上,CPython 需要 61 秒,但使用 numba 包装器需要 7.2 微秒,这将类似于 C 并且可能比 Go 更快。那是 800 万倍的加速。

        因此,在 Python 中,如果数字看起来有点慢,有一些工具可以解决它 - 您仍然可以获得 Python 的程序员生产力和 REPL。

        def incALot(y):
            x = 0
            while x < y:
                x += 1
        
        @njit('i8(i8)')
        def nbIncALot(y):
            x = 0
            while x < y:
                x += 1
            return x
        
        size = 1000000000
        start = time.time()
        incALot(size)
        t1 = time.time() - start
        start = time.time()
        x = nbIncALot(size)
        t2 = time.time() - start
        print('CPython3 takes %.3fs, Numba takes %.9fs' %(t1, t2))
        print('Speedup is: %.1f' % (t1/t2))
        print('Just Checking:', x)
        
        CPython3 takes 58.958s, Numba takes 0.000007153s
        Speedup is: 8242982.2
        Just Checking: 1000000000
        

        【讨论】:

          【解决方案6】:

          问题是 Python 被解释,GO 不是,所以没有真正的方法来测试速度。解释语言通常(并不总是有 vm 组件)是问题所在,您运行的任何测试都是在解释边界而不是实际运行时边界中运行的。 Go 在速度方面比 C 稍慢,这主要是因为它使用垃圾收集而不是手动内存管理。也就是说,GO 与 Python 相比速度更快,因为它是一种编译语言,GO 中唯一缺少的就是错误测试,如果我错了,我会纠正。

          【讨论】:

            【解决方案7】:

            有可能编译器意识到你没有在循环之后使用“i”变量,所以它通过删除循环来优化最终代码。

            即使你之后使用它,编译器也可能足够聪明,可以用

            代替循环
            i = 1000000000;
            

            希望这会有所帮助 =)

            【讨论】:

            • 您可以通过获取汇编程序列表来检查循环是否仍在代码中:go build -gcflags -S main.go
            【解决方案8】:

            我不熟悉 go,但我猜 go 版本会忽略循环,因为循环的主体什么都不做。另一方面,在 python 版本中,您在循环体中递增x,因此它可能实际上正在执行循环。

            【讨论】:

            • 我更改了 for 循环,将 i 分配给每个循环的另一个变量(即 i2 = i),速度仍然相同(所以基本上我知道 for 循环已执行)。
            • 最后我有程序打印 i2,i2 是 999999999
            猜你喜欢
            • 2015-09-11
            • 2018-04-05
            • 1970-01-01
            • 2011-11-27
            • 2020-04-23
            • 2016-11-12
            • 1970-01-01
            • 2018-05-19
            • 2011-09-28
            相关资源
            最近更新 更多