情境:
给定一组资源R:
给定一组线程T,它将并行运行:
每个线程都需要访问n个资源的列表。每个列表都是R的样本,这意味着每个资源在每个列表中都是唯一的:
但由于访问列表是随机抽样的,因此可能存在冲突:
随机资源列表将在开头初始化一次。之后,每个线程随后将对列表中的每个资源执行atomicAdd操作。每个列表中资源的访问顺序无关紧要。
问题:
是否存在对调度列表进行排序的算法,以便最大限度地减少写入冲突的数量?所以最终结果如下:
到目前为止我的见解:
可能的方法:
我正在为这个问题寻找解决方案。它可能是完整的吗?如果是这种情况,我正在考虑设计一种遗传算法来解决这个问题。
修改1 :添加了图表。
答案 0 :(得分:4)
我认为问题是要求"我们可以对列表进行排序以减少冲突。"
我认为最佳解决方案是NP完成,但我会查找每个资源的集合中出现的次数。
最常用的资源是最难安排的资源。因此,我会在每个线程中将此资源放置在位置1,2,3,4 ......中,直到发生冲突(这可能是不可避免的),例如1,2,3,4,...,n,1,2,....
这些是"大石头"。这些应该放在第一位。
然后应尝试下一个最常用的资源。这应该识别最近使用了哪些时隙(1 => n),并在该列表中搜索未分配且最近未使用的切片。
无论选择哪个插槽,它都会移动到最近使用的队列的顶部,以避免一段时间。
这有利于分发资源,但允许重复。这些重复项最近将被使用,并且在没有有效选择可用之前不会再次受到调度。
对每个资源按其出现的顺序重复步骤2。
答案 1 :(得分:4)
起初,它看起来像OSSP的变体。我们需要在R
个处理器上安排T
个资源。有些计划时间为0
,有些则为1
。
但是,我们需要在n
个时间步长内完成整个序列,并且确实有n*T
个非零调度时间。
因此,我们正在寻找n
时间内的调度,没有T-T
冲突(因为没有线程可以同时对两个资源进行操作),并且{{1}的最小数量冲突我假设要最小化的目标函数是:
其中R-R
是在count
时使用资源j
的多个线程。
让我们为每个线程(第一部分)和每个资源(第二部分)构建一个带有顶点的图i
。对于每个非零调度时间,我们都有从线程到资源的优势。这个图显然是二分的。
每个线程顶点的度数为G=(V,E)
。
我们的目标是使用n
颜色对此图表进行边缘着色,其方式如下:
没有线程顶点具有两个相同颜色的相邻边
具有相同颜色的相邻边数最小
如果没有度n
的资源顶点,则图形具有最多d > n
种颜色的正确边缘着色。正确的着色当然是目标函数的最佳着色 - 根本没有冲突。
双向图边缘着色可以在O(n * T * R)
time中完成。
现在,假设我们有一个度n
的资源顶点。这意味着d > n
颜色没有正确的边缘着色,我们的日程安排会有冲突。
限制冲突次数。
我们有一些n
的顶点V_conflict
。然后,冲突的数量正好是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)
CountedResourceList
的合并列表,按照计数的递减顺序存储<resource-id, total occurrence count of that id>
。这将花费O(rt * log(rt))时间。 为什么?在下面的示例运行中进行了解释。同样在同一次迭代中,我们创建了一个HashMap,这样我们就可以在O(1)时间内找到这个资源id放在哪个线程的列表中(基于Inverted Index的想法)对于那些可以联系的人。)SortedListMatrix
- t行和r列。在这个矩阵中,我们首先将CountedResourceList
中的资源ID放入该线程的行中,该行最初包含该最大发生的资源ID。通过这种方式,资源 - ids以大多数发生的顺序排放到最不发生的。int[t] FreeColumnCount
来保存每个线程的总空闲列数。由于具有较小空闲槽的线程在冲突的情况下具有较少的移动资源id的能力,因此资源id将首先填充在包含该资源id的线程具有最少空闲槽的行中。row = GetRowForResource(id) in the HashMap, and column = (column+1)%r
选择要放置的下一个ID的位置。如果行中的结果列被占用,我们通过使用相同的列值公式迭代来获取下一个未占用的列值,而不更改行。因此 - 列需要环绕并且从HashMap确定行,以便资源ID进入正确的列表和最不相互冲突的位置。Attn:我间歇性地提到了几个步骤的复杂性,但很快就会更新整体运行时复杂性。目前我正在努力想出一个正确的算法。
初步假设和定义:
让我们从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。