3

我正在用 Matlab 写一个模拟。我最终会运行这个模拟数百次。在每次模拟运行中,都有数百万个模拟周期。在每个循环中,我都会计算一个非常复杂的函数,需要~0.5sec 才能完成。0函数输入是一个长位数组(>1000 位) - 它是一个和的数组10我将位数组保存在and的矩阵中1,并且对于其中的每一个我只运行一次函数 - 因为我将结果保存在不同的数组 (res) 中并在运行函数之前检查位数组是否在矩阵中:

for i=1:1000000000
    %pick a bit array somehow
    [~,indx] = ismember(bit_array,bit_matrix,'rows');
    if indx == 0
        indx = length(results) + 1;
        bit_matrix(indx,:) = bit_array;
        res(indx) = complex_function(bit_array);
    end
    result = res(indx)
    %do something with result
end

我有两个问题,真的:

  1. 有没有比“ismember”更有效的方法来查找矩阵中行的索引?

  2. 由于我多次运行模拟,并且我得到的位数组中有很大的重叠,我想在运行之间缓存矩阵,这样我就不会一遍又一遍地重新计算相同的位数组上的函数再次。我怎么做?

4

3 回答 3

5

这两个问题的答案都是使用地图。有几个步骤可以做到这一点。

  1. 首先,您需要一个函数将您的 bit_array 转换为数字或字符串。例如,[0 1 1 0 1 0]变成'011010'. (Matlab 只支持标量或字符串键,这就是为什么需要这一步。)

  2. 定义了一个地图对象

    cachedRunMap = containers.Map;  %See edit below for more on this
    
  3. 要检查是否已运行特定案例,请使用iskey.

    cachedRunMap.isKey('011010');
    
  4. 要添加运行结果,请使用附加语法

    cachedRunMap('011010') = [0 1 1 0 1];  %Or whatever your result is.  
    
  5. 要检索缓存的结果,请使用getting 语法

    tmpResult = cachedRunMap.values({'011010'});
    

这应该有效地存储和检索值,直到您用完系统内存。


把这些放在一起,现在你的代码看起来像这样:

%Hacky magic function to convert an array into a string of '0' and '1'
strFromBits = @(x) char((x(:)'~=0)+48); %'

%Initialize the map
cachedRunMap = containers.Map;

%Loop, computing and storing results as needed
for i=1:1000000000
    %pick a bit array somehow
    strKey = strFromBits(bit_array);
    if cachedRunMap.isKey(strKey)
        result = cachedRunMap(strKey);
    else
        result = complex_function(bit_array);
        cachedRunMap(strKey) = reult;
    end
    %do something with result
end

如果您想要一个不是字符串的键,则需要在步骤 2 中声明。一些示例是:

cachedRunMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
cachedRunMap = containers.Map('KeyType', 'double', 'ValueType', 'any');
cachedRunMap = containers.Map('KeyType', 'uint64', 'ValueType', 'any');
cachedRunMap = containers.Map('KeyType', 'uint64', 'ValueType', 'double');

设置 a KeyTypeof'char'将映射设置为使用字符串作为键。所有其他类型必须是标量。


关于扩大规模时的问题(根据您最近的评论)

  • 在会话之间保存数据:将此映射保存到 *.mat 文件应该没有问题,直到系统内存的限制

  • 清除旧数据:我不知道将 LRU 功能添加到此地图的直接方法。如果你能找到一个 Java 实现,你可以很容易地在 Matlab 中使用它。否则,将需要一些思考来确定跟踪上次使用密钥的最有效方法。

  • 在并发会话之间共享数据:正如您所指出的,这可能需要数据库高效执行。DB 表将是两列(如果要实现 LRU 功能,则为 3),键、值和(如果需要,最后使用时间)。如果您的“结果”不是很容易适合 SQL 的类型(例如,大小不一的数组或复杂的结构),那么您需要额外考虑如何存储它。您还需要一种访问数据库的方法(例如数据库工具箱,或 Mathworks 文件交换中的各种工具)。最后,您将需要在服务器上实际设置一个数据库(例如,MySql,如果您像我一样便宜,或者您有最丰富的经验,或者可以找到最多的帮助。)这实际上并不难,但它第一次通过需要一些时间和精力。

    另一种需要考虑的方法(效率低得多,但不需要数据库)是将数据存储分解为大量(例如,1000 或数百万)数量的地图。将每个文件保存到单独的 *.mat 文件中,文件名基于该映射中包含的键(例如字符串键的前 N ​​个字符),然后根据需要在会话之间加载/保存这些文件。这会很慢......根据您的使用情况,每次从源函数重新计算可能会更快......但这是我能想到的最好的方法,而无需设置数据库(显然是一个更好的答案)。

于 2012-02-14T23:04:20.847 回答
0
  1. 对于大型列表,手动编码的二进制搜索可以击败 ismember,如果以排序顺序维护它并不太昂贵。如果这真的是你的瓶颈。使用分析器查看 ismember 实际花费了您多少。如果没有太多不同的值,您还可以通过将 bit_matrix 打包到char数组中并将其用作键来将它们存储在 container.Map 中。

  2. 如果它足够小以适合内存,则可以使用saveand将其存储在 MAT 文件中load。它们可以存储任何基本的 Matlab 数据类型。让模拟save累积resbit_matrix在其运行结束时,并load在下次调用时重新调用它们。

于 2012-02-14T23:01:10.773 回答
0

我认为你应该使用containers.Map()加速的目的。

一般的想法是保存一个包含所有哈希值的映射。如果您的位数组在哈希函数下具有均匀分布,则大多数情况下您不需要调用ismember.

由于key type不能是 Matlab 中的数组,因此您可以在位数组上计算一些哈希函数。

例如:

 function s = GetHash(bitArray)
      s = mod( sum(bitArray), intmax('uint32'));          
 end

这是一个糟糕的哈希函数,但足以理解其原理。然后代码将如下所示:

map = containers.Map('KeyType','uint32','ValueType','any');
for i=1:1000000000
    %pick a bit array somehow
    s = GetHash(bit_array);   
    if isKey  %Do the slow check.
        [~,indx] = ismember(bit_array,bit_matrix,'rows');
    else
       map(s) = 1;
       continue;
    end
    if indx == 0
        indx = length(results) + 1;
        bit_matrix(indx,:) = bit_array;
        res(indx) = complex_function(bit_array);
    end
    result = res(indx)
    %do something with result
end
于 2012-02-14T23:02:11.377 回答