Q : «Ускоряет ли l oop развертывание время выполнения?»
L oop развертывание можно избежать накладные расходы на цикл, но есть больше проблем для более крупных и многоуровневых циклов, учитывая, что сглаженная «длина» A[N,N,N]
скоро вырастет для N
выше ~ 1E3, 1E4, 1E5
намного выше нескольких GB
.
Q : «Есть ли другой способ улучшить мой тройной l oop?»
Избегайте передачи избыточных «параметров». Это дорого. Чем больше, тем больше они становятся. Ia, Ja, Ka
- повторно представить индексы естественной области из A[i,j,k]
, которые уже присутствуют внутри A
-пример, не так ли? Передача и получение больших избыточных параметров - это роскошь, которую мы часто предпочитаем избегать.
@njit(parallel=True)
def SwVer( ResSwc, SwNew, Ia, Ja, Ka ):
# [: : :] || || ||
for k in ( Ka ):
for j in ( Ja ):
for i in ( Ia ):
if ( SwNew[i,j,k] < ResSwc ):
SwNew[i,j,k] = ResSwc
if ( SwNew[i,j,k] > ( 1 - ResSwc ) ):
SwNew[i,j,k] = ( 1 - ResSwc )
# SwNew[:,:,:]
return SwNew
Как было СОСТОЯНИЕ-0:
Исходный код был выполнен примерно в ~ 1,824,168 [us] ~ 1.8 [s]
.
Небольшой ШАГ для человека:
Модификации на месте всегда быстрее, чем наличие множества промежуточных экземпляров того же размера для сбора окончательных результатов, что является еще одним анти-шаблоном высокопроизводительного кода.
Просто удаление последней строки return SwNew
дает ~ 714,977 [us] ~ 0.7 [s]
Теперь
а маленький ШАГ для человека,
но ГИГАНСКИЙ Прыжок для ЧЕЛОВЕЧЕСТВА (производительность) :
Для уникальной производительности на тривиальных [i,j,k]
преобразованиях можно попробовать смелые numpy
-векторизованные вычисления.
Весь фокус в следующем: np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ))
Ваша функция может работать где угодно между 12086 [us]
и 97810 [us]
в numpy
-векторный режим. Детали физической ОЗУ, кеша, ЦП и операционных нагрузок будут вызывать различные эффекты, но размеры ОЗУ имеют значение для умно-векторизованного кода, а numpy
умный и много, самый :
A[1000,1000,1000] ~ 8 [GB]
-RAM-footprints. Но детали имеют значение. Очень много.
A[ 100, 100, 100] ~ 8 [MB]
-ОЗУ-следы. Теперь он помещается в кэш L3 / L2 ... это важно ...
A[ 10, 10, 10] ~ 8 [kB]
-RAM-footprints. Теперь он помещается в L1d-кеш ... это важно. Много ...
Давайте начнем с очень начала:
Мы начнем с 2D-уменьшенной размерности из-за ОЗУ.
>>> from zmq import Stopwatch; aClk = Stopwatch() # a [us]-resolution clock
>>> pass; import gc; gc.disable() # ... elementary
>>> pass; import numpy as np
#_______________________________________________________________________________
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N, N ) ); aClk.stop()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "mtrand.pyx", line 861, in mtrand.RandomState.random_sample
File "mtrand.pyx", line 167, in mtrand.cont0_array
MemoryError
Тем не менее, Трюк с векторизованным кодом остается в принципе таким же для обработки 1D- ... nD-тензора:
#_______________________________________________________________________________
# [us]
#_______________________________________________________________________________
>>> N = int( 1E4 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
17801992
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
184895
>>> N = int( 1E2 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
1585
>>> N = int( 1E1 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
44
>>> N = int( 1E2 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
465
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
48651
>>> N = int( 1E4 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
4954694
>>> N = int( 1E4 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
25549190
#_______________________________________________________________________________
# [us] SEE THE GROWING COSTS FOR ram-ALLOCATIONS & STORAGE OF RANDOM num-s
#_______________________________________________________________________________
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
471956
50067
49184
42891
48897
52639
45464
48828
#_______________________________________________________________________________
# [us] SEE THE 1st, resp. WAY-LOWER COSTS FOR 1st, resp. OTHER ram-ACCESS
#_______________________________________________________________________________
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
71044
12086
16277
28192
#_______________________________________________________________________________
# [us] SEE ALSO THE "noise" IN THE LATENCY-COSTS IN 2nd+ RE-RUN-s
#_______________________________________________________________________________
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
45759
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
38362
46640
37927
>>> N = int( 1E4 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
4885472
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
35747022
#_______________________________________________________________________________
# [us] SEE THE SHIFT IN LATENCY-COSTS AS ram-i/o COSTS DOMINATE > 1 [GB]
#_______________________________________________________________________________
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
2307509
50446
49464
43006
50958
54800
43418
57091
52135
46451
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
97810
20117
14480
22696
31172
14846
#_______________________________________________________________________________
# [us] SEE THE "noise" FOR 2nd+ RE-RUN-s
#_______________________________________________________________________________
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
47437
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
39298
19422
#_______________________________________________________________________________
# [us] SEE THE "noise" FOR 2nd+ RE-RUN
#_______________________________________________________________________________
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
44814
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
42565
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
43120
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
38039
>>> N = int( 1E3 ); aClk.start(); A = np.random.random( ( N, N ) ); aClk.stop()
45296
>>> T_lo = 0.25; T_hi = 1 - T_lo; aClk.start(); A = np.where( A > T_hi, T_hi, np.where( A < T_lo, T_lo, A ) ); aClk.stop()
41898
#_______________________________________________________________________________
# [us] SEE THE "noise" FOR block-RE-randomised-RE-RUN-s
#_______________________________________________________________________________