将Matlab函数结果缓存到文件中

3
我正在使用Matlab编写模拟程序。 最终我将运行这个模拟数百次。 在每次模拟运行中,有数百万个模拟周期。 在这些周期中,我计算一个非常复杂的函数,需要大约0.5秒才能完成。 该函数的输入是一个长的位数组(>1000位),其中包含了0和1。 我将位数组存储在一个由0和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. 因为我要多次运行模拟,且得到的位数组之间存在很大的重叠,所以我想在运行之间缓存矩阵,这样我就不必一遍又一遍地对相同的位数组重新计算函数。我该怎么做?


你是如何在内存中存储bit_array和bit_matrix的?使用“logical”吗?还是打包到其他数据类型中?(每个“logical”值实际上存储在一个字节中,而不是一个位,尽管它只能有两个可能的值。) - Andrew Janke
bit_array 会有多少个不同的总值?重叠部分是否足够大,可以将所有值及其结果存储在内存中?还是其中一些需要转移到磁盘上?单个模拟周期内是否存在重叠,还是仅在不同模拟之间存在?在模拟中执行 bit_arrays 的顺序是否具有局部性(例如,通常只更改某些低位)? - Andrew Janke
我没有声明任何东西,所以我猜我正在使用常规的 int32? - yoavram
有超过1000个位,模拟可以达到所有状态,因此我不能使用内存或磁盘来保存所有结果。尽管如此,大多数状态将不会被触及,而某些状态将被多次触及,因此我考虑使用LRU映射 - 至少如果我使用Java或Python的话,我会这样做。此外,相同的状态很可能不仅在同一模拟运行中重复出现,而且在多个模拟中也会重复出现,因此我确实希望将缓存保存到磁盘(或数据库?)。 - yoavram
3个回答

5

回答这两个问题的方法是使用地图。这需要几个步骤。

  1. First you will need a function to turn your bit_array into either a number or a string. For example, turn [0 1 1 0 1 0] into '011010'. (Matlab only supports scalar or string keys, which is why this step is required.)

  2. Defined a map object

    cachedRunMap = containers.Map;  %See edit below for more on this
    
  3. To check if a particular case has been run, use iskey.

    cachedRunMap.isKey('011010');
    
  4. To add the results of a run use the appending syntax

    cachedRunMap('011010') = [0 1 1 0 1];  %Or whatever your result is.  
    
  5. To retrieve cached results, use the getting syntax

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

KeyType设置为'char'将使得Map使用字符串作为键,而所有其他类型必须是标量。


关于扩展的问题(根据您最近的评论)

  • 在会话之间保存数据:将此Map保存到*.mat文件中应该没有问题,但也受系统内存限制。

  • 清除旧数据:我不知道如何直接添加LRU功能。如果您可以找到Java实现,那么在Matlab中使用它相当容易。否则,需要考虑最有效地跟踪上次使用key的时间的方法。

  • 在并发会话之间共享数据:正如您所指出的,这可能需要一个数据库来实现高效性。数据库表将有两列(如果您想要实现LRU功能,则为3列),即键、值(和最后使用时间(如果需要)。如果您的“结果”不易适合SQL(例如非统一大小数组或复杂结构),则需要进一步考虑如何存储它。您还需要一种访问数据库的方法(例如,数据库工具箱或Mathworks文件交换中的各种工具)。最后,您需要在服务器上实际设置数据库(例如,如果您像我一样比较节省,您可以使用MySql,或者您有最丰富经验或者能够找到最多帮助的工具)。这实际上并不难,但第一次需要花费一些时间和精力。

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


不错!唯一的问题是将 bit_array 转换为字符串是否会太慢,从而使一切变得低效。 - Andrey Rubshtein
谢谢,你也一样。当他提到单次运行需要0.5秒时,我决定字符串转换时间可能不是速率限制因素。 - Pursuit
好的,但是我有很多不同的值,所以我必须使用LRU映射,对吧?另外,由于我希望缓存在多个并行模拟运行中保持持久性,所以我需要将其放在文件或数据库中,对吧? - yoavram
这需要考虑很多。请查看对原回答的编辑。 - Pursuit

0

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

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

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

例如:

 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

0
对于一个大列表来说,如果维护它的排序不太昂贵,手动编写的二分搜索可以击败ismember函数。如果这真的是你的瓶颈所在,请使用性能分析器查看ismember函数实际消耗了多少时间。如果不同的值并不太多,你还可以将它们存储在containers.Map中,通过将bit_matrix打包成char数组并将其用作键。
如果数据量小到可以放入内存中,你可以使用save和load将其存储为MAT文件。它们可以存储任何基本的Matlab数据类型。让模拟程序在运行结束时使用save函数保存累积的res和bit_matrix,并在下次调用时重新使用load函数加载它们。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接