В Matlab, когда оптимально использовать bsxfun?

Мой вопрос: Я заметил, что много хороших ответов на вопросы Matlab на SO часто используют функцию 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) применяет двоичную операцию по элементам, указанную функцией handle fun для массивов A и B, с включенным режимом Singleton.". Что означает выражение "с включенным однотонным расширением"?

Ответ 1

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

  • bsxfun быстрее, чем repmat (см. ниже)
  • bsxfun требует меньше ввода
  • Используя bsxfun, например, используя accumarray, я чувствую себя хорошо в своем понимании Matlab.

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

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

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

enter image description here

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

enter image description here

Скачок во время выполнения 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

Ответ 2

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

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

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

Мне нужно решить несколько проблем:

1) size(A,1) или size(A,2)

2) ones(sizes(A,1),1) или ones(1,sizes(A,1))

3) ones(size(A, 1), 1) * mean(A) или mean(A)*ones(size(A, 1), 1)

4) mean(A) или mean(A,2)

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

a) mean(A) или mean(A,2)

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

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

Ответ 3

Очень интересный вопрос! Я недавно наткнулся на такую ​​ситуацию, отвечая на этот вопрос. Рассмотрим следующий код, который вычисляет индексы скользящего окна размером 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 обычно считается для обеспечения производительности.

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

Ответ 4

Как и в R2016b, Matlab поддерживает Implicit Expansion для широкого круга операторов, поэтому в большинстве случаев больше не нужно использовать bsxfun:

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

Там подробное обсуждение "Неявное расширение" и его производительность в блоге Loren. К quote Стив Эддинс из MathWorks:

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

Ответ 5

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

comparison

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