Подтвердить что ты не робот

Может ли Go действительно быть намного быстрее, чем Python?

Я думаю, что я мог реализовать это неправильно, потому что результаты не имеют смысла. У меня есть программа Go, которая рассчитывает на 1000000000:

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 или я что-то упустил?

4b9b3361

Ответ 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 должен создать 1000000000 объектов int. Go просто увеличивает область памяти.

Ответ 3

Этот сценарий будет в значительной степени способствовать созданию достойных исходно-скомпилированных статически типизированных языков. Натурно скомпилированные статически типизированные языки способны испускать очень тривиальный цикл, скажем, 4-6 кодов операций процессора, который использует простое условие проверки для завершения. Этот цикл имеет фактически нулевые отклонения от прогноза ветвления и может быть эффективно рассмотрен как выполнение прироста каждого цикла ЦП (это не совсем верно, но..)

Реализации Python должны делать значительно больше работы, в первую очередь из-за динамического ввода. Python должен сделать несколько разных вызовов (внутренних и внешних), чтобы добавить два int вместе. В Python он должен вызывать __add__ (эффективно i = i.__add__(1), но этот синтаксис будет работать только в Python 3.x), который, в свою очередь, должен проверить тип переданного значения (чтобы убедиться, что это int), то он добавляет целочисленные значения (извлекает их из обоих объектов), а затем новое целочисленное значение снова завершается в новом объекте. Наконец, он повторно назначает новый объект локальной переменной. Это значительно больше, чем один кодовый код для увеличения, и даже не обращается к самому циклу. По сравнению с исходной версией Go/native, скорее всего, только увеличение регистра по побочному эффекту.

Java будет намного лучше в тривиальных тестах, подобных этому, и, вероятно, будет достаточно близка к Go; JIT и статическая типизация переменной счетчика могут обеспечить это (она использует специальную команду integer add JVM). Опять же, Python не имеет такого преимущества. Теперь есть некоторые реализации, такие как PyPy/RPython, которые запускают статическую типизацию и должны здесь гораздо лучше, чем CPython здесь.

Ответ 4

У вас здесь две вещи. Первым из них является то, что Go скомпилирован в машинный код и запускается непосредственно на CPU, в то время как Python скомпилирован для байт-кода для работы с (особенно медленной) VM.

Вторая и более значительная вещь, влияющая на производительность, заключается в том, что семантика двух программ на самом деле существенно отличается. Версия Go делает "поле" под названием "x", которое содержит число и увеличивает его на 1 на каждом проходе через программу. Версия Python на самом деле должна создать новый "ящик" (int object) в каждом цикле (и, в конце концов, должна отбросить их). Мы можем продемонстрировать это, слегка изменив ваши программы:

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, но вы выбрали наихудший сценарий - в игре Benchmarks, мы видим, что в среднем В 25 раз быстрее (100x в худшем случае).

Вы, наверное, читали, что если ваши программы на Python слишком медленные, вы можете ускорить их, переместив вещи на C. К счастью, в этом случае кто-то уже сделал это для вас. Если вы перепишете свой пустой цикл, чтобы использовать xrange() так:

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

... вы увидите, что он работает примерно в два раза быстрее. Если вы обнаружите, что счетчики циклов на самом деле являются основным узким местом в вашей программе, возможно, пришло время исследовать новый способ решения проблемы.

Ответ 5

@troq

Я немного опаздываю на вечеринку, но я бы сказал, что ответ "да" и "нет". Как отметил @gnibbler, CPython работает медленнее в простой реализации, но pypy jit скомпилирован для гораздо более быстрого кода, когда вам это нужно.

Если вы выполняете числовую обработку с помощью CPython, большинство будет делать это с numpy, что приведет к быстрой работе с массивами и матрицами. В последнее время я много делаю с numba, что позволяет вам добавить простую оболочку в ваш код. Для этого я просто добавил @njit к функции incALot(), которая запускает ваш код выше.

На моей машине CPython занимает 61 секунду, но с оболочкой numba требуется 7,2 микросекунды, которые будут похожи на C и, возможно, быстрее, чем Go. Это ускорение в 8 миллионов раз.

Итак, в 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;

Надеюсь, что это поможет =)

Ответ 8

Я не знаком с go, но я бы предположил, что версия go игнорирует цикл, поскольку тело цикла ничего не делает. С другой стороны, в версии python вы увеличиваете x в теле цикла, чтобы он, вероятно, фактически выполнял цикл.