Ответ 1
Основное различие заключается в больших накладных расходах при расчете a.sum(axis=1)
. Вычисление сокращения (в данном случае sum
) не является тривиальным вопросом:
- нужно учитывать ошибки округления и, таким образом, использовать парное суммирование, чтобы уменьшить его.
- тайлинг важен для больших массивов, так как он максимально использует доступный кеш
- Чтобы иметь возможность использовать SIMD-инструкции/неиспользуемые возможности выполнения современных процессоров, необходимо рассчитать несколько строк параллельно
Я обсуждал темы выше более подробно, например здесь и здесь.
Однако все это не нужно и не лучше, чем наивное суммирование, если добавить только два элемента - вы получите тот же результат, но с гораздо меньшими издержками и быстрее.
Только для 1000 элементов затраты на вызов функциональности numpy, вероятно, выше, чем на самом деле выполнение этих 1000 сложений (или умножений в этом отношении, потому что на современных процессорах конвейерные сложения/умножения имеют одинаковую стоимость) -as, как вы можете видеть, что для 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]