【问题标题】:Caching Matlab function results to file将 Matlab 函数结果缓存到文件
【发布时间】:2012-03-06 05:57:51
【问题描述】:

我正在用 Matlab 编写一个模拟程序。 我最终会运行这个模拟数百次。 在每次模拟运行中,都有数百万个模拟周期。 在每个循环中,我都会计算一个非常复杂的函数,它需要 ~0.5 秒才能完成。 函数输入是一个长位数组(>1000 位)——它是一个01 的数组。 我将位数组保存在01 的矩阵中,并且对于它们中的每一个我只运行一次函数 - 因为我将结果保存在不同的数组(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. 由于我多次运行模拟,并且我得到的位数组中有很大的重叠,我想缓存运行之间的矩阵,这样我就不会重新计算相同的函数位数组一遍又一遍。我怎么做?

【问题讨论】:

  • 你如何将 bit_array 和 bit_matrix 存储在内存中?作为logical?还是打包成其他数据类型? (每个logical 值实际上存储在一个字节中,而不是一位,即使它只能采用两个可能的值。)
  • bit_array 总共有多少不同的值?重叠是否足以让您将所有它们及其结果存储在内存中?或者它们中的一些是否需要卸载到磁盘?在单个模拟中是否存在跨模拟周期的重叠,或者只是跨不同的模拟? bit_arrays 在模拟中的执行顺序是否存在局部性(例如,通常只有一些低位发生变化)?
  • 我没有声明任何东西,所以我想我使用的是常规 int32?
  • 有 >1000 位,模拟大概可以达到所有状态,所以我不能使用内存或磁盘来保存所有结果。尽管如此,大多数状态都不会到达,有些状态会多次到达,所以我想到了使用LRU 映射——至少如果我使用 Java 或 Python 时我会这样做。此外,相同的状态可能不仅在同一次模拟运行中重复,而且在多次模拟中重复,所以我确实想将缓存保存到磁盘(或数据库??)。

标签: matlab caching simulation bitstring


【解决方案1】:

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

  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');

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


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

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

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

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

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

【讨论】:

  • 好一个!唯一的问题是bit_array到string的转换是否不会太慢,从而使一切变得低效。
  • 谢谢,回复你。当他提到单次运行 0.5 秒时,我认为字符串转换时间可能不会受到速率限制因素。
  • 好的,但是我确实有很多不同的值,所以我必须使用LRU 映射,对吗?此外,由于我希望缓存在多个并行模拟运行中保持不变,因此我需要将其保存在文件或数据库中,不是吗?
  • 这需要考虑很多。请参阅对原始答案的编辑。
【解决方案2】:

我认为您应该使用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

【讨论】:

    【解决方案3】:
    1. 对于大型列表,手动编码的二进制搜索可以击败 ismember,如果以排序顺序维护它并不太昂贵的话。如果这真的是你的瓶颈。使用分析器查看 ismember 实际花费了您多少。如果没有太多不同的值,您还可以将它们存储在容器中。通过将 bit_matrix 打包到 char 数组中并将其用作键来映射。

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

    【讨论】:

      猜你喜欢
      • 2012-06-26
      • 2016-05-23
      • 2010-11-13
      • 2020-01-22
      • 1970-01-01
      • 2011-01-14
      • 2019-09-05
      • 1970-01-01
      相关资源
      最近更新 更多