【问题标题】:Using maps inside of a parfor loop in Matlab在 Matlab 中使用 parfor 循环内的映射
【发布时间】:2023-03-22 15:37:02
【问题描述】:

我目前有执行以下操作的 Matlab 代码:

map = collections.Map;
for i = 1:N
    key = getKey(i);
    if isKey(map, key)
        % Return the value stored at key.
    else
        % Calculate a new value, store it in the map using key.
    end
end

这个循环需要很长时间才能运行,我想使用 parfor 来帮助提高效率。但是,我似乎无法为 parfor 循环内的映射分配值。关于我能做什么的任何想法? collections.Map 的使用并不固定,我愿意接受并行记忆的替代建议,只要它们快速高效(并且线程安全,我意识到 Map 可能不是)。

从下面的评论中添加:我希望有更多的线程安全方式在循环期间向映射添加新值,以便任何后续循环都可以使用预先计算的值。计算在时间上非常昂贵。

【问题讨论】:

  • 您的密钥是什么类型的数据?
  • 我正在使用字符串。更具体地说,我在键数组上使用 mat2str 的结果。

标签: matlab parallel-processing memoization


【解决方案1】:

“线程安全”在这里是错误的关注点。一般来说,“线程安全”不适用于 Matlab 对象,因为 Matlab 在 M 代码级别是单线程的。每个 Matlab 实例/进程只有一个 Matlab 解释器线程。并行化parfor 迭代发生在单独的进程中,甚至发生在单独的机器上,而不是单独的线程上,因此这更像是一个进程间通信问题而不是多线程。并且您不能通过分配在 parfor 工作循环传递的状态与彼此或封闭工作区之间进行双向“通信”;我很确定数据传输只发生在循环开始和循环结束时。

如果你真的想在parfor 中进行记忆,你需要某种客户端-服务器记忆机制。您需要做的是在所有 Matlab 池工作人员都可以看到但在 M 代码中涉及的 Matlab 工作区外部的服务器上的进程中设置您的记忆缓存,并在循环内使用客户端代理对象通过某种进程间通信(如 RMI 或套接字调用)来查询缓存。主“服务器”缓存对象甚至可以在使用parfor 循环运行代码的主 Matlab 进程内,只要它是不受 M 代码解释器管理的 Java 或 C 结构。缓存的服务器端将处理安全的并发访问(可能使用线程安全映射和多个工作线程,可能通过序列化请求)。客户端可以有一个本地状态代理,只要它不基于 M 代码索引来更新其元素。

更一般地说,如果您希望在 parfor 传递中的工作人员之间进行交互,您需要一个共享数据存储,例如他们都可以访问的数据库或文件系统。

也许你可以设置一个小的memcached 实例,或者一个轻量级的内存数据库(也许是 JDK 附带的 Derby?),并将其用作服务器端缓存,在所有数据库中创建 db 句柄工人访问它。或者也许有一组更轻量级的 Java 对象,它为ConcurrentMapsynchronized Map 提供了一个精简的 RMI 接口?您还可以使用像 containers.Map 这样的 Matlab 对象创建一个缓存服务器,方法是让一个单独的 Matlab 进程充当缓存服务器,在套接字上侦听请求并序列化它们,并让 parfor 工作人员通过客户端代理对象访问它有 get()set() 方法使用缓存服务器进程上的 IPC 调用来完成它们的工作。

这一切都可能是一个很大的开销,所以只有在计算真的很昂贵时才值得。

【讨论】:

    【解决方案2】:

    显然你指的是containers.map。我会使用临时数组/单元格数组来存储parfor 的乘积,并在循环之后分配所有新内容。

    % original map
    keySet = {'a','b','c','f'};
    map = containers.Map(keySet, 1:length(keySet));
    
    % simulates your keys/generated values 
    getKey = {'f','g','h'};
    getVal = (5:7);
    
    % Parfor body
    parfor i = 1:3
        key = getKey{i};
        if isKey(map, key)
            % Return the value stored at key.
            map(key);
        else
            % Calculate a new value, store it in a temp array/cell-array 
            keyNew{i} = key;
            valNew(i) = getVal(i);
        end
    end
    
    % assign to map the new pairs of keys/values
    indNew = ~cellfun(@isempty, keyNew); % clean up empty cells
    newMap = containers.Map(keyNew(indNew), valNew(indNew));
    map = [map; newMap];
    

    编辑:您不能使用parfor 并动态地使值可用于后续迭代,因为parfor 循环中没有subsequent iterations 这样的东西。

    来自parfor 文档:

    注意:由于迭代顺序的独立性,parfor 的执行不会 不保证确定性结果。

    这意味着(正如这个SO 问题/答案很好地强调的那样),并行执行的迭代必须被认为是独立的,而不是常规的 for 循环。结果,它们的执行/完成顺序是不可预测的,并且它们对其他迭代输出的访问/依赖是不可行的。

    【讨论】:

    • 我希望有更多的线程安全方式在循环期间向映射添加新值,以便任何后续循环都可以使用预先计算的值。计算时间非常昂贵。
    • 我不认为你可以用parfor 做到这一点,因为迭代是/必须被认为是独立的(因此它们的执行顺序、访问和依赖于其他迭代的输出)。另请参阅此处的相关答案stackoverflow.com/questions/13769499/…
    • 实际上,请仔细阅读其他答案;我认为它意外地展示了 OP 想要的那种状态共享副作用。它正在调用randn,它推进了全局rng 状态,因此每个循环传递都直接依赖于先前循环传递的存储值。这是一种实现 OP 正在寻找的缓存逻辑的本地版本。您可以将其用作指南 - 如果您将缓存逻辑填充到函数中,则可以绕过一些语法限制。 (具体来说,我认为rng 的状态隐藏在Java 对象中。)
    猜你喜欢
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 1970-01-01
    • 2017-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多