为多个线程排序资源访问计划,以便最大限度地减少写入冲突的数量

时间:2013-06-19 11:00:48

标签: algorithm sorting parallel-processing

情境:

给定一组资源R: set of resources

给定一组线程T,它将并行运行: set of threads

每个线程都需要访问n个资源的列表。每个列表都是R的样本,这意味着每个资源在每个列表中都是唯一的: threads access random samples of resources

但由于访问列表是随机抽样的,因此可能存在冲突: conflicting access

随机资源列表将在开头初始化一次。之后,每个线程随后将对列表中的每个资源执行atomicAdd操作。每个列表中资源的访问顺序无关紧要。

问题:

是否存在对调度列表进行排序的算法,以便最大限度地减少写入冲突的数量?所以最终结果如下: resolved conflicts

到目前为止我的见解:

  • 随机抽样对于算法的上下文很重要,因此不能以另一种方式初始化列表(只能改变它们的顺序)。
  • 可以将整体时间表视为具有| T |的矩阵S. rows和n列,其中每个条目都是R的一个元素。
  • 如果| T | < = | R |,可以解决没有任何冲突的解决方案。
  • 如果| T | == | R |,优化调度矩阵S的列是R的排列。
  • 如果| T | > | R |,优化调度矩阵中的并发访问的平均数应为| T | / | R |

可能的方法:

我正在为这个问题寻找解决方案。它可能是完整的吗?如果是这种情况,我正在考虑设计一种遗传算法来解决这个问题。

修改1 :添加了图表。

4 个答案:

答案 0 :(得分:4)

我认为问题是要求"我们可以对列表进行排序以减少冲突。"

我认为最佳解决方案是NP完成,但我会查找每个资源的集合中出现的次数。

第1步

最常用的资源是最难安排的资源。因此,我会在每个线程中将此资源放置在位置1,2,3,4 ......中,直到发生冲突(这可能是不可避免的),例如1,2,3,4,...,n,1,2,....

这些是"大石头"。这些应该放在第一位。

第2步

然后应尝试下一个最常用的资源。这应该识别最近使用了哪些时隙(1 => n),并在该列表中搜索未分配且最近未使用的切片。

无论选择哪个插槽,它都会移动到最近使用的队列的顶部,以避免一段时间。

这有利于分发资源,但允许重复。这些重复项最近将被使用,并且在没有有效选择可用之前不会再次受到调度。

最后

对每个资源按其出现的顺序重复步骤2。

答案 1 :(得分:4)

正式化问题

起初,它看起来像OSSP的变体。我们需要在R个处理器上安排T个资源。有些计划时间为0,有些则为1

但是,我们需要在n个时间步长内完成整个序列,并且确实有n*T个非零调度时间。

因此,我们正在寻找n时间内的调度,没有T-T冲突(因为没有线程可以同时对两个资源进行操作),并且{{1}的最小数量冲突我假设要最小化的目标函数是:

enter image description here

其中R-R是在count时使用资源j的多个线程。

构建问题图

让我们为每个线程(第一部分)和每个资源(第二部分)构建一个带有顶点的图i。对于每个非零调度时间,我们都有从线程到资源的优势。这个图显然是二分的。

每个线程顶点的度数为G=(V,E)

我们的目标是使用n颜色对此图表进行边缘着色,其方式如下:

  • 没有线程顶点具有两个相同颜色的相邻边

  • 具有相同颜色的相邻边数最小

无冲突解决方案

如果没有度n的资源顶点,则图形具有最多d > n种颜色的正确边缘着色。正确的着色当然是目标函数的最佳着色 - 根本没有冲突。

双向图边缘着色可以在O(n * T * R) time中完成。

有冲突的解决方案

现在,假设我们有一个度n的资源顶点。这意味着d > n颜色没有正确的边缘着色,我们的日程安排会有冲突。

限制冲突次数。

我们有一些n的顶点V_conflict。然后,冲突的数量正好是d > nsum max(0, d-n)

它可能会更少,因为边缘着色中的每个冲突颜色都是我们的计划中的冲突,并且对于具有q度的每个顶点,我们至少有d > n个冲突的颜色。

现在,我们想构建一个具有d - n个冲突的解决方案。从q中的每个顶点移除任何一组边,将其度数降低到V_conflict。我们已经删除了n个边缘。现在,我们有一个无冲突的解决方案(作为q颜色的正确图形边缘着色)。

现在插入先前删除的n边,指定尚未分配给相应线程顶点任何边的颜色。由于每个添加的边缘只能引入1个冲突,我们现在确切地具有q,这被证明是下限。

冲突的整个步骤可以在:

完成
  • q用于确定O(R)

  • V_conflict用于删除冲突的边缘

  • O(R*T)用于解决减少无冲突的版本。

  • O(n * T * R)用于将边缘添加回图表

因此可以在O(n * q)时间内完成解决方案。

答案 2 :(得分:3)

方法

  1. 从每个线程的资源列表中,我们创建一个名为CountedResourceList的合并列表,按照计数的递减顺序存储<resource-id, total occurrence count of that id>。这将花费O(rt * log(rt))时间。 为什么?在下面的示例运行中进行了解释。同样在同一次迭代中,我们创建了一个HashMap,这样我们就可以在O(1)时间内找到这个资源id放在哪个线程的列表中(基于Inverted Index的想法)对于那些可以联系的人。)
  2. 接下来,我们创建一个矩阵 - 名为SortedListMatrix - t行和r列。在这个矩阵中,我们首先将CountedResourceList中的资源ID放入该线程的行中,该行最初包含该最大发生的资源ID。通过这种方式,资源 - ids以大多数发生的顺序排放到最不发生的
  3. 为了帮助插入资源ID,我们还创建了一个int[t] FreeColumnCount来保存每个线程的总空闲列数。由于具有较小空闲槽的线程在冲突的情况下具有较少的移动资源id的能力,因此资源id将首先填充在包含该资源id的线程具有最少空闲槽的行中。
  4. 从包含最高资源ID的第一个线程的第0列开始,我们使用公式row = GetRowForResource(id) in the HashMap, and column = (column+1)%r选择要放置的下一个ID的位置。如果行中的结果列被占用,我们通过使用相同的列值公式迭代来获取下一个未占用的列值,而不更改行。因此 - 列需要环绕并且从HashMap确定行,以便资源ID进入正确的列表和最不相互冲突的位置
  5. 完全填充此矩阵后,从第0行开始,将行分配给每个线程作为新的冲突最小的资源列表
  6. Attn:我间歇性地提到了几个步骤的复杂性,但很快就会更新整体运行时复杂性。目前我正在努力想出一个正确的算法。

    样本运行

    初步假设和定义:

    • 线程数= t ,从线程0开始到t-1
    • 每个主题的资源数= r
    • 为线程准备资源列表的总资源将是&gt; = r

    让我们从t = 4的情况开始.T0 = {4,3,5},T1 = {1,2,6},T2 = {3,1,2},T4 = { 2,7,1}。现在,我知道这个列表已经没有任何冲突了。应该有一个预处理步骤,以确定是否应该首先进行一些重新安排。就像列表已经存在可能的最小冲突或者如果没有冲突一样,列表应该按原样返回。不过,让我们看看伪代码在行动。

    首先,我们读取所有列表中的所有资源,并将它们放在资源ID的各个桶中。利用Counting Sort,这将是采取 O(tr +常数)时间的线性步骤。但是,由于这只会计算资源ID,我们需要进一步使用Merge Sort按照减少发生的顺序对id进行排序。此步骤将采用 O((rt)log(rt))。结果将是按发生的降序排序的列表或ID -

      

    CountedResourceList =&lt; 3次,id 1&gt;,&lt; 3次,id 2&gt;,&lt; 2次,id 3&gt;,&lt; 1次每个id 4,5,6,7&gt;

    在同一次迭代中,我们还创建了HashMap和FreeColumnCount数组。

    接下来,我们创建从{em> row = first thread开始填充的SortedListMatrix,以包含最多出现的资源ID (通过调用GetRowForResource(id)) column = 0 。矩阵最初为空,然后填充如下:

    Initially:
    {_, _, _}
    {_, _, _}
    {_, _, _}
    {_, _, _}
    
    Taking 1st element '1' at (1, 0):
    {_, _, _}
    {1, _, _}
    {_, _, _}
    {_, _, _}  
    
    Taking 2nd element '1' at (2, 1):
    {_, _, _}
    {1, _, _}
    {_, 1, _}
    {_, _, _} 
    
    Taking 3rd element '1' at (3, 2):
    {_, _, _}
    {1, _, _}
    {_, 1, _}
    {_, _, 1}
    
    Taking 4th element '2' at (1, 1) as (1, 0) is already occupied:
    {_, _, _}
    {1, 2, _}
    {_, 1, _}
    {_, _, 1}
    
    Taking 5th element '2' at (2, 2):
    {_, _, _}
    {1, 2, _}
    {_, 1, 2}
    {_, _, 1}
    
    Taking 6th element '2' at (3, 0):
    {_, _, _}
    {1, 2, _}
    {_, 1, 2}
    {2, _, 1}
    
    Taking 7th element '3' at (0, 0) because row 2 has least free slots:
    {_, _, _}
    {1, 2, _}
    {3, 1, 2}
    {2, _, 1} 
    
    Taking 8th element '3' at (0, 1):
    {_, 3, _}
    {1, 2, _}
    {3, 1, 2}
    {2, _, 1} 
    
    Taking 9th element '4' at (0, 2):
    {_, 3, 4}
    {1, 2, _}
    {3, 1, 2}
    {2, _, 1}
    
    Taking 10th element '5' at (0, 0):
    {5, 3, 4}
    {1, 2, _}
    {3, 1, 2}
    {2, _, 1}
    
    Taking 11th element '6' at (1, 2):
    {5, 3, 4}
    {1, 2, 6}
    {3, 1, 2}
    {2, _, 1}
    
    Taking 12th element '7' at (3, 1):
    {5, 3, 4}
    {1, 2, 6}
    {3, 1, 2}
    {2, 7, 1}
    

    上述步骤的运行时复杂性将为 O(rt)

    当矩阵已完全填充时,T0将被指定为第0行作为其资源列表,T1指定为第1行......

答案 3 :(得分:0)

我不知道任何算法。一种理解可以重新排序序列的方法是 - 具有代表每个资源的锁。

访问资源时的线程,获取该资源的相应锁。如果另一个线程想要访问相同的资源,则它会使用下一个线程重新调度访问。 例如,T1可以访问R1。如果T2还需要访问R1,那么T2可以改为对R1进行重新调度(交换)访问,然后对R2进行访问,然后假设T1已经完成,则占用R1。