Более быстрый способ инициализации массивов с помощью умножения пустой матрицы? (Матлаб)
я наткнулся на странный способ (на мой взгляд), что MATLAB имеет дело с пустой матрицы. Например, если умножить две пустые матрицы, то получится:
zeros(3,0)*zeros(0,3)
ans =
0 0 0
0 0 0
0 0 0
теперь это уже застало меня врасплох, однако быстрый поиск привел меня к ссылке выше, и я получил объяснение несколько искаженной логики того, почему это происходит.
, ничто не подготовило меня к следующему наблюдению. Я спросил себя, насколько эффективен этот тип умножения против просто использования zeros(n)
функция, скажем с целью инициализации? Я использовал timeit
чтобы ответить на этот:
f=@() zeros(1000)
timeit(f)
ans =
0.0033
vs:
g=@() zeros(1000,0)*zeros(0,1000)
timeit(g)
ans =
9.2048e-06
оба имеют одинаковый результат матрицы 1000x1000 нулей класса double
, но пустая матрица умножения один ~350 раз быстрее! (аналогичный результат происходит с помощью tic
и toc
и петля)
как это может быть? являются timeit
или tic,toc
блеф или я нашел более быстрый способ инициализации матриц?
(это было сделано с matlab 2012a, на машине win7-64, intel-i5 650 3.2 Ghz...)
EDIT:
прочитав ваши отзывы, я более внимательно изучил эту особенность и протестировал на 2 разных компьютерах (тот же matlab ver, хотя 2012a) код, который проверяет время выполнения против размера матрицы n. вот что я получаю:
код для генерации этого используется timeit
как и раньше, но цикл с tic
и toc
будет выглядеть так же. Итак, для небольших размеров,zeros(n)
сравнимо. Впрочем, вокруг n=400
есть скачок в производительности для умножения пустой матрицы. Код, который я использовал для создания этого сюжета, был:
n=unique(round(logspace(0,4,200)));
for k=1:length(n)
f=@() zeros(n(k));
t1(k)=timeit(f);
g=@() zeros(n(k),0)*zeros(0,n(k));
t2(k)=timeit(g);
end
loglog(n,t1,'b',n,t2,'r');
legend('zeros(n)','zeros(n,0)*zeros(0,n)',2);
xlabel('matrix size (n)'); ylabel('time [sec]');
кто-нибудь из вас тоже такое?
EDIT #2:
кстати, для получения этого эффекта не требуется умножение пустой матрицы. Можно просто делать:
z(n,n)=0;
где n > некоторый пороговый размер матрицы, видимый на предыдущем графике, и получить точно профиль эффективности как с пустым умножением матрицы (снова используя timeit).
вот пример, где это повышает эффективность кода:
n = 1e4;
clear z1
tic
z1 = zeros( n );
for cc = 1 : n
z1(:,cc)=cc;
end
toc % Elapsed time is 0.445780 seconds.
%%
clear z0
tic
z0 = zeros(n,0)*zeros(0,n);
for cc = 1 : n
z0(:,cc)=cc;
end
toc % Elapsed time is 0.297953 seconds.
, используя z(n,n)=0;
вместо этого дает аналогичные результаты zeros(n)
случае.5 ответов:
это странно, я вижу, что f быстрее, а g медленнее, чем то, что вы видите. Но для меня они оба одинаковы. Возможно, другая версия MATLAB ?
>> g = @() zeros(1000, 0) * zeros(0, 1000); >> f = @() zeros(1000) f = @()zeros(1000) >> timeit(f) ans = 8.5019e-04 >> timeit(f) ans = 8.4627e-04 >> timeit(g) ans = 8.4627e-04
EDIT можете ли вы добавить + 1 для конца f и g, и посмотреть, какое время вы получаете.
EDIT Jan 6, 2013 7: 42 EST
я использую машину удаленно, так что извините за низкое качество графиков (пришлось их генерировать штора.)
машина конфиг:
i7 920. 2.653 ГГц. Линукс. 12 ГБ ОПЕРАТИВНОЙ ПАМЯТИ. 8 МБ кэш.
похоже, что даже машина у меня есть доступ к показывает такое поведение, за исключением большего размера (где-то между 1979 и 2073). Нет никакой причины, по которой я могу думать прямо сейчас, чтобы умножение пустой матрицы было быстрее при больших размерах.
я буду исследовать немного больше, прежде чем прийти спина.
редактировать 11 января 2013
после сообщения @EitanT, я хотел сделать немного больше копать. Я написал некоторый код C, чтобы увидеть, как matlab может создавать матрицу нулей. Вот код c++, который я использовал.
int main(int argc, char **argv) { for (int i = 1975; i <= 2100; i+=25) { timer::start(); double *foo = (double *)malloc(i * i * sizeof(double)); for (int k = 0; k < i * i; k++) foo[k] = 0; double mftime = timer::stop(); free(foo); timer::start(); double *bar = (double *)malloc(i * i * sizeof(double)); memset(bar, 0, i * i * sizeof(double)); double mmtime = timer::stop(); free(bar); timer::start(); double *baz = (double *)calloc(i * i, sizeof(double)); double catime = timer::stop(); free(baz); printf("%d, %lf, %lf, %lf\n", i, mftime, mmtime, catime); } }
вот результаты.
$ ./test 1975, 0.013812, 0.013578, 0.003321 2000, 0.014144, 0.013879, 0.003408 2025, 0.014396, 0.014219, 0.003490 2050, 0.014732, 0.013784, 0.000043 2075, 0.015022, 0.014122, 0.000045 2100, 0.014606, 0.014480, 0.000045
Как видите,
calloc
(4-й столбец), кажется, самый быстрый способ. Он также становится значительно быстрее между 2025 и 2050 годами (я бы предположил, что это будет примерно в 2048 году ?).теперь я вернулся в matlab, чтобы проверить то же самое. Вот результаты.
>> test 1975, 0.003296, 0.003297 2000, 0.003377, 0.003385 2025, 0.003465, 0.003464 2050, 0.015987, 0.000019 2075, 0.016373, 0.000019 2100, 0.016762, 0.000020
похоже, что оба f() и g () используют
calloc
при меньших размерах (malloc +memset
, в то время как g() (нули(m, 0) * нули(0, n)) продолжает использоватьcalloc
.таким образом, расхождение объясняется следующим
- нули(..) начинает использовать другой (медленнее ?) схема на большем размеры.
calloc
также ведет себя несколько неожиданно, что приводит к улучшению производительности.это поведение на Linux. Может ли кто-то сделать тот же эксперимент на другой машине (и, возможно, на другой ОС) и посмотреть, выполняется ли эксперимент ?
результаты могут быть немного обманчивы. Когда вы умножаете две пустые матрицы, результирующая матрица не сразу "выделяется" и "инициализируется", а это откладывается до тех пор, пока вы ее не используете (вроде как ленивая оценка).
то же самое относится, когда индексации запрещен растут переменная, которая в случае числовых массивов заполняет любые отсутствующие записи нулями (я обсуждаю впоследствии нечисловой случай). Конечно рост матрицы таким образом не перезаписывает существующие элементы.
поэтому, хотя это может показаться быстрее, вы просто задерживаете время выделения, пока вы на самом деле не используете матрицу. В конце концов вы будете иметь аналогичные тайминги, как если бы вы сделали выделение с самого начала.
пример, чтобы показать это поведение, по сравнению с несколько другие альтернативы:
N = 1000; clear z tic, z = zeros(N,N); toc tic, z = z + 1; toc assert(isequal(z,ones(N))) clear z tic, z = zeros(N,0)*zeros(0,N); toc tic, z = z + 1; toc assert(isequal(z,ones(N))) clear z tic, z(N,N) = 0; toc tic, z = z + 1; toc assert(isequal(z,ones(N))) clear z tic, z = full(spalloc(N,N,0)); toc tic, z = z + 1; toc assert(isequal(z,ones(N))) clear z tic, z(1:N,1:N) = 0; toc tic, z = z + 1; toc assert(isequal(z,ones(N))) clear z val = 0; tic, z = val(ones(N)); toc tic, z = z + 1; toc assert(isequal(z,ones(N))) clear z tic, z = repmat(0, [N N]); toc tic, z = z + 1; toc assert(isequal(z,ones(N)))
результат показывает, что если вы суммируете прошедшее время для обеих инструкций в каждой случай, вы в конечном итоге с аналогичными общими таймингами:
// zeros(N,N) Elapsed time is 0.004525 seconds. Elapsed time is 0.000792 seconds. // zeros(N,0)*zeros(0,N) Elapsed time is 0.000052 seconds. Elapsed time is 0.004365 seconds. // z(N,N) = 0 Elapsed time is 0.000053 seconds. Elapsed time is 0.004119 seconds.
другие тайминги были:
// full(spalloc(N,N,0)) Elapsed time is 0.001463 seconds. Elapsed time is 0.003751 seconds. // z(1:N,1:N) = 0 Elapsed time is 0.006820 seconds. Elapsed time is 0.000647 seconds. // val(ones(N)) Elapsed time is 0.034880 seconds. Elapsed time is 0.000911 seconds. // repmat(0, [N N]) Elapsed time is 0.001320 seconds. Elapsed time is 0.003749 seconds.
эти измерения слишком малы в миллисекундах и могут быть не очень точными, поэтому вы можете запустить эти команды в цикле несколько тысяч раз и взять среднее значение. Также иногда запуск сохраненных M-функций выполняется быстрее, чем запуск сценариев или в командной строке, так как некоторые оптимизации происходят только таким образом...
в любом случае распределение обычно делается один раз, так что кто заботится, если это займет дополнительные 30 мс :)
аналогичное поведение можно наблюдать с массивами ячеек или массивами структур. Рассмотрим следующий пример:
N = 1000; tic, a = cell(N,N); toc tic, b = repmat({[]}, [N,N]); toc tic, c{N,N} = []; toc
что дает:
Elapsed time is 0.001245 seconds. Elapsed time is 0.040698 seconds. Elapsed time is 0.004846 seconds.
обратите внимание, что даже если они все равны, они занимают разные объемы памяти:
>> assert(isequal(a,b,c)) >> whos a b c Name Size Bytes Class Attributes a 1000x1000 8000000 cell b 1000x1000 112000000 cell c 1000x1000 8000104 cell
на самом деле ситуация здесь немного сложнее, так как MATLAB, вероятно,обмен тот же пустой матрица для всех ячеек, а не создание нескольких копий.
массив ячеек
a
фактически является массивом неинициализированных ячеек (массив нулевых указателей), в то время какb
- это массив ячеек, где каждая ячейка является пустой массив[]
(внутренне и из-за обмена данными, только первая ячейкаb{1}
указывает на[]
в то время как все остальные имеют ссылку на первую ячейку). Последний массивc
похож наa
(неинициализированные ячейки), но с последним содержит пустую числовую матрицу[]
.
я просмотрел список экспортированных функций C из
libmx.dll
(через Dependency Walker), и я нашел несколько интересных вещей.
существуют недокументированные функции для создания неинициализированных массивов:
mxCreateUninitDoubleMatrix
,mxCreateUninitNumericArray
иmxCreateUninitNumericMatrix
. На самом деле есть представление о Обмен использует эти функции для обеспечения более быстрого альтернатива
после проведения некоторых исследований, я нашел в этой статье на "Недокументированный Matlab", в котором Г-Н Яир Альтман уже пришел к выводу, что MathWork способ предварительного распределения матриц используя
zeros(M, N)
действительно не самый эффективный способ.он приурочен
x = zeros(M,N)
иclear x, x(M,N) = 0
и обнаружил, что последний ~в 500 раз быстрее. Согласно его объяснению, второй метод просто создает M-by-N матрица, элементы которой автоматически инициализируются до 0. Однако первый метод создаетx
(Сx
с автоматическими нулевыми элементами), а затем присваивает ноль каждому элементу вx
опять же, и это избыточная операция, которая занимает больше времени.в случае умножения пустой матрицы, например, что вы показали в своем вопросе, MATLAB ожидает, что продукт будет матрицей m×N, и поэтому он выделяет матрицу M×N. Следовательно, выходная матрица автоматически инициализируется до нуля. Поскольку исходные матрицы пусты, дальнейшие вычисления не выполняются, и, следовательно, элементы в выходной матрице остаются неизменными и равными нулю.
этот ответ фальсифицирован. Хранится только для записи. Кажется, matlab решает использовать разреженных матриц, когда он получил команду
z(n,n)=0;
когда n достаточно большой. Внутренняя реализация может быть в виде условия, например: (если newsize>THRESHOLD+oldsize, то используйте sparse...) где порог - это ваш "пороговый размер".однако, это несмотря на Mathworks утверждая: "Matlab никогда создает разреженные матрицы автоматически" (ссылке)
У меня нет Matlab, чтобы проверить это. Тем не менее, использование разреженных матриц (внутренне) является более коротким объяснением (бритва Оккама), следовательно, лучше до фальсификации.