Я также могу подтвердить результаты. Я попытался увидеть, как это будет выглядеть, используя все типы numpy, и разница сохраняется. Итак, мои тесты были:
def testStandard(length=100000):
s = 1.0
addend = 8.0
modulo = 2399232.0
startTime = datetime.now()
for i in xrange(length):
s = (s + addend) * s % modulo
return datetime.now() - startTime
def testNumpy(length=100000):
s = np.float64(1.0)
addend = np.float64(8.0)
modulo = np.float64(2399232.0)
startTime = datetime.now()
for i in xrange(length):
s = (s + addend) * s % modulo
return datetime.now() - startTime
Таким образом, на этом этапе все типы numpy взаимодействуют друг с другом, но разница в 10 раз сохраняется (2 с против 0,2 с).
Если бы мне пришлось угадывать, я бы сказал, что есть две возможные причины того, почему типы с плавающей запятой по умолчанию намного быстрее. Первая возможность состоит в том, что python выполняет значительные оптимизации под капотом для работы с определенными числовыми операциями или циклом в целом (например, развертывание цикла). Вторая возможность состоит в том, что тип numpy включает дополнительный уровень абстракции (то есть, для чтения с адреса). Чтобы изучить эффекты каждого из них, я сделал несколько дополнительных проверок.
Одним из отличий может быть то, что python должен был предпринять дополнительные шаги для разрешения типов float64. В отличие от скомпилированных языков, которые генерируют эффективные таблицы, Python 2.6 (и, возможно, 3) имеет значительные затраты для решения задач, которые вы обычно считаете бесплатными. Даже простое разрешение X.a должно разрешать оператор точки КАЖДЫЙ раз, когда он вызывается. (Вот почему, если у вас есть цикл, который вызывает instance.function (), вам лучше иметь переменную «function = instance.function», объявленную вне цикла).
Насколько я понимаю, когда вы используете стандартные операторы python, они довольно похожи на те, которые используются в операторе импорта. Если вы замените add, mul и mod на ваши +, * и%, вы увидите статическое снижение производительности примерно на 0,5 с по сравнению со стандартными операторами (в обоих случаях). Это означает, что, оборачивая операторы, стандартные операции с плавающей точкой Python становятся в 3 раза медленнее. Если вы сделаете еще одно, использование operator.add и этих вариантов прибавит примерно 0,7 с (более 1 м испытаний, начиная с 2 с и 0,2 с соответственно). Это граничит с 5-кратной медлительностью. Таким образом, в принципе, если каждая из этих проблем происходит дважды, вы в основном в 10 раз медленнее.
Итак, давайте на минутку предположим, что мы интерпретатор python. Случай 1, мы делаем операцию над нативными типами, скажем, a + b. Под капотом мы можем проверить типы a и b и отправить наше дополнение к оптимизированному коду Python. В случае 2 мы имеем операцию двух других типов (также a + b). Под капотом мы проверяем, являются ли они нативными типами (они не являются). Мы переходим к другому делу. В другом случае мы получаем что-то вроде a. add (b). a. add может отправлять оптимизированный код numpy. Таким образом, в этот момент у нас были дополнительные издержки на дополнительную ветку, одну '.' получить свойство slots и вызов функции. И мы только вошли в операцию сложения. Затем мы должны использовать результат, чтобы создать новый float64 (или изменить существующий float64). Между тем, нативный код Python, вероятно, обманывает, обрабатывая его типы специально, чтобы избежать такого рода накладных расходов.
Исходя из вышеизложенного изучения дороговизны вызовов функций Python и объема служебной информации, для numpy было бы довольно легко получить 9-кратный штраф, просто добираясь до и от своих математических функций. Я могу себе представить, что этот процесс занимает много раз дольше, чем простой математический вызов операции. Для каждой операции библиотека numpy должна будет пройти через слои python, чтобы добраться до своей реализации на C.
Так что, на мой взгляд, причина этого, вероятно, заключена в следующем:
length = 10000000
class A():
X = 10
startTime = datetime.now()
for i in xrange(length):
x = A.X
print "Long Way", datetime.now() - startTime
startTime = datetime.now()
y = A.X
for i in xrange(length):
x = y
print "Short Way", datetime.now() - startTime
Этот простой случай показывает разницу в 0,2 с по сравнению с 0,14 с (очевидно, на коротком пути быстрее). Я думаю, то, что вы видите, - это, в основном, куча этих проблем, которые складываются.
Чтобы избежать этого, я могу придумать пару возможных решений, которые в основном повторяют сказанное. Первое решение состоит в том, чтобы как можно больше сохранить ваши оценки внутри NumPy, как сказал Селинап. Большая часть потерь, вероятно, из-за сопряжения. Я хотел бы изучить способы передачи вашей работы в numpy или какую-либо другую числовую библиотеку, оптимизированную в C (gmpy уже упоминалось). Цель должна состоять в том, чтобы одновременно вставить как можно больше в C, а затем вернуть результат (ы). Вы хотите поставить большую работу, а не много мелких.
Вторым решением, конечно, было бы сделать больше ваших промежуточных и небольших операций в python, если вы можете. Понятно, что использование нативных объектов будет быстрее. Они будут первыми опциями для всех операторов ветвления и всегда будут иметь кратчайший путь к коду C. Если у вас нет особой необходимости в вычислениях с фиксированной точностью или других проблем с операторами по умолчанию, я не понимаю, почему нельзя было бы использовать прямые функции python для многих вещей.