基准测试
从Dev-iL's benchmark 开始,我添加了OP 的find 方法和rahnema1's ismember method,并查看这些方法如何随数据大小(键数)缩放。我还比较了两种不同的用例:一次查找 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 也很容易成为最快的方法。
同时获取一个key时,sparse方法最快,除非数据量非常小,在这种情况下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