Действительно ли A == 0 лучше, чем ~ A?
Введение в настройку проблемы
Я делал некоторые тесты, связанные с ~A
и A==0
для double array with no NaNs
, оба из которых конвертируют A
в логический массив, где все zeros
преобразуются в значения true
, а остальные устанавливаются как false
.
Для бенчмаркинга я использовал три набора входных данных -
- Очень маленькие и мелкие данные -
15:5:100
- Данные малого и среднего размера -
50:40:1000
- Средние и большие данные -
200:400:3800
Вход создается с помощью A = round(rand(N)*20)
, где N - параметр, взятый из массива размера. Таким образом, N
будет меняться от 15 to 100 with stepsize of 5
для первого набора и аналогично для второго и третьего множеств. Пожалуйста, обратите внимание, что я определяю данные как N, поэтому количество элементов будет составлять данные ^ 2 или N ^ 2.
Код бенчмаркинга
N_arr = 15:5:100; %// for very small to small sized input array
N_arr = 50:40:1000; %// for small to medium sized input array
N_arr = 200:400:3800; %// for medium to large sized input array
timeall = zeros(2,numel(N_arr));
for k1 = 1:numel(N_arr)
A = round(rand(N_arr(k1))*20);
f = @() ~A;
timeall(1,k1) = timeit(f);
clear f
f = @() A==0;
timeall(2,k1) = timeit(f);
clear f
end
Результаты
![enter image description here]()
![enter image description here]()
![enter image description here]()
Наконец, вопросы
Можно видеть, как A==0
работает лучше, чем ~A
для всех типов данных. Итак, вот некоторые замечания и связанные с ними вопросы вместе с ними -
-
A==0
имеет один реляционный оператор и один операнд, тогда как ~A
имеет только один реляционный оператор. Оба производят логические массивы и оба принимают двойные массивы. Фактически, A==0
будет работать с NaNs
тоже, если ~A
не будет. Итак, почему все еще ~A
по крайней мере не так хорошо, как A==0
, поскольку он выглядит так: A==0
делает больше работы или я что-то пропустил здесь?
-
Это своеобразное падение прошедшего времени с A==0
и, таким образом, увеличение производительности при N = 320
, т.е. в 102400
для элементов A. Я наблюдал это во многих прогонах с этим размером на двух разных системах, У меня есть доступ. Так что происходит там?
Ответы
Ответ 1
Это не строго ответ, а мой вклад в обсуждение
Я использовал profiler
для исследования слегка модифицированной версии вашего кода:
N_arr = 200:400:3800; %// for medium to large sized input array
for k1 = 1:numel(N_arr)
A = randi(1,N_arr(k1));
[~]=eq(A,0);
clear A
A = randi(1,N_arr(k1));
[~]=not(A);
clear A
end
Я использовал следующие фреймы профайлера (согласно UndocumentedMatlab ряд сообщений о profiler
):
profile('-memory','on');
profile('on','-detail','builtin');
И вот выдержка из результатов профилировщика (ссылка на увеличенное изображение):
![Profiler output]()
Кажется, что вариант ==
выделяет крошечный бит дополнительной памяти, который позволяет ему работать с магией....
Что касается вашего вопроса 2: Прежде чем удалять сохранение timeall
, я попытался построить те же диаграммы, которые вы сделали в Excel. Я не заметил поведения, которое вы упомянули для N = 320
. Я подозреваю, что это может иметь какое-то отношение к дополнительным оболочкам (т.е. Дескрипторам функций), которые вы используете в своем коде.
Я подумал, что для быстрой справки я буду прилагать доступную документацию для обсуждаемых функций.
Документация для ~
(\ MATLAB\R20???\toolbox\matlab\ops\not.m):
%~ Logical NOT.
% ~A performs a logical NOT of input array A, and returns an array
% containing elements set to either logical 1 (TRUE) or logical 0 (FALSE).
% An element of the output array is set to 1 if A contains a zero value
% element at that same array location. Otherwise, that element is set to
% 0.
%
% B = NOT(A) is called for the syntax '~A' when A is an object.
%
% ~ can also be used to ignore input arguments in a function definition,
% and output arguments in a function call. See "help punct"
% Copyright 1984-2005 The MathWorks, Inc.
Документация для ==
(\ MATLAB\R20???\toolbox\matlab\ops\eq.m):
%== Equal.
% A == B does element by element comparisons between A and B
% and returns a matrix of the same size with elements set to logical 1
% where the relation is true and elements set to logical 0 where it is
% not. A and B must have the same dimensions unless one is a
% scalar. A scalar can be compared with any size array.
%
% C = EQ(A,B) is called for the syntax 'A == B' when A or B is an
% object.
% Copyright 1984-2005 The MathWorks, Inc.
Ответ 2
Также не строго ответ, но я хочу добавить к обсуждению. Возможно, это сводится к вашей функции timeit
.
Я пробовал функцию Dev-iL. Я профилировал и получил те же результаты: EQ
кажется быстрее, чем NOT
, а EQ
выделяет немного больше памяти, чем NOT
. Казалось логичным, что если оператор EQ
выделяет больше памяти, то, увеличив размер массива, это распределение памяти также увеличится. Подозрительно, это не так!
Я продолжал и удалял все ненужное и повторял цикл для N=1000
итераций. Профилировщик по-прежнему соглашался с тем, что EQ
быстрее, чем NOT
. Но я не был уверен.
Далее я должен был удалить странно выглядящие [~] = ~A
и [~] = A == 0
для чего-то более гуманного, выглядящего как tmp1 = ~A
и tmp2 = A == 0
и voilà! Время выполнения почти равно.
![profiler results]()
Поэтому я предполагаю, что вы делаете что-то подобное внутри своей функции timeid
. Стоит отметить, что назначение [~]
замедляет обе функции, но NOT
кажется более подверженным влиянию, чем EQ
.
Теперь большой вопрос: почему оператор [~]
замедляет функции? Я не знаю. Возможно, только Mathworks может ответить на это. Вы можете открыть билет на веб-странице Mathworks.
Частичный ответ: у них почти одинаковое время выполнения, даже для больших массивов (самый большой массив, который я пытался - 10K).
неотвеченная часть: почему [~]
назначение замедляет код. Почему NOT
больше влияет на EQ
.
Мой код:
clear all
clear classes
array_sizes = [1000:1000:10000];
repetitions = 10000;
for i = 1:length(array_sizes)
A1 = randi([0, 1], array_sizes(i), 1);
for j = 1:repetitions
tmp1 = eq(A1, 0);
end
end
for i = 1:length(array_sizes)
A2 = randi([0, 1], array_sizes(i), 1);
for j = 1:repetitions
tmp2 = not(A2);
end
end