Can Go真的比python快得多吗?

时间:2012-09-25 01:00:20

标签: python performance go

我想我可能已经错误地实现了这个,因为结果没有意义。我有一个计数到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版本要快得多。他们都数到10亿还是我错过了什么?

8 个答案:

答案 0 :(得分:78)

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

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

答案 1 :(得分:61)

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需要实例化其100,000个int个对象。 Go只是递增一个内存位置。

答案 2 :(得分:18)

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

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

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

答案 3 :(得分:8)

你有两件事在这里工作。第一个是将Go编译为机器代码并直接在CPU上运行,而Python被编译为针对(特别慢)VM运行的字节码。

影响性能的第二个也是更重要的事情是两个程序的语义实际上有很大不同。 Go版本创建一个名为“x”的“盒子”,它保存一个数字,并在每次通过程序时递增1。 Python版本实际上必须在每个循环中创建一个新的“box”(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根而采用变量名称来引用地方,其中Python使用变量名称来引用事物。由于整数被认为是python中唯一的,不可变的实体,我们必须不断创建新的实体。 Python应该比Go慢,但你已经选择了最糟糕的情况 - in the Benchmarks Game,我们看到平均来说,速度提高约25倍(在最坏的情况下为100倍)。

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

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

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

答案 4 :(得分:1)

@troq

我有点迟到了,但我说答案是肯定的,不是。正如@gnibbler指出的那样,CPython在简单的实现中速度较慢,但​​pypy是在需要时编译得更快的代码。

如果您正在使用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

答案 5 :(得分:1)

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

答案 6 :(得分:-1)

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

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

替换循环
i = 1000000000;

希望这有助于=)

答案 7 :(得分:-2)

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