Benchmark
Начиная с бенчмарка Dev-iL , я добавил метод OP find
и метод rahnema1 ismember
и посмотрим, как методы масштабируются с размером данных (число ключи). Я также сравниваю два разных варианта использования: нахождение 5000 ключей одновременно и нахождение 5000 ключей по одному за раз.
Вот результаты:
One key at the time Many keys at once
------------------------------------- -------------------------------------
size sparse c.Map find ismember sparse c.Map find ismember
---- ------- ------- ------- ------- ------- ------- ------- -------
50 5.1681 54.3091 3.7766 28.8590 0.0956 1.2973 0.5578 0.0537
500 5.0864 54.7872 6.9310 32.5554 0.0977 1.6847 3.6726 0.0499
5000 5.2052 56.4472 35.1449 60.6480 0.1140 2.0886 38.7444 0.0789
[ Время на MATLAB R2017a, на 3-летнем iMac. Ваш пробег будет варьироваться. ]
Вопреки моим ожиданиям, containers.Map
имеет много накладных расходов и не очень подходит для этой цели . Даже с массивом из 5000 ключей метод O (n) find
на самом деле быстрее, чем хэш-карта O (log n). containers.Map
- это пользовательский класс, и MATLAB JIT все еще не так хорош в оптимизации этого типа кода. Тем не менее, отчетливо виден эффект масштабирования, поскольку метод find
является единственным, время выполнения которого значительно увеличивается с увеличением размеров данных.
Интересно, что метод "разреженный" в ~ 50 раз быстрее при векторизации. Обычно это не так с векторизацией. Например, метод find
только в 1x-2x быстрее при векторизации (для больших размеров данных векторизация требует слишком много памяти и в конечном итоге окажется очень медленной).
Наибольшая разница здесь между векторизованным и циклическим кодом заключается в функции ismember
. Это сортирует входные данные, и здесь мы видим разницу между выполнением этого один раз и выполнением 5000 раз. Этот метод действительно подходит только при вызове несколько раз. Но в этом случае ismember
также является самым быстрым методом .
При извлечении по одному ключу за раз, самый разреженный метод самый быстрый , если только размер данных не очень мал; в этом случае метод find
выигрывает . Тем не менее, метод разреженный является единственным, который требует, чтобы ключи были положительными целыми числами (он не будет работать с 0, отрицательными значениями или нецелыми значениями). Все остальные методы работают со значениями произвольных типов (включая строки).
Код эталона:
function t = so(N)
% Define the mapping(s):
keys = (1:N).*5; % Keys must be positive integers!
values = 1:N;
% Sparse lookup table
sparseMap = sparse(ones(numel(keys),1), keys, values);
% containers.Map lookup table
hashMap = containers.Map(keys, values);
% Try this out:
queryKeys = keys(randi(numel(keys),5000,1));
queryKeysCell = num2cell(queryKeys); % trick to read many values from the hashMap at once
t = [timeit(@f1,1), timeit(@f2,1), timeit(@f3,1), timeit(@f4,1), ...
timeit(@f1q,1), timeit(@f2q,1), timeit(@f3q,1), timeit(@f4q,1)] * 1000;
% Functions that do the lookup one at the time:
function queryVals = f1
queryVals = zeros(size(queryKeys));
for ii=1:numel(queryKeys)
queryVals(ii) = full(sparseMap(queryKeys(ii)));
end
end
function queryVals = f2
queryVals = zeros(size(queryKeys));
for ii=1:numel(queryKeys)
queryVals(ii) = hashMap(queryKeys(ii));
end
end
function queryVals = f3
queryVals = zeros(size(queryKeys));
for ii=1:numel(queryKeys)
queryVals(ii) = find(keys==queryKeys(ii));
end
end
function queryVals = f4
queryVals = zeros(size(queryKeys));
for ii=1:numel(queryKeys)
[~, queryVals(ii)] = ismember(queryKeys(ii), keys);
end
end
% Functions that do the lookup all at once:
function queryVals = f1q
queryVals = reshape(full(sparseMap(queryKeys)), size(queryKeys));
end
function queryVals = f2q
queryVals = hashMap.values(queryKeysCell);
end
function queryVals = f3q
[queryVals,~] = find(keys.'==queryKeys);
end
function queryVals = f4q
[~,queryVals] = ismember(queryKeys, keys);
end
end