Когда векторизация является лучшим или худшим решением, чем цикл?

В Matlab я пытаюсь векторизовать свой код, чтобы сократить время моделирования. Однако в результате я получил снижение общей эффективности.

Чтобы понять это явление, я создал 3 разные функции, которые делают то же самое, но с другим подходом:

Основной файл:

clc,
clear,

n = 10000;
Value = cumsum(ones(1,n));

NbLoop = 10000;

time01 = zeros(1,NbLoop);
time02 = zeros(1,NbLoop);
time03 = zeros(1,NbLoop);

for test = 1 : NbLoop

    tic
    vector1 =  function01(n,Value);
    time01(test) = toc ;

    tic
    vector2 =  function02(n,Value);
    time02(test) = toc ;

    tic
    vector3 =  function03(n,Value);
    time03(test) = toc ; 

end

figure(1)
hold on

plot( time01, 'b')
plot( time02, 'g')
plot( time03, 'r')

Функция 01:

function vector =  function01(n,Value)

vector = zeros( 2*n,1);
for k = 1:n
    vector(2*k -1) =  Value(k);
    vector(2*k) =  Value(k);
end

end

Функция 02:

function vector =  function02(n,Value)

vector = zeros( 2*n,1);
vector(1:2:2*n) = Value; 
vector(2:2:2*n) = Value; 

end

Функция 03:

function vector =  function03(n,Value)

MatrixTmp = transpose([Value(:), Value(:)]);
vector = MatrixTmp (:);

end

Синий график соответствует циклу for.

n = 100:

n = 100

n = 10000:

n = 10000

Когда я запускаю код с n = 100, более эффективным решением является первая функция с циклом for. Когда n = 10000 Первая функция становится менее эффективной.

  • У вас есть способ узнать, как и когда правильно заменить цикл for векторизованным аналогом?
  • Какое влияние оказывает поиск по индексу с массивом огромных размеров?
  • Вычисляет ли Matlab по-другому массив размерности 3 или выше, чем массив размерности 1 или 2?
  • Есть ли умный способ заменить цикл while, который использует результат итерации для следующей итерации?

person Chewbaka    schedule 30.08.2019    source источник
comment
Для повышения точности тестов производительности используется timeit вместо _2 _ / _ 3_. Поскольку вы написали свои тесты как функции, переключиться на использование timeit должно быть легко. Также будет полезно, если вы отредактируете свой вопрос, включив в него графики, чтобы нам не пришлось запускать ваш код. чтобы увидеть результаты.   -  person Wolfie    schedule 30.08.2019
comment
Здравствуйте, спасибо за ответ, вы правы, цифры добавил. Я не знал этой функции, спасибо вам за это. С timeit результаты с n = 100 лучше для цикла for. Однако я также получил это предупреждение: Предупреждение: измеренное время для F может быть неточным, потому что он идет слишком быстро. Попробуйте измерить то, что занимает больше времени.   -  person Chewbaka    schedule 30.08.2019
comment
И timeit, и _2 _ / _ 3_ имеют ограниченную точность. Помимо повторения вашей процедуры несколько тысяч раз и измерения совокупных показателей, используйте сторонние таймеры, такие как этот, если вам нужно больше точности. Кроме того, ни одна из ваших функций не кажется мне векторизацией, которая более или менее связана с использованием собственных функций Matlab для задач, охватывающих весь массив - итератор не включен, а function03 не использует свой первый ввод.   -  person Argyll    schedule 01.09.2019
comment
Не могли бы вы дать мне названия некоторых из этих функций: собственные функции Matlab для задач, охватывающих весь массив? это похоже на logical?   -  person Chewbaka    schedule 02.09.2019
comment
@Agyll: timeit имеет очень высокую точность. Он вычисляет, сколько раз запускать ваш код для получения значимых значений, он предварительно нагревает среду и выясняет, какие накладные расходы связаны с циклом, чтобы удалить это из времени. Предложенная вами альтернатива ничего из этого не делает. Не думаю, что это хорошо.   -  person Cris Luengo    schedule 02.09.2019
comment
@CrisLuengo: Дело в том, что я не знаю, как timeit достигает точности вывода, которая часто равна E-11 в секундах. Я не знаю, является ли такая точность просто тем, что tic/toc обычно делят на количество повторений или выполняется надлежащий статистический анализ. Ты? В любом случае, пакет Houtzager выводит пикосекунды. Принимая во внимание чистую монету, его пакет, кажется, может получить максимально возможную точность от времени часов процессора, в то время как timeit не может.   -  person Argyll    schedule 02.09.2019
comment
@CrisLuengo: Учитывая разницу в точности, для любого набора данных приличного размера вам не нужно измерять совокупное время выполнения. Вы можете измерить время выполнения каждого прохода и среднее значение. Предварительный прогрев можно выполнить вручную, и вы можете убедиться, что ваш процессор нагружен, прежде чем проводить измерения. Так что эти 3 преимущества timeit на самом деле не применимы.   -  person Argyll    schedule 02.09.2019
comment
@Agyll: Эти преимущества применимы, потому что люди не знают, как это делать правильно. Пикосекундные измерения - это BS, если вы не принимаете во внимание накладные расходы на вызов функций синхронизации и т. Д. Кроме того, повторные измерения чего-то, что занимает 1 мс, дает значения с очень широким диапазоном. Это потому, что на вашем компьютере одновременно происходит множество других вещей. Усреднение - неправильный подход. timeit измеряет количество повторений кода, достаточное для того, чтобы разница во времени была значимой, затем повторяет измерение, чтобы избежать ошибок из-за других фоновых заданий.   -  person Cris Luengo    schedule 02.09.2019
comment
@CrisLuengo: время процессора должно быть в пикосекундах. Да, ошибки времени доступа составляют десятки нс или более в зависимости от того, как осуществляется доступ ко времени. Я просто указал точность вывода пакета Houtzager. Принимая во внимание чистую монету, я предполагаю, что код имеет максимально возможную точность. Я думаю, вы хотели добавить ссылку?   -  person Argyll    schedule 02.09.2019
comment
@Ageryll: если вы хотите прочитать о timeit, вы можете увидеть Запись Стива или просто edit timeit.   -  person Cris Luengo    schedule 02.09.2019
comment
@ Крис Луенго: Хорошо. Спасибо за ссылку.   -  person Argyll    schedule 06.09.2019


Ответы (1)


Используя MATLAB Online, я вижу нечто иное:

n            10000       100
function01   5.6248e-05  2.2246e-06
function02   1.7748e-05  1.9491e-06
function03   2.7748e-05  1.2278e-06
function04   1.1056e-05  7.3390e-07  (my version, see below)

Таким образом, версия цикла всегда самая медленная. Метод №2 быстрее для очень больших матриц, Метод №3 быстрее для очень маленьких матриц.

Причина в том, что метод № 3 создает 2 копии данных (transpose или матрица принимает копию), и это плохо, если данных много. Метод № 2 использует индексирование, которое дорого, но не так дорого, как двойное копирование большого количества данных.

Вместо этого я бы предложил эту функцию (метод № 4), которая перемещает только векторы (что по сути является бесплатным). Это простая модификация вашего метода №3:

function vector = function04(n,Value)
vector = [Value(:).'; Value(:).'];
vector = vector(:);
end

У вас есть способ узнать, как и когда правильно заменить цикл for векторизованным аналогом?

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

Какое влияние оказывает поиск по индексу с массивом огромных размеров?

Это относится к таким операциям, как d = data(data==0). Как и все остальное, это эффективно для небольших данных и в меньшей степени для больших данных, потому что data==0 является промежуточным массивом того же размера, что и data.

Вычисляет ли Matlab по-другому массив размерности 3 или выше, чем массив размерности 1 или 2?

Нет, не в общем. Такие функции, как sum, реализованы независимо от размерности требуется ссылка.

Есть ли умный способ заменить цикл while, который использует результат итерации для следующей итерации?

Это во многом зависит от того, что это за операции. Такие функции, как cumsum, часто можно использовать для векторизации этого типа кода, но не всегда.


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

%n = 10000;
n = 100;
Value = cumsum(ones(1,n));

vector1 = function01(n,Value);
vector2 = function02(n,Value);
vector3 = function03(n,Value);
vector4 = function04(n,Value);
assert(isequal(vector1,vector2))
assert(isequal(vector1,vector3))
assert(isequal(vector1,vector4))

timeit(@()function01(n,Value))
timeit(@()function02(n,Value))
timeit(@()function03(n,Value))
timeit(@()function04(n,Value))

function vector = function01(n,Value)
vector = zeros(2*n,1);
for k = 1:n
    vector(2*k-1) = Value(k);
    vector(2*k) = Value(k);
end
end

function vector = function02(n,Value)
vector = zeros(2*n,1);
vector(1:2:2*n) = Value; 
vector(2:2:2*n) = Value; 
end

function vector = function03(n,Value)
MatrixTmp = transpose([Value(:), Value(:)]);
vector = MatrixTmp(:);
end

function vector = function04(n,Value)
vector = [Value(:).'; Value(:).'];
vector = vector(:);
end
person Cris Luengo    schedule 30.08.2019
comment
Спасибо за подробный ответ, думаю, действительно проблема в создании промежуточной матрицы. Я обычно использую reshape, repmat и transpose, которые, как вы сказали, ухудшают время. Под поиском по индексу я имел в виду что-то вроде использования IDs = logical('Big Matrix == expression' ) и извлечения данных путем создания нового элемента: ExtractedData = BigMatrix(IDs). Поэтому я создал множество промежуточных элементов. - person Chewbaka; 02.09.2019
comment
@Chewbaka: тебе там logical не нужно, результат == уже логичен. Я обновил ответ, чтобы ответить на этот вопрос. - person Cris Luengo; 02.09.2019
comment
О, кстати, reshape по сути бесплатен. permute, transpose и repmat требуют копии данных. - person Cris Luengo; 02.09.2019