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

119

Мой вопрос: Я заметил, что много хороших ответов на вопросы 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.". Что означает выражение "с включенным однотонным расширением"?

  • 4
    Обратите внимание, что скорость чтения зависит от теста, который вы выполняете. Если вы запустите приведенный выше код после перезапуска Matlab и просто поместите tic...toc вокруг строк, скорость кода будет зависеть от необходимости читать функции в памяти.
  • 0
    @Jonas Да, я только что узнал об этом, прочитав о функции timeit в ссылке, которую вы / angainor / Dan предоставили.
Теги:
arrays
bsxfun

5 ответов

140
Лучший ответ

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

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

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

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

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

Изображение 6279

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

Изображение 6280

Скачок во время выполнения 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
  • 0
    Спасибо за отличный ответ +1. Я пометил этот ответ, так как он является наиболее полным обсуждением и также (на данный момент) получил наибольшее количество голосов.
39

В моем случае я использую 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, у меня меньше ошибок, а я быстрее.

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

  • 1
    Спасибо за ответ Оли. +1, так как я думаю, что этот ответ внес что-то в дополнение к ответам Ангайнора и Йонаса. Мне особенно понравилось, как вы изложили ряд концептуальных проблем, которые должны быть решены в данной строке кода.
16

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

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

  • 7
    Эта статья может быть актуальной: blogs.mathworks.com/loren/2008/08/04/…
  • 0
    @Dan Спасибо за отличный отзыв.
Показать ещё 1 комментарий
11

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

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

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

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

8

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

Изображение 6281

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню