Parallel processing 更新加权有向图中所有简单路径遍历代价的高效并行算法

Parallel processing 更新加权有向图中所有简单路径遍历代价的高效并行算法,parallel-processing,gpgpu,graph-databases,graph-traversal,blazegraph,Parallel Processing,Gpgpu,Graph Databases,Graph Traversal,Blazegraph,我正在处理相对较小的有向图(~10个节点),每个有~10000个简单路径和循环。我想维护一个总成本的排序列表,以遍历所有这些简单的路径和周期。我的边有几个不同的权重,但聚合函数对所有边都是交换/关联的(例如,和和和积) 现在,我正在使用RejectionDB(nosql数据库)和python。我正在预先计算所有可能的简单路径,将它们存储在哈希映射中,每次更新边权重时,只需使用蛮力重新计算遍历开销。我的哈希映射将给定的边(其权重刚刚更新)指向它所属的所有简单路径和循环。然后我去重新计算每一个的遍历

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

现在,我正在使用RejectionDB(nosql数据库)和python。我正在预先计算所有可能的简单路径,将它们存储在哈希映射中,每次更新边权重时,只需使用蛮力重新计算遍历开销。我的哈希映射将给定的边(其权重刚刚更新)指向它所属的所有简单路径和循环。然后我去重新计算每一个的遍历成本

嗯,我发现这是非常缓慢,并没有规模!我知道这是一个困难的问题,但我希望这对于我相对较小的图是可行的

我最初的方法中的一个低效之处似乎是对每条路径进行了浪费性的冗余计算,尽管有些路径是彼此的集合。例如,一个项目的成本→B→C→D→E是a的组成部分→B→C和C→D→那么为什么不聪明地计算它们呢?我想出了一个办法,但似乎一点帮助都没有,这让我觉得我真的需要后退一步

于是我上网搜索,偶然发现了这篇非常有用的文章: . 它说:

大图反模式是“把所有东西都放到大图中,然后 然后使用相同的工具,让我们水平缩放其他 问题:映射/减少和键值存储。”

我突然意识到这正是我一直在做的(错误的)事情

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

问题:

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

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

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

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

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

    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'))
    # ] 
    
  • 另外,初始化路径及其成本的字典:

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

    一,。在
    edges\u 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])
    

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

    我不确定我是否正确理解你的问题。不过,我还是要试一试:

    如果有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) 现在的问题是:什么是更快的-查找此子集合的解决方案的查找成本,还是简单地添加数组指针并保留这些指针?保持10000个指针并对数字求和应该是非常快的事情。特别是如果你把你的重量保持为CPU可以很好处理的数字。我假设它们是int,long,而不是float或double,即使使用64位cpu,这也应该没有问题


    考虑到您的小数字,我将尝试使用适当的编程语言(如C/Assembler)创建一个最有效的计算,这将降低计算的CPU周期。我的直觉是,这比找到一个有效的算法要快(但只适用于少量的n-我想知道转换点在哪里…

    谢谢你的回答。使用编译语言感觉真的是一个很好的建议。至少,这会让我了解记忆中到底发生了什么。也许这是一个学习生锈的好机会。我最近看到了2018年在486上运行python 3的视频:启动python并打印“Hello,world”需要13秒!这种魔力是有代价的。你也可以尝试使用ApacheTinkerpop/gremlin进行实验。一个小精灵查询可能会给你更多的洞察力,你可以在尝试优化之前尝试不同的算法。你能提供一个tinkerpop可以读/写的格式的例子吗?