Основное различие заключается в больших накладных расходах при расчете a.sum(axis=1)
. Расчет сокращения (в данном случае sum
) не является тривиальным вопросом:
- необходимо учитывать ошибки округления и, следовательно, использовать парное суммирование для его уменьшения.
- тайлинг важен для больших массивов, поскольку он максимально использует доступный кеш
- Для того чтобы иметь возможность использовать SIMD-инструкции / возможности выполнения внеочередных функций современных процессоров, необходимо рассчитать несколько строк параллельно
Я обсуждал вышеуказанные темы более подробно, например здесь и здесь .
Однако все это не нужно и не лучше наивного суммирования, если добавить только два элемента - вы получите тот же результат, но с гораздо меньшими затратами и быстрее.
Только для 1000 элементов затраты на вызов функциональности numpy, вероятно, выше, чем на самом деле выполнение этих 1000 сложений (или умножений в этом отношении, потому что на современных процессорах конвейерные сложения / умножения имеют одинаковую стоимость) - как вы можете видеть, что для 10 ^ 4 время работы только примерно в 2 раза выше, что является верным признаком того, что накладные расходы играют большую роль для 10 ^ 3! В этом ответе более подробно рассматривается влияние потерь служебной информации и кэша.
Давайте посмотрим на результат профилировщика, чтобы увидеть, справедлива ли приведенная выше теория (я использую perf
):
Для a.sum(axis=1)
:
17,39% python umath.cpython-36m-x86_64-linux-gnu.so [.] reduce_loop
11,41% python umath.cpython-36m-x86_64-linux-gnu.so [.] pairwise_sum_DOUBLE
9,78% python multiarray.cpython-36m-x86_64-linux-gnu.so [.] npyiter_buffered_reduce_iternext_ite
9,24% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_add
4,35% python python3.6 [.] _PyEval_EvalFrameDefault
2,17% python multiarray.cpython-36m-x86_64-linux-gnu.so [.] _aligned_strided_to_contig_size8_src
2,17% python python3.6 [.] lookdict_unicode_nodummy
...
Издержки использования reduce_loop
, pairwise_sum_DOUBLE
доминируют.
Для a[:,0]+a[:,1])
:
7,24% python python3.6 [.] _PyEval_EvalF
5,26% python python3.6 [.] PyObject_Mall
3,95% python python3.6 [.] visit_decref
3,95% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_add
2,63% python python3.6 [.] PyDict_SetDef
2,63% python python3.6 [.] _PyTuple_Mayb
2,63% python python3.6 [.] collect
2,63% python python3.6 [.] fast_function
2,63% python python3.6 [.] visit_reachab
1,97% python python3.6 [.] _PyObject_Gen
Как и следовало ожидать: большие издержки Python играют большую роль, используется простой DOUBLE_add
.
При звонке * меньше затрат a.sum()
- за один раз
reduce_loop
вызывается не для каждой строки, а только один раз, что означает значительно меньшие накладные расходы.
- новые результирующие массивы не создаются, больше нет необходимости записывать 1000 дублей в память.
, поэтому можно ожидать, что a.sum()
быстрее (несмотря на то, что нужно добавить 2000, а не 1000 - но, как мы видели, это в основном накладные расходы и реальная работа - дополнения не являются отвечает за большую долю времени работы).
Получение данных с помощью команды:
perf record python run.py
perf report
и
#run.py
import numpy as np
a=np.random.rand(1000,2)
for _ in range(10000):
a.sum(axis=1)
#a[:,0]+a[:,1]