Я только что заметил, что время выполнения script моих почти половин, только изменяя умножение на деление.
Чтобы исследовать это, я написал небольшой пример:
import numpy as np
import timeit
# uint8 array
arr1 = np.random.randint(0, high=256, size=(100, 100), dtype=np.uint8)
# float32 array
arr2 = np.random.rand(100, 100).astype(np.float32)
arr2 *= 255.0
def arrmult(a):
"""
mult, read-write iterator
"""
b = a.copy()
for item in np.nditer(b, op_flags=["readwrite"]):
item[...] = (item + 5) * 0.5
def arrmult2(a):
"""
mult, index iterator
"""
b = a.copy()
for i, j in np.ndindex(b.shape):
b[i, j] = (b[i, j] + 5) * 0.5
def arrmult3(a):
"""
mult, vectorized
"""
b = a.copy()
b = (b + 5) * 0.5
def arrdiv(a):
"""
div, read-write iterator
"""
b = a.copy()
for item in np.nditer(b, op_flags=["readwrite"]):
item[...] = (item + 5) / 2
def arrdiv2(a):
"""
div, index iterator
"""
b = a.copy()
for i, j in np.ndindex(b.shape):
b[i, j] = (b[i, j] + 5) / 2
def arrdiv3(a):
"""
div, vectorized
"""
b = a.copy()
b = (b + 5) / 2
def print_time(name, t):
print("{: <10}: {: >6.4f}s".format(name, t))
timeit_iterations = 100
print("uint8 arrays")
print_time("arrmult", timeit.timeit("arrmult(arr1)", "from __main__ import arrmult, arr1", number=timeit_iterations))
print_time("arrmult2", timeit.timeit("arrmult2(arr1)", "from __main__ import arrmult2, arr1", number=timeit_iterations))
print_time("arrmult3", timeit.timeit("arrmult3(arr1)", "from __main__ import arrmult3, arr1", number=timeit_iterations))
print_time("arrdiv", timeit.timeit("arrdiv(arr1)", "from __main__ import arrdiv, arr1", number=timeit_iterations))
print_time("arrdiv2", timeit.timeit("arrdiv2(arr1)", "from __main__ import arrdiv2, arr1", number=timeit_iterations))
print_time("arrdiv3", timeit.timeit("arrdiv3(arr1)", "from __main__ import arrdiv3, arr1", number=timeit_iterations))
print("\nfloat32 arrays")
print_time("arrmult", timeit.timeit("arrmult(arr2)", "from __main__ import arrmult, arr2", number=timeit_iterations))
print_time("arrmult2", timeit.timeit("arrmult2(arr2)", "from __main__ import arrmult2, arr2", number=timeit_iterations))
print_time("arrmult3", timeit.timeit("arrmult3(arr2)", "from __main__ import arrmult3, arr2", number=timeit_iterations))
print_time("arrdiv", timeit.timeit("arrdiv(arr2)", "from __main__ import arrdiv, arr2", number=timeit_iterations))
print_time("arrdiv2", timeit.timeit("arrdiv2(arr2)", "from __main__ import arrdiv2, arr2", number=timeit_iterations))
print_time("arrdiv3", timeit.timeit("arrdiv3(arr2)", "from __main__ import arrdiv3, arr2", number=timeit_iterations))
Отпечатывает следующие тайминги:
uint8 arrays
arrmult : 2.2004s
arrmult2 : 3.0589s
arrmult3 : 0.0014s
arrdiv : 1.1540s
arrdiv2 : 2.0780s
arrdiv3 : 0.0027s
float32 arrays
arrmult : 1.2708s
arrmult2 : 2.4120s
arrmult3 : 0.0009s
arrdiv : 1.5771s
arrdiv2 : 2.3843s
arrdiv3 : 0.0009s
Я всегда думал, что умножение вычисляется дешевле, чем деление. Однако для uint8
деление, по-видимому, почти в два раза эффективнее. Связано ли это с тем фактом, что * 0.5
должен вычислить умножение в поплавке и затем вернуть результат обратно к целому числу?
По крайней мере, для поплавков умножения кажутся быстрее, чем деления. Это вообще правда?
Почему умножение в uint8
более экспансивное, чем в float32
? Я думал, что 8-разрядное целое число без знака должно быть намного быстрее для вычисления, чем 32-битные поплавки?!
Может кто-нибудь "демистифицировать" это?
EDIT: чтобы иметь больше данных, я включил векторизованные функции (например, предлагаемые) и добавил итераторы индекса. Эти векторизованные функции намного быстрее, что на самом деле не сравнимо. Однако, если timeit_iterations
задано намного выше для векторизованных функций, оказывается, что умножение выполняется быстрее для обоих, uint8
и float32
. Думаю, это смущает еще больше?!
Возможно, умножение на самом деле всегда быстрее, чем деление, но основные утечки производительности в for-loops - это не арифметическая операция, а сам цикл. Хотя это не объясняет, почему петли ведут себя по-разному для разных операций.
EDIT2. Как уже говорилось, @jotasi, мы ищем полное объяснение division
vs. multiplication
и int
(или uint8
) по сравнению с float
( или float32
). Кроме того, было бы интересно пояснить различные тенденции векторизованных подходов и итераторов, как в векторизованном случае, деление кажется более медленным, тогда как в итераторе оно быстрее.