MATLAB Language
Производительность и бенчмаркинг
Поиск…
замечания
- Профилирующий код - это способ избежать страшной практики « преждевременной оптимизации », сосредоточив внимание разработчиков на тех частях кода, которые фактически оправдывают усилия по оптимизации.
- Документация MATLAB под названием « Измерение эффективности вашей программы ».
Определение узких мест производительности с помощью Profiler
MATLAB Profiler - это инструмент для профилирования программного кода MATLAB. Используя Profiler, можно получить визуальное представление как времени выполнения, так и потребления памяти.
Запуск Profiler можно выполнить двумя способами:
Нажатие кнопки «Выполнить и время» в графическом интерфейсе MATLAB при открытии некоторого файла
.m
в редакторе (добавлено в R2012b ).Программно, используя:
profile on <some code we want to test> profile off
Ниже приведен пример кода примера и результат его профилирования:
function docTest
for ind1 = 1:100
[~] = var(...
sum(...
randn(1000)));
end
spy
Из вышесказанного мы узнаем, что функция spy
занимает около 25% от общего времени выполнения. В случае «реального кода» функция, которая занимает такой большой процент времени выполнения, будет хорошим кандидатом на оптимизацию, в отличие от функций, аналогичных var
и cla
, оптимизация которых следует избегать.
Кроме того, можно щелкнуть записи в столбце « Имя функции», чтобы просмотреть подробную разбивку времени выполнения для этой записи. Вот пример щелчка spy
:
Также возможно профилировать потребление памяти, выполняя profile('-memory')
перед запуском Profiler.
Сравнение времени выполнения нескольких функций
Широко используемая комбинация tic
и toc
может дать приблизительное представление о времени выполнения функции или фрагментов кода.
Для сравнения нескольких функций он не должен использоваться. Зачем? Почти невозможно обеспечить равные условия для всех фрагментов кода для сравнения в скрипте с использованием вышеприведенного решения. Возможно, функции имеют одно и то же функциональное пространство и общие переменные, поэтому более поздние функции и фрагменты кода уже используют ранее инициализированные переменные и функции. Также нет никакой информации о том, будет ли компилятор JIT обрабатывать эти впоследствии называемые фрагменты одинаково.
timeit
функция для эталонных тестов - timeit
. Следующий пример иллюстрирует его использование.
Есть массив A
и матрица B
Следует определить, какая строка B
наиболее похожа на A
, подсчитывая количество разных элементов.
function t = bench()
A = [0 1 1 1 0 0];
B = perms(A);
% functions to compare
fcns = {
@() compare1(A,B);
@() compare2(A,B);
@() compare3(A,B);
@() compare4(A,B);
};
% timeit
t = cellfun(@timeit, fcns);
end
function Z = compare1(A,B)
Z = sum( bsxfun(@eq, A,B) , 2);
end
function Z = compare2(A,B)
Z = sum(bsxfun(@xor, A, B),2);
end
function Z = compare3(A,B)
A = logical(A);
Z = sum(B(:,~A),2) + sum(~B(:,A),2);
end
function Z = compare4(A,B)
Z = pdist2( A, B, 'hamming', 'Smallest', 1 );
end
Этот метод тестирования был впервые замечен в этом ответе .
Это нормально быть «единственным»!
Обзор:
Тип данных по умолчанию для числовых массивов в MATLAB является double
. double
- это число чисел с плавающей запятой , и этот формат занимает 8 байтов (или 64 бит) за каждое значение. В некоторых случаях, когда, например, работа с целыми числами или когда числовая неустойчивость не является непосредственной проблемой, такая высокая глубина бит может не потребоваться. По этой причине рекомендуется учитывать преимущества single
точности (или других подходящих типов ):
- Более быстрое время выполнения (особенно заметно на графических процессорах).
- Половина потребления памяти: может преуспеть, если
double
сбой из-за ошибки из-за памяти; более компактный при хранении в виде файлов.
Преобразование переменной из любого поддерживаемого типа данных в single
выполняется с помощью:
sing_var = single(var);
Некоторые обычно используемые функции (такие как: zeros
, eye
, ones
и т. Д. ), Которые по умолчанию выдают double
значения, позволяют указать тип / класс вывода.
Преобразование переменных в сценарий в нестандартную точность / тип / класс:
По состоянию на июль 2016 года не существует документального способа изменения типа данных MATLAB по умолчанию из double
.
В MATLAB новые переменные обычно имитируют типы данных переменных, используемых при их создании. Чтобы проиллюстрировать это, рассмотрим следующий пример:
A = magic(3);
B = diag(A);
C = 20*B;
>> whos C
Name Size Bytes Class Attributes
C 3x1 24 double
A = single(magic(3)); % A is converted to "single"
B = diag(A);
C = B*double(20); % The stricter type, which in this case is "single", prevails
D = single(size(C)); % It is generally advised to cast to the desired type explicitly.
>> whos C
Name Size Bytes Class Attributes
C 3x1 12 single
Таким образом, может показаться достаточным для перевода / преобразования нескольких начальных переменных, чтобы изменение пронизывало всюду по коду - однако это не рекомендуется (см. Ниже « Оговорки и ошибки» ).
Предостережения и ошибки:
Повторные конверсии не рекомендуется из-за введения числового шума (при литье из
single
вdouble
) или потери информации (при кастинге отdouble
доsingle
или между определенными целыми типами ), например:double(single(1.2)) == double(1.2) ans = 0
Это можно немного смягчить, используя
typecast
. См. Также Будьте в курсе неточности с плавающей запятой .Опираясь исключительно на неявное типирование данных (то есть, что MATLAB догадывается, какой тип вывода вычисления должен быть) не рекомендуется из-за нескольких нежелательных эффектов, которые могут возникнуть:
Потеря информации : когда ожидается
double
результат, но небрежное сочетаниеsingle
иdouble
операндов даетsingle
точность.Неожиданно высокая потребляемая память : когда ожидается
single
результат, но небрежное вычисление приводит кdouble
выходу.Ненужные накладные расходы при работе с графическими процессорами : при смешивании типов
gpuArray
(т.gpuArray
Переменных, хранящихся в VRAM) с переменнымиgpuArray
(то есть, которые обычно хранятся в ОЗУ) данные должны быть переданы так или иначе, прежде чем вычисление может быть выполнено. Эта операция требует времени и может быть очень заметна при повторных вычислениях.Ошибки при смешивании типов с плавающей запятой с целыми типами : такие функции, как
mtimes
(*
), не определены для смешанных входов целых чисел и типов с плавающей точкой - и будут ошибки. Функции типаtimes
(.*
) Вообще не определены для входов целых типов - и снова будут ошибки.>> ones(3,3,'int32')*ones(3,3,'int32') Error using * MTIMES is not fully supported for integer classes. At least one input must be scalar. >> ones(3,3,'int32').*ones(3,3,'double') Error using .* Integers can only be combined with integers of the same class, or scalar doubles.
Для лучшей читаемости коды и снижения риска нежелательных видов, оборонительный подход рекомендуются, где переменные явно приводятся к нужному типу.
Смотрите также:
- Документация MATLAB: номера с плавающей запятой .
- Техническая статья Mathworks: лучшие практики для преобразования кода MATLAB в фиксированную точку .
переупорядочить ND-массив может улучшить общую производительность
В некоторых случаях нам нужно применять функции к набору ND-массивов. Давайте посмотрим на этот простой пример.
A(:,:,1) = [1 2; 4 5];
A(:,:,2) = [11 22; 44 55];
B(:,:,1) = [7 8; 1 2];
B(:,:,2) = [77 88; 11 22];
A =
ans(:,:,1) =
1 2
4 5
ans(:,:,2) =
11 22
44 55
>> B
B =
ans(:,:,1) =
7 8
1 2
ans(:,:,2) =
77 88
11 22
Обе матрицы 3D, скажем, мы должны вычислить следующее:
result= zeros(2,2);
...
for k = 1:2
result(i,j) = result(i,j) + abs( A(i,j,k) - B(i,j,k) );
...
if k is very large, this for-loop can be a bottleneck since MATLAB order the data in a column major fashion. So a better way to compute "result" could be:
% trying to exploit the column major ordering
Aprime = reshape(permute(A,[3,1,2]), [2,4]);
Bprime = reshape(permute(B,[3,1,2]), [2,4]);
>> Aprime
Aprime =
1 4 2 5
11 44 22 55
>> Bprime
Bprime =
7 1 8 2
77 11 88 22
Теперь мы заменим вышеприведенный цикл следующим образом:
result= zeros(2,2);
....
temp = abs(Aprime - Bprime);
for k = 1:2
result(i,j) = result(i,j) + temp(k, i+2*(j-1));
...
Мы перегруппировали данные, чтобы мы могли использовать кэш-память. Пермутация и изменение могут быть дорогостоящими, но при работе с большими ND-массивами вычислительная стоимость, связанная с этими операциями, намного ниже, чем работа с не организованными массивами.
Важность предварительного распределения
Массивы в MATLAB хранятся в виде непрерывных блоков в памяти, которые автоматически выделяются и выпускаются MATLAB. MATLAB скрывает операции управления памятью, такие как изменение размера массива за простым в использовании синтаксисом:
a = 1:4
a =
1 2 3 4
a(5) = 10 % or alternatively a = [a, 10]
a =
1 2 3 4 10
Важно понимать, что вышеизложенное не является тривиальной операцией, a(5) = 10
заставит MATLAB выделить новый блок памяти размером 5, скопировать первые 4 числа и установить 5'-10. Это операция O(numel(a))
, а не O(1)
.
Рассмотрим следующее:
clear all
n=12345678;
a=0;
tic
for i = 2:n
a(i) = sqrt(a(i-1)) + i;
end
toc
Elapsed time is 3.004213 seconds.
a
перераспределяется n
раз в этом цикле (исключая некоторые оптимизации, предпринятые MATLAB)! Обратите внимание, что MATLAB дает нам предупреждение:
«Переменная« a », по-видимому, меняет размер на каждой итерации цикла. Подумайте о предварительном распределении для скорости».
Что происходит, когда мы предварительно распределяем?
a=zeros(1,n);
tic
for i = 2:n
a(i) = sqrt(a(i-1)) + i;
end
toc
Elapsed time is 0.410531 seconds.
Мы видим, что время выполнения сокращается на порядок.
Методы предварительного распределения:
MATLAB предоставляет различные функции для размещения векторов и матриц в зависимости от конкретных требований пользователя. К ним относятся: zeros
, ones
, nan
, eye
, true
и т. Д.
a = zeros(3) % Allocates a 3-by-3 matrix initialized to 0
a =
0 0 0
0 0 0
0 0 0
a = zeros(3, 2) % Allocates a 3-by-2 matrix initialized to 0
a =
0 0
0 0
0 0
a = ones(2, 3, 2) % Allocates a 3 dimensional array (2-by-3-by-2) initialized to 1
a(:,:,1) =
1 1 1
1 1 1
a(:,:,2) =
1 1 1
1 1 1
a = ones(1, 3) * 7 % Allocates a row vector of length 3 initialized to 7
a =
7 7 7
Также может быть указан тип данных:
a = zeros(2, 1, 'uint8'); % allocates an array of type uint8
Также легко клонировать размер существующего массива:
a = ones(3, 4); % a is a 3-by-4 matrix of 1's
b = zeros(size(a)); % b is a 3-by-4 matrix of 0's
И клонируйте тип:
a = ones(3, 4, 'single'); % a is a 3-by-4 matrix of type single
b = zeros(2, 'like', a); % b is a 2-by-2 matrix of type single
обратите внимание, что «как» также клонирует сложность и разреженность .
Предварительное выделение неявно достигаются с помощью какой - либо функции , которая возвращает массив конечного требуемого размера, например, rand
, gallery
, kron
, bsxfun
, colon
и многие другие. Например, общий способ выделения векторов с линейно изменяющимися элементами - это использование оператора двоеточия (с вариантом 2 или 3-операнда 1 ):
a = 1:3
a =
1 2 3
a = 2:-3:-4
a =
2 -1 -4
Ячейные массивы могут быть выделены с помощью функции cell()
почти так же, как и zeros()
.
a = cell(2,3)
a =
[] [] []
[] [] []
Обратите внимание, что массивы ячеек работают, удерживая указатели в ячейках памяти в содержимом ячейки. Таким образом, все подсказки preallocation применяются также к отдельным элементам массива ячеек.
Дальнейшее чтение:
- Официальная документация MATLAB « Предварительная память ».
- Официальная документация MATLAB « Как MATLAB выделяет память ».
- Производительность предварительного распределения на недокументированной матрице .
- Понимание массива Prelocation на Loren по искусству MATLAB