Почему Python такой медленный для простого цикла for? - PullRequest
39 голосов
/ 11 ноября 2011

Мы делаем несколько реализаций kNN и SVD в Python.Другие выбрали Java.Наши сроки исполнения очень разные.Я использовал cProfile, чтобы увидеть, где я делаю ошибки, но на самом деле все довольно отлично .Да, я также использую numpy.Но я хотел бы задать простой вопрос.

total = 0.0
for i in range(9999): # xrange is slower according 
    for j in range(1, 9999):            #to my test but more memory-friendly.
        total += (i / j)
print total

Этот фрагмент занимает 31.40 с на моем компьютере.

Java-версия этого кода занимает 1 секунду или менее на том же компьютере.Полагаю, проверка типов является основной проблемой для этого кода.Но я должен проделать большую работу, подобную этой, для своего проекта, и я думаю, что 9999 * 9999 не так уж много.

Я думаю, что я делаю ошибки, потому что я знаю, что Python используется во многих научных проектах.Но почему этот код такой медленный и как я могу справиться с проблемами большего размера?

Должен ли я использовать JIT-компилятор, такой как Psyco?

EDIT

Я такжескажем, что эта проблема цикла является лишь примером.Код не такой простой, как этот, и может быть сложно применить на практике ваши улучшения / примеры кода.

Другой вопрос заключается в том, могу ли я реализовать множество алгоритмов интеллектуального анализа данных и машинного обучения с numpy иscipy если я правильно его использую?

Ответы [ 9 ]

32 голосов
/ 11 ноября 2011

Я думаю, что я делаю ошибки, потому что знаю, что Python используется во многих научных проектах.

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

Стандартной (и в настоящее время наиболее популярной и широко поддерживаемой) реализацией является простой интерпретатор байт-кода. Даже самые простые операции, такие как целочисленное деление, могут занимать сотни циклов ЦП, множественный доступ к памяти (популярный пример - проверка типов), несколько вызовов функций C и т. Д. Вместо нескольких (или даже одного, в случае целого числа). разделение) инструкция. Кроме того, язык разработан со многими абстракциями, которые добавляют накладные расходы. Ваш цикл выделяет 9999 объектов в куче, если вы используете xrange - гораздо больше, если вы используете range (целое число 9999 * 9999 минус около 256 * 256 для небольших целых чисел, которые кэшируются). Кроме того, версия xrange вызывает метод для каждой итерации для продвижения - версия range тоже будет, если итерация последовательностей не была специально оптимизирована. Тем не менее, для этого требуется целая отправка байт-кода, что само по себе очень сложно (по сравнению с целочисленным делением, конечно).

Было бы интересно посмотреть, что такое JIT (я бы порекомендовал PyPy вместо Psyco, последний больше не разрабатывается активно и в любом случае очень ограничен по объему - хотя в этом простом примере это может хорошо работать). После крошечной доли итераций он должен создать почти оптимальный цикл машинного кода, дополненный несколькими охранниками - простыми целочисленными сравнениями, прыжками в случае неудачи - чтобы сохранить корректность в случае, если вы получили строку в этом списке. Java может сделать то же самое, только раньше (она не должна сначала проследить) и с меньшим количеством охранников (по крайней мере, если вы используете int s). Вот почему это намного быстрее.

14 голосов
/ 11 ноября 2011

Поскольку вы упоминаете научный код, взгляните на numpy. То, что вы делаете, вероятно, уже сделано (или, скорее, оно использует LAPACK для таких вещей, как SVD). Когда вы слышите о том, что python используется для научного кода, люди, вероятно, не имеют в виду использовать его так, как вы это делаете в своем примере.

В качестве быстрого примера:

(Если вы используете python3, в вашем примере будет использоваться деление с плавающей запятой. В моем примере предполагается, что вы используете python2.x, и, следовательно, целочисленное деление. Если нет, укажите i = np.arange(9999, dtype=np.float) и т. Д.)

import numpy as np
i = np.arange(9999)
j = np.arange(1, 9999)
print np.divide.outer(i,j).sum()

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

import numpy as np

def f1(num):
    total = 0.0
    for i in range(num): 
        for j in range(1, num):
            total += (float(i) / j)
    return total

def f2(num):
    i = np.arange(num, dtype=np.float)
    j = np.arange(1, num, dtype=np.float)
    return np.divide.outer(i, j).sum()

def f3(num):
    """Less memory-hungry (and faster) version of f2."""
    total = 0.0
    j = np.arange(1, num, dtype=np.float)
    for i in xrange(num):
        total += (i / j).sum()
    return total

Если мы сравним время:

In [30]: %timeit f1(9999)
1 loops, best of 3: 27.2 s per loop

In [31]: %timeit f2(9999)
1 loops, best of 3: 1.46 s per loop

In [32]: %timeit f3(9999)
1 loops, best of 3: 915 ms per loop
5 голосов
/ 30 мая 2014

Преимущество Python заключается в том, что он обладает гораздо большей гибкостью (например, классы являются объектами) по сравнению с Java (где у вас есть только этот механизм отражения)

Здесь не упоминается Cython . Это позволяет вводить типизированные переменные и транскомпилировать ваш пример в C / C ++. Тогда это намного быстрее. Я также изменил границы в цикле ...

from __future__ import division

cdef double total = 0.00
cdef int i, j
for i in range(9999):
    for j in range(1, 10000+i):
        total += (i / j)

from time import time
t = time()
print("total = %d" % total)
print("time = %f[s]" % (time() - t))

Вслед за

$ cython loops.pyx
$ gcc -I/usr/include/python2.7 -shared -pthread -fPIC -fwrapv -Wall -fno-strict-aliasing -O3 -o loops.so loops.c
$ python -c "import loops"

дает

total = 514219068
time = 0.000047[s]
4 голосов
/ 25 ноября 2011

Использование понимания списка kindall

total = sum(i / j for j in xrange(1, 9999) for i in xrange(9999))

составляет 10,2 секунды, а при использовании Pypy 1,7 - 2,5 секунды. Это забавно, потому что pypy ускоряет оригинальную версию до 2,5 секунд. Так что для понимания списка pypy было бы преждевременной оптимизации;). Хорошая работа, пыпы!

4 голосов
/ 11 ноября 2011

Вы обнаружите, что списки или выражения генератора значительно быстрее.Например:

total = sum(i / j for j in xrange(1, 9999) for i in xrange(9999))

Это выполняется на моей машине ~ 11 секунд против ~ 26 для вашего исходного кода.Все еще на порядок медленнее, чем Java, но это больше соответствует тому, что вы ожидаете.

Кстати, ваш исходный код может быть немного ускорен путем инициализации total до 0вместо 0.0 использовать целое число, а не сложение с плавающей точкой.Все ваши подразделения имеют целочисленные результаты, поэтому нет смысла суммировать результаты с плавающей точкой.

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

4 голосов
/ 11 ноября 2011

Это известное явление - код Python динамический и интерпретируемый, код Java статически типизирован и скомпилирован.Там нет сюрпризов.

Причины, по которым люди предпочитают python, часто бывают:

  • меньшая база кода
  • меньшая избыточность (больше DRY)
  • Более чистый код

Однако, если вы используете библиотеку, написанную на C (из python), производительность может быть намного лучше (сравните: pickle с cpickle).

3 голосов
/ 28 августа 2012

Python для циклов статически типизирован и интерпретируется.Не скомпилировано.Java работает быстрее, потому что имеет дополнительные функции ускорения JIT, которых нет в Python.

http://en.wikipedia.org/wiki/Just-in-time_compilation

Чтобы показать, насколько огромны различия в Java JIT, взгляните на эту программу на Pythonэто занимает около 5 минут:

if __name__ =='__main__':
    total = 0.0
    i=1
    while i<=9999:
        j=1
        while j<=9999:
            total=1
            j+=1
        i+=1
    print total

Хотя эта принципиально эквивалентная Java-программа занимает около 23 миллисекунд:

public class Main{
    public static void main(String args[]){
        float total = 0f; 

        long start_time = System.nanoTime();
        int i=1;

        while (i<=9999){
            int j=1;
            while(j<=9999){
                total+=1;
                j+=1;
            }
            i+=1;
        }
        long end_time = System.nanoTime();

        System.out.println("total: " + total);
        System.out.println("total milliseconds: " + 
           (end_time - start_time)/1000000);
    }
}

С точки зрения выполнения чего-либо в цикле, Java очищает часы Python с помощьюБыть на 1-1000 порядков быстрее.

Мораль истории: следует избегать базового питона для циклов любой ценой, если требуется быстрая производительность.Это может быть связано с тем, что Гвидо ван Россум хочет побудить людей использовать многопроцессорные дружественные конструкции, такие как объединение массивов, которые работают быстрее, чем Java.

0 голосов
/ 11 июля 2017

Не уверен, что рекомендация была сделана, но мне нравится заменять циклы на понимание списка. Это быстрее, чище и более питонично.

http://www.pythonforbeginners.com/basics/list-comprehensions-in-python

0 голосов
/ 24 мая 2014

Выполнение научных вычислений с помощью python часто означает использование некоторого программного обеспечения для вычислений, написанного на C / C ++, в наиболее важных частях, с python как внутренним языком сценариев, как ex Sage (который также содержит много кода python).

Я думаю, что это может быть полезно: http://blog.dhananjaynene.com/2008/07/performance-comparison-c-java-python-ruby-jython-jruby-groovy/

Как видите, psyco / PyPy может принести определенное улучшение, но, тем не менее, он, вероятно, будет намного медленнее, чем C ++ или Java.

...