Когда в MATLAB оптимально использовать bsxfun?

Я заметил, что многие хорошие ответы на вопросы MATLAB о переполнении стека часто используют функцию bsxfun. Почему?

Мотивация: В документации MATLAB для bsxfun предоставляется следующий пример:

A = magic(5);
A = bsxfun(@minus, A, mean(A))

Конечно, мы могли бы проделать ту же операцию, используя:

A = A - (ones(size(A, 1), 1) * mean(A));

И на самом деле простой тест скорости показывает, что второй метод примерно на 20% быстрее. Так зачем использовать первый метод? Я предполагаю, что в некоторых обстоятельствах использование bsxfun будет намного быстрее, чем ручной подход. Мне было бы очень интересно увидеть пример такой ситуации и объяснение, почему это быстрее.

Кроме того, последний элемент этого вопроса, снова из документации MATLAB для bsxfun: C = bsxfun (fun, A, B) применяет поэлементную двоичную операцию, указанную дескриптором функции fun к массивам A и B, с одиночным элементом расширение включено .. Что означает фраза с включенным одноэлементным расширением?


person Colin T Bowers    schedule 18.10.2012    source источник
comment
Обратите внимание, что полученная скорость чтения зависит от выполняемого теста. Если вы запустите приведенный выше код после перезапуска Matlab и просто поместите tic...toc вокруг строк, скорость кода будет зависеть от необходимости считывать функции в память.   -  person Jonas    schedule 18.10.2012
comment
@Jonas Да, я только что узнал об этом, прочитав о функции timeit в ссылке, которую вы / angainor / Dan предоставили.   -  person Colin T Bowers    schedule 19.10.2012


Ответы (5)


Я использую bsxfun по трем причинам (документация, ссылка на блог)

  1. bsxfun быстрее, чем repmat (см. Ниже)
  2. bsxfun требует меньше ввода
  3. Использование bsxfun, как и использование accumarray, дает мне хорошее представление о моем понимании MATLAB.

bsxfun будет реплицировать входные массивы по их одноэлементным измерениям, то есть измерениям, по которым размер массива равен 1, так что они будут соответствовать размеру соответствующего измерения другого массива. Это то, что называется одноэлементным расширением. Кстати, одноэлементные измерения - это те, которые будут отброшены, если вы вызовете squeeze.

Возможно, что для очень небольших проблем подход repmat будет быстрее, но при таком размере массива обе операции выполняются настолько быстро, что, скорее всего, это не повлияет на общую производительность. Есть две важные причины, по которым bsxfun работает быстрее: (1) вычисление происходит в скомпилированном коде, что означает, что фактическая репликация массива никогда не происходит, и (2) bsxfun является одной из многопоточных функций MATLAB.

Я провел сравнение скорости между repmat и bsxfun с помощью MATLAB R2012b на моем прилично быстром ноутбуке.

Введите описание изображения здесь

Для меня bsxfun примерно в три раза быстрее, чем repmat. Разница становится более заметной, если массивы становятся больше:

Введите описание изображения здесь

Скачок во времени выполнения repmat происходит около размера массива в 1 МБ, что может иметь какое-то отношение к размеру моего кеш-памяти процессора - bsxfun не становится таким плохим скачком, потому что ему нужно только выделить выходной массив.

Ниже вы найдете код, который я использовал для определения времени:

n = 300;
k=1; %# k=100 for the second graph
a = ones(10,1);
rr = zeros(n,1);
bb = zeros(n,1);
ntt = 100;
tt = zeros(ntt,1);
for i=1:n;
   r = rand(1,i*k);
   for it=1:ntt;
      tic,
      x = bsxfun(@plus,a,r);
      tt(it) = toc;
   end;
   bb(i) = median(tt);
   for it=1:ntt;
      tic,
      y = repmat(a,1,i*k) + repmat(r,10,1);
      tt(it) = toc;
   end;
   rr(i) = median(tt);
end
person Jonas    schedule 18.10.2012
comment
Спасибо за отличный ответ +1. Я отметил это как ответ, так как это наиболее подробное обсуждение, и оно также (на данный момент) получило наибольшее количество голосов "за". - person Colin T Bowers; 19.10.2012

В моем случае я использую bsxfun, потому что это позволяет мне не думать о проблемах столбца или строки.

Чтобы написать свой пример:

A = A - (ones(size(A, 1), 1) * mean(A));

Мне предстоит решить несколько задач:

  1. size(A,1) or size(A,2)

  2. ones(sizes(A,1),1) or ones(1,sizes(A,1))

  3. ones(size(A, 1), 1) * mean(A) or mean(A)*ones(size(A, 1), 1)

  4. mean(A) or mean(A,2)

Когда я использую bsxfun, мне просто нужно решить последний вопрос:

a) mean(A) or mean(A,2)

Вы можете подумать, что это лениво или что-то в этом роде, но когда я использую bsxfun, у меня появляется меньше ошибок, и я программирую быстрее.

Кроме того, он короче, что улучшает скорость набора и читаемость.

person Oli    schedule 18.10.2012
comment
Спасибо за ответ Оли. +1 поскольку я думаю, что этот ответ внес что-то в дополнение к ответам ангайнора и Джонаса. Мне особенно понравилось, как вы изложили количество концептуальных проблем, которые необходимо решить в данной строке кода. - person Colin T Bowers; 19.10.2012

Очень интересный вопрос! Недавно я наткнулся на именно такую ​​ситуацию, отвечая на этот вопрос. Рассмотрим следующий код, который вычисляет индексы скользящего окна размера 3 через вектор a:

a = rand(1e7, 1);

tic;
idx = bsxfun(@plus, [0:2]', 1:numel(a)-2);
toc

% Equivalent code from im2col function in MATLAB
tic;
idx0 = repmat([0:2]', 1, numel(a)-2);
idx1 = repmat(1:numel(a)-2, 3, 1);
idx2 = idx0+idx1;
toc;

isequal(idx, idx2)

Elapsed time is 0.297987 seconds.
Elapsed time is 0.501047 seconds.

ans =

 1

В этом случае bsxfun почти вдвое быстрее! Это полезно и быстро, потому что оно позволяет избежать явного выделения памяти для матриц idx0 и idx1, сохраняя их в памяти, а затем считывая их снова, чтобы добавить их. Поскольку полоса пропускания памяти является ценным активом и часто является узким местом в современных архитектурах, вы хотите использовать ее с умом и уменьшить требования к памяти вашего кода для повышения производительности.

bsxfun позволяет вам делать именно это: создавать матрицу, основанную на применении произвольного оператора ко всем парам элементов двух векторов, вместо того, чтобы явно работать с двумя матрицами, полученными путем репликации векторов. Это одноэлементное расширение. Вы также можете рассматривать его как внешний продукт из BLAS:

v1=[0:2]';
v2 = 1:numel(a)-2;
tic;
vout = v1*v2;
toc
Elapsed time is 0.309763 seconds.

Вы умножаете два вектора, чтобы получить матрицу. Просто внешний продукт выполняет только умножение, а bsxfun может применять произвольные операторы. Кстати, очень интересно увидеть, что bsxfun работает так же быстро, как внешний продукт BLAS. И обычно считается, что BLAS обеспечивает производительность ...

Благодаря комментарию Дэна, вот отличный статья Лорен, в которой обсуждается именно это.

person angainor    schedule 18.10.2012
comment
Эта статья может быть актуальной: blogs.mathworks .com / loren / 2008/08/04 / - person Dan; 18.10.2012
comment
@Dan Спасибо за отличную ссылку. - person angainor; 18.10.2012
comment
Спасибо за отличный ответ angainor. +1 за то, что первым четко обозначил главное преимущество bsxfun на хорошем примере. - person Colin T Bowers; 19.10.2012

Начиная с R2016b, MATLAB поддерживает неявное расширение для самых разных операторов, поэтому в большинстве случаев нет необходимости использовать bsxfun:

Ранее эта функция была доступна через функцию bsxfun. Теперь рекомендуется заменить большинство случаев использования bsxfun прямыми вызовами функций и операторов, поддерживающих неявное раскрытие. По сравнению с использованием bsxfun неявное раскрытие обеспечивает более высокую скорость, лучшее использование памяти и улучшенную читаемость кода.

Здесь есть подробное обсуждение Неявное расширение и его эффективность в блоге Лорен. Чтобы процитировать Стив Эддинс из MathWorks:

В R2016b неявное раскрытие в большинстве случаев работает так же быстро или быстрее, чем bsxfun. Наилучший прирост производительности для неявного раскрытия достигается при небольших размерах матрицы и массива. Для матриц больших размеров неявное расширение имеет примерно такую ​​же скорость, как и bsxfun.

person nirvana-msu    schedule 07.10.2016

Вещи не всегда согласуются с 3-мя общими методами: repmat, расширение за счет индексации и bsxfun. Это становится намного интереснее, когда вы еще больше увеличиваете размер вектора. Смотрите сюжет:

сравнение

bsxfun на самом деле в какой-то момент становится немного медленнее, чем два других, но что меня удивило, так это то, что если вы увеличите размер вектора еще больше (> 13E6 выходных элементов), bsxfun внезапно снова станет быстрее примерно в 3 раза. Кажется, что их скорости скачкообразно скачут, и порядок не всегда одинаков. Я предполагаю, что это также может зависеть от размера процессора / памяти, но в целом я думаю, что я буду придерживаться bsxfun, когда это возможно.

person Justin Wong    schedule 19.10.2013