8

设想:

给定一组资源 R: 资源集

给定一组线程 T,它们将并行运行: 一组线程

每个线程需要访问 n 个资源的列表。每个列表都是 R 的一个样本,这意味着每个资源在每个列表中都是唯一的: 线程访问资源的随机样本

但由于访问列表是随机抽样的,因此可能会出现冲突: 冲突的访问

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

问题:

是否有一种算法可以对调度列表进行排序,从而最大限度地减少写入冲突的数量?所以最终的结果应该是这样的: 已解决的冲突

到目前为止我的见解:

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

可能的方法:

我正在寻找这个问题的分析解决方案。它可以是 np-complete 吗?如果是这样的话,我正在考虑设计一种遗传算法来解决这个问题。

编辑 1:添加图表。

4

4 回答 4

4

我认为问题在于“我们能否对列表进行排序以减少冲突”。

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

步骤1

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

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

第2步

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

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

这有利于分配资源,但允许重复。这些副本将成为最近使用的,并且在没有可用的有效选择之前不会再次用于调度。

最后

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

于 2015-08-31T12:33:52.303 回答
4

形式化问题

起初,它看起来像是OSSP的变体。我们需要在处理器上调度R资源。T有些调度时间是0,有些是1

但是,我们需要以时间步长完成整个序列n,并且n*T调度时间恰好不为零。

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

在此处输入图像描述

一次count使用资源的线程数在哪里。ji

构建问题图

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

每个线程顶点都有一个度数n

我们的目标是用颜色给这个图n上色,以这样的方式:

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

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

无冲突解决方案

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

二分图边缘着色可以及时O(n * T * R)完成

有冲突的解决方案

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

受限于冲突的数量。

我们有一些V_conflict带有 degree的顶点d > n。那么冲突的数量正好是q总和最大值(0,dn)

不能少,因为边缘着色中的每种冲突颜色都是我们日程安排中的冲突,并且对于每个具有度数的顶点,d > n我们至少有d - n冲突的颜色。

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

现在插入先前移除q的边,分配尚未分配给其相应线程顶点的任何边的颜色。由于每条添加的边只能引入 1 个冲突,我们现在正好有q,这已被证明是下限。

有冲突的整个步骤可以通过以下方式完成:

  • O(R)用于确定V_conflict

  • O(R*T)用于消除冲突边缘

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

  • O(n * q)用于将边添加回图形

因此可以及时得到完整的解决方案O(n * T * R)

于 2015-09-01T10:17:25.630 回答
3

方法

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

Attn:我断断续续地提到了几个步骤的复杂性,但很快就会更新整体运行时的复杂性。现在我正在尝试提出一个正确的算法。


样品运行

初步假设和定义:

  • 线程数 = t,从线程 0 开始到 t-1
  • 每个线程的资源数 = r
  • 为线程准备资源列表的总资源将>= r

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

首先,我们读取所有列表中的所有资源,并将它们放入单独的资源 ID 桶中。利用Counting Sort,这将是一个线性步骤,需要O(tr + constant)时间。但是,由于这只会给出资源 id 的计数,因此我们需要进一步使用Merge Sort以按出现次数减少的顺序对 id 进行排序。这一步将花费O((rt) log(rt))。结果将是按出现降序排序的列表或 id -

CountedResourceList= <3 次, id 1>, <3 次, id 2>, <2 次, id 3>, <1 次, id 4, 5, 6, 7>

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

接下来,我们创建SortedListMatrixrow = first thread 开始填充以包含最大出现的 resource-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 行...

于 2015-08-26T23:24:07.917 回答
0

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

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

于 2015-09-02T21:15:28.703 回答