#python #numpy
#python #numpy
Вопрос:
Я обнаружил, что при matmul
использовании двух numpy
массивов, если один из двух является действительной или мнимой частью большего сложного массива, операция может быть в десятки или даже сотни раз медленнее, чем при использовании исходного сложного массива.
Рассмотрим следующий пример:
import numpy as np
from time import time
class timeit():
def __init__(self, string):
self.string = string
def __enter__(self):
self.t0 = time()
def __exit__(self, *args):
print(f'{self.string} : {time() - self.t0}')
A = np.random.rand(200, 1000) 0j
B = np.random.rand(1000, 5000)
with timeit('with complex'):
out = A @ B
Ar = A.real
with timeit('after .real'):
out = Ar @ B
Ai = (A * 1j).imag
with timeit('after .imag'):
out = Ai @ B
with timeit('after .astype(float)'):
out = A.astype(np.float64()) @ B
with timeit('after .real.astype(float)'):
out = A.real.astype(np.float64()) @ B
Вывод
with complex : 0.09374785423278809
after .real : 1.9792003631591797
after .imag : 1.717487096786499
after .astype(float) : 0.016920804977416992
after .real.astype(float) : 0.017952442169189453
Обратите внимание, что когда один из двух массивов A.real
или A.imag
операция выполняется в 20 раз медленнее (число может увеличиваться в сотни раз медленнее, если массивы больше).
Использование A.astype(np.float64)
выполняется очень быстро, но каждый раз выдает предупреждение, даже если мнимая часть равна нулю.
Кажется, что единственное быстрое и тихое решение A.real.astype(float)
, но, честно говоря, оно выглядит довольно уродливо для меня.
Проверяя адрес памяти этого массива, я получаю следующее
def aid(x):
# This function returns the memory
# block address of an array.
return x.__array_interface__['data'][0]
print(f'ID(A.real) == ID(A): {aid(A.real) == aid(A)}')
print(f'ID(A.imag) == ID(A): {aid(A.imag) == aid(A)}')
print(f'ID(A.astype) == ID(A): {aid(A.astype(np.float64())) == aid(A)}')
print(f'ID(A.real.astype) == ID(A): {aid(A.real.astype(np.float64())) == aid(A)}')
это возвращает
ID(A.real) == ID(A): True
ID(A.imag) == ID(A): False
ID(A.astype) == ID(A): False
ID(A.real.astype) == ID(A): False
Это, по-видимому, указывает на то, что A.real
имеет тот же адрес памяти A
A.astype(np.float64)
, что и , в то время как нет. Может ли это быть причиной такого поведения? Однако A
и A.imag
имеют разные адреса памяти, но все равно matmul
очень медленно.
Это ошибка? Является ли решение A.real.astype(np.float64)
тем, которое я должен использовать?
Комментарии:
1. Или, возможно, pickle неправильно выбрасывает большую часть ваших данных.
2. Не могу воспроизвести это в моей системе. оба дают мне примерно 0,2 с, как и ожидалось.
3. @mCoding проверьте редактирование. Также
numpy.array.copy()
решает проблему. На мой взгляд, это не имеет никакого отношения кpickle
4. Не зная, как были созданы исходные массивы, я не думаю, что мы сможем помочь. Я не могу придумать хранилище или свойство массива, которое замедляло бы работу и не проходило бы через
pickle
orcopy
.5. @hpaulj это был в основном мой вопрос. Какое свойство может быть причиной этого и не проходить через
pickle
orcopy
? Или … какое свойство я должен проверить в исходном массиве, что позволило бы мне воспроизвести ошибку? Я попытаюсь привести минимальный рабочий пример, но проблема кажется трудной для воспроизведения (даже если это происходит систематически в моем коде)
Ответ №1:
Это не место в памяти или расположение, которое имеет значение. Это маршрут, который @
выбирается в зависимости от типа ввода. A.real
это не «новый» массив; это метод доступа к реальным значениям сложного dtype . У меня нет времени, чтобы представить мои полные результаты синхронизации, но вот пара быстрых результатов
In [2]: timeit A@B
436 ms ± 32.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
добавление real
замедляет этот путь:
In [3]: timeit A.real@B
3.93 s ± 4.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [4]: %%timeit a = A.real
...: a@B
3.92 s ± 3.29 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Но создание нового массива с плавающей запятой ускоряет процесс:
In [5]: %%timeit a = A.real.copy()
...: a@B
101 ms ± 496 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
A.real
фактически не выполняет никаких вычислений:
In [6]: timeit A.real
203 ns ± 9.42 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
И создание нового массива из реальных значений не так уж и медленно:
In [7]: timeit A.real.copy()
239 µs ± 3.54 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Включение встроенной копии не сокращает время:
In [8]: timeit A.real.copy()@B
102 ms ± 1.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
dot
не беспокоит real
:
In [9]: timeit A.real.dot(B)
106 ms ± 3.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
A.dot(B)
раз так же, как A@B
.
Таким образом, сложная оценка примерно в 4 раза медленнее, чем плавающая. Учитывая, что A
это в два раза больше средних значений, это звучит разумно.
dot
обрабатывает real
правильно, извлекая реальные значения без особых хлопот.
@
имеет какую-то ошибку, отправляя ее на медленный трек.
Комментарии:
1. Так вы думаете, это связано с ошибкой в реализации
@
? В предыдущей версии вопроса я заметил, что.copy()
это решает проблему. Мой вопрос был … почему?2.
matmul
по-видимому, выбирает разные маршруты вычисления в зависимости от характера входных данных. Для прямой прямой оценки массивов с плавающей запятой он передает задачу непосредственно высокооптимизированным подпрограммам типа BLAS. Но это скомпилированный код, поэтому его сложнее исследовать. Могут быть некоторые обсуждения по перемещению github.