Проблемы в реализации метод Горнера в Python

поэтому я записал коды для оценки полинома, используя три разных метода. Метод Хорнера должен быть самым быстрым, в то время как наивный метод должен быть самым медленным, верно? Но почему время для вычислений это не то, что я ожидаю? И время для расчета иногда оказывается точно таким же для Итеры и наивного метода. Что с ним не так?

import numpy.random as npr
import time

def Horner(c,x):
    p=0
    for i in c[-1::-1]:
        p = p*x+i
    return p

def naive(c,x):
    n = len(c)
    p = 0
    for i in range(len(c)):
        p += c[i]*x**i 
    return p

def itera(c,x):
    p = 0
    xi = 1
    for i in range(len(c)):
        p += c[i]*xi
        xi *= x 
    return p

c=npr.uniform(size=(500,1))
x=-1.34

start_time=time.time()
print Horner(c,x)
print time.time()-start_time

start_time=time.time()
print itera(c,x)
print time.time()-start_time

start_time=time.time()
print naive(c,x)
print time.time()-start_time

вот некоторые результаты:

[  2.58646959e+69]
0.00699996948242
[  2.58646959e+69]
0.00600004196167
[  2.58646959e+69]
0.00600004196167

[ -3.30717922e+69]
0.00899982452393
[ -3.30717922e+69]
0.00600004196167
[ -3.30717922e+69]
0.00600004196167

[ -2.83469309e+69]
0.00999999046326
[ -2.83469309e+69]
0.00999999046326
[ -2.83469309e+69]
0.0120000839233

3 ответов


ваше профилирование может быть значительно улучшено. Кроме того, мы можем сделать ваш код работать 200-500x быстрее.


(1) повтор

вы не можете выполнить только одну итерацию теста производительности по двум причинам.

  1. ваше разрешение времени может быть недостаточно хорошим. Вот почему вы иногда получали одно и то же время для двух реализаций: время для одного запуска было близко к разрешению вашего механизма синхронизации, поэтому вы записали только один "галочка."
  2. здесь всякие факторов, влияющих на производительность. Ваш лучший выбор для значимого сравнения будет много итераций.

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

timeit - хороший маленький модуль для профилирования кода Python.

я добавил это в нижней части вашего скрипта.

import timeit

n = 1000

print 'Horner', timeit.timeit(
    number = n,
    setup='from __main__ import Horner, c, x',
    stmt='Horner(c,x)'
)
print 'naive', timeit.timeit(
    number = n,
    setup='from __main__ import naive, c, x',
    stmt='naive(c,x)', 
)
print 'itera', timeit.timeit(
    number = n,
    setup='from __main__ import itera, c, x',
    stmt='itera(c,x)', 
)

которая производит

Horner 1.8656351566314697
naive 2.2408010959625244
itera 1.9751169681549072

Хорнер самый быстрый, но он точно не сдувает двери с двух других.


(2) посмотрите, что происходит...очень осторожно!--18-->

Python имеет перегрузку оператора,поэтому легко пропустить это.

npr.uniform(size=(500,1)) дает вам структуру 500 x 1 numpy случайных чисел.

так что?

Ну c[i] это не число. это массив numpy с одним элементом. Numpy перегружает операторы, поэтому вы можете делать такие вещи, как умножение массива на скаляр.

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

вместо этого давайте попробуем простой список Python:

import random
c = [random.random() for _ in range(500)]

и теперь,

Horner 0.034661054611206055
naive 0.12771987915039062
itera 0.07331395149230957

Эй! все время просто стало быстрее (на 10-60x). Пропорционально, реализация Horner стала еще быстрее, чем две другие. Мы удалили накладные расходы на всех трех и теперь можем видеть разницу "голых костей".

Хорнер в 4 раза быстрее наивного и в 2 раза быстрее Итеры.


(3) альтернативные среды выполнения

вы используете Python 2. Я предполагаю 2.7.

давайте посмотрим, как работает Python 3.4. (Настройка синтаксиса: вам нужно поставить скобки вокруг списка аргументов в print.)

Horner 0.03298933599944576
naive 0.13706714100044337
itera 0.06771054599812487

примерно то же самое.

попробуем PyPy, реализация JIT Python. ("Нормальная" реализация Python называется CPython.)

Horner 0.006507158279418945
naive 0.07541298866271973
itera 0.005059003829956055

приятно! Каждая реализация теперь работает на 2-5x быстрее. Horner теперь 10x скорость наивной, но немного медленнее, чем "Итера".

JIT-среды выполнения сложнее профилировать, чем интерпретаторы. Давайте увеличим количество итераций до 50000 и попробуем это сделать, чтобы убедиться.

Horner 0.12749004364013672
naive 3.2823100090026855
itera 0.06546688079833984

(обратите внимание, что у нас есть 50X итерации, но только 20x время...JIT не имел полного эффекта для многих из первых 1000 запусков.) Те же выводы, но различия еще более выражены.

конечно, идея JIT-профилировать, анализировать и переписывать программу во время выполнения, поэтому если ваша цель-сравнить алгоритмы, это добавит много неочевидных деталей реализации.

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


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

ничего из этого не изменилось результаты для меня много значили, но их стоило рассмотреть.


вы не можете получить точный результат, измеряя такие вещи:

start_time=time.time()
print Horner(c,x)
print time.time()-start_time

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


если вы делаете много бенчмаркинга, научных вычислений, numpy связанной работы и многое другое, используя оболочкой IPython будет чрезвычайно полезным инструментом.

для проверки вы можете время код с timeit с помощью оболочкой IPython магии где вы получите более последовательные результаты каждый запуск, это просто вопрос использования timeit затем функция или код времени:

In [28]: timeit Horner(c,x)
1000 loops, best of 3: 670 µs per loop

In [29]: timeit naive(c,x)
1000 loops, best of 3: 983 µs per loop

In [30]: timeit itera(c,x)
1000 loops, best of 3: 804 µs per loop

для кода времени, охватывающего более одной строки, вы просто используете %%timeit:

In [35]: %%timeit
   ....: for i in range(100):
   ....:     i ** i
   ....: 
10000 loops, best of 3: 110 µs per loop

ipython может скомпилировать код цитона, f2py код и выполнять множество других очень полезных задач, используя различные плагины и команды IPython magic.

встроенные магические команды

используя cython и некоторые очень основные улучшения, мы можем повысить эффективность Horner примерно на 25 процентов:

In [166]: %%cython
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.float_t DTYPE_t
def C_Horner(c, DTYPE_t x):
    cdef DTYPE_t p
    for i in reversed(c):
        p = p * x + i
    return p   

In [28]: c=npr.uniform(size=(2000,1))

In [29]: timeit Horner(c,-1.34)
100 loops, best of 3: 3.93 ms per loop
In [30]: timeit C_Horner(c,-1.34)
100 loops, best of 3: 2.21 ms per loop

In [31]: timeit itera(c,x)
100 loops, best of 3: 4.10 ms per loop
In [32]: timeit naive(c,x)
100 loops, best of 3: 4.95 ms per loop

используя список в @Paul drapers ответ наша цитонизированная версия работает в два раза быстрее как исходная функция и намного быстрее, чем ietra и наивный:

In [214]: import random

In [215]: c = [random.random() for _ in range(500)]

In [44]: timeit C_Horner(c, -1.34)
10000 loops, best of 3: 18.9 µs per loop    
In [45]: timeit Horner(c, -1.34)
10000 loops, best of 3: 44.6 µs per loop
In [46]: timeit naive(c, -1.34)
10000 loops, best of 3: 167 µs per loop
In [47]: timeit itera(c,-1.34)
10000 loops, best of 3: 75.8 µs per loop