【问题标题】:efficient parallel algorithm to update traversal costs for all simple paths in a weighted, directed graph有效的并行算法来更新加权有向图中所有简单路径的遍历成本
【发布时间】:2017-12-20 22:30:10
【问题描述】:

我正在使用相对较小的有向图(约 10 个节点),每个有约 10,000 个简单路径和循环。我想维护一个总成本的排序列表,以遍历所有这些简单的路径和周期。我的边有几种不同的权重,但聚合函数对于所有这些都是可交换的/关联的(例如总和和乘积)。

现在,我正在使用 rethinkdb(一个 nosql 数据库)和 python。我正在预先计算所有可能的简单路径,将它们存储在哈希图中,并且每次更新边权重时,暴力重新计算遍历都会花费它们。我的哈希图将给定的边(其权重刚刚更新)指向它所属的所有简单路径和循环。然后我去重新计算每一个的遍历成本。

嗯,我发现这非常慢并且无法扩展!我知道这是一个难题,但希望它适用于我相对较小的图表。

我最初的方法的一个低效率似乎在于每条路径的浪费冗余计算,即使有些是彼此的聚合。例如,A→B→C→D→E 的成本是 A→B→C 和 C→D→E 的组合。那么为什么不巧妙地计算它们呢?我想出了一种方法,但它似乎没有一点帮助,这让我觉得我真的需要退后一步。

于是我上网搜索了一下,偶然发现了这篇很有帮助的文章: https://blog.blazegraph.com/?p=628 。它说:

大图反模式是“将所有内容都放入大图中,然后 然后使用相同的工具给我们水平缩放其他 问题:map/reduce 和键值存储。”

令我震惊的是,这正是我一直在做的事情(错误)。

而且似乎 GPU 是文章中提到的内存带宽问题的正确解决方案...除了我不确定如何并行解决这个问题。

问题:

  1. 如何并行处理这个问题?收集-应用-分散是正确的方向吗?以前在哪里做过?

  2. 如何在不并行的情况下有效优化当前方法?

作为参考,这里是我当前算法的草图:

  1. 枚举我的图表中的所有简单路径和循环
  2. 保留边及其权重的字典。例如,如果('A','B') 是从节点A 到节点B 的一条边,

    edges_weights[('A','B')] # -> {'x': 1.3, 'y': 32, 'z': 0.231232}
    
  3. 保留每条边所涉及的所有简单路径和循环的字典,例如:

    paths_containing_edges[('A','B')] 
    # ->
    # [
    #      (('A','B'), ('B','C')), 
    #      (('A','B'), ('B','C'), ('C','D')), 
    #      (('A','B'), ('B','C'), ('C','A')), 
    #      ... 
    #      (('A','B'), ('B','C'), ('C','D'), ('D','A'))
    # ] 
    
  4. 另外,初始化路径及其成本的字典:

    paths_costs = {
            (('A','B'), ('B','C')): {'x': ..., 'y': ..., 'z': ...}
    }
    
  5. 更新边缘时:

    我。在edges_weights 中更新其权重

    二。查找所有包含这条边的简单路径并更新它们:

    fn = lambda p,q: p+q # the cost aggregation function        
    weight_keys = ['x','y','z']
    for path in paths_containing_edges[updated_edge]:
      # path is a tuple of edge tuples, i.e. (('A','B'),('B','C'),('C','D'))
      for w in weight_keys:
        paths_costs[path][w] = reduce(fn,[edges_weights[e][w] for e in path])
    

显然有很多嵌套循环和查找正在发生......但我正在努力了解如何以不同的方式做事。

【问题讨论】:

  • 您也可以尝试使用 apache tinkerpop/gremlin。 gremlin 查询可能会给您更多的洞察力,您可以在尝试优化之前尝试不同的算法。你能提供一个tinkerpop可以读/写的格式的例子吗?

标签: parallel-processing gpgpu graph-databases graph-traversal blazegraph


【解决方案1】:

我不确定我是否正确理解了您的问题。我还是试试看:

如果您有 n 个节点,则节点之间的边数最多为 (n*n-n)/2 个。

如果 n 为 10,则意味着有 50 个可能的边。

在你得到一个循环之前,10 个节点的最大路径长度是 10。 一个简单的边缘数组应该确保您可以访问边缘的重量信息。 所以如果例如有一条路径 A->B->C->D->E->G->H->I->J

你必须总结 (A->B) (B->C) (D->E) (E->F) (E->G) (H-I) (I->J) 现在的问题是:什么更快 - 查找此子项的解决方案的查找成本还是简单地添加数组指针并保留这些指针?保持 10.000 个指针并对数字求和应该非常快。特别是如果您将权重保持为 CPU 可以很好处理的数字。我假设它们是 int、long 而不是 float 或 double,即使使用 64 位 cpu 这也应该没问题。

鉴于您的数字很小,我会尝试使用适当的编程语言(如 C / Assembler)简单地创建最有效的计算,这将降低计算的 CPU 周期。我的直觉是,这将比找到一个有效的算法更快(但仅适用于少量的 n - 我想知道切换点在哪里......)

【讨论】:

  • 感谢您的回复。转向编译语言感觉是非常好的建议。至少,它可以让我了解内存中实际发生的情况。也许这是学习 Rust 的好机会。我最近在 2018 年看到了一段 python 3 在 486 上运行的视频:youtu.be/4qSziR6sD8Q?t=1219 启动 python 并打印“Hello, world”需要 13 秒!魔法是有代价的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 1970-01-01
  • 2016-07-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多