无法将具有相同优先级的对象插入ConcurrentSkipListSet(Unable to insert objects of equal priority into ConcurrentSkipListSet)
我在使用ConcurrentSkipListSet处理观察者模式的简单线程安全实现时遇到问题,以便在插入期间跟踪观察者优先级。 大多数观察者都没有任何特殊的优先权归属于他们,并且遵循此Comparable#compareTo方法在比较时将显示相同的优先级(其中优先级是五个优先级的枚举值,范围从最高到最低):
public int compareTo(BaseLink<?> link) { return this.priority.compareTo(link.getPriority()); }
当我向ConcurrentSkipListSet添加具有相同优先级的观察者时,似乎在插入过程中会丢失一些添加的对象。 更改我在测试时创建的任何观察者的优先级会导致这些观察者被添加到集合中而没有问题,但我认为如果给予相同优先级的足够观察者,则问题将再次出现。
我不确定导致这个问题的原因,以及我应该做些什么来帮助解决这个问题。 我有什么办法可以解决这个问题吗? 或者,如果这是ConcurrentSkipListSet的固有问题,是否还有其他线程安全的数据结构可以为独特对象提供合理的高效插入和排序时间?
I am having an issue with a simplistic thread-safe implementation of the observer pattern using a ConcurrentSkipListSet to handle keeping track of observer priorities during insertion. The majority of observers will not have any special priority attributed to them, and following this Comparable#compareTo method will show as equal priority when compared (where priority is a value in an enum of five priorities ranging from highest to lowest):
public int compareTo(BaseLink<?> link) { return this.priority.compareTo(link.getPriority()); }
When I add observers of equal priorities to the ConcurrentSkipListSet , it seems like some of the added objects are simply lost during the insertion process. Changing the priorities of any of the observers I have created while testing this results in those observers being added to the set without issue, though I assume that given enough observers of the same priority the issue will arise again.
I am unsure about what is causing this issue, and of what I should do to help resolve it. Is there anything I can do to resolve this issue? Alternatively if this is an inherent problem with the ConcurrentSkipListSet, are there any other thread-safe data structures that can give me reasonably performant insertion and sorting times for unique objects?
原文:https://stackoverflow.com/questions/37310754
最满意答案
事实证明,我没有正确理解Keppler架构。 正如Greg Smith上面的评论中指出的那样,Keppler可以配置为具有8个字节的32个共享存储体。 在这种情况下,使用
cudaDeviceSetSharedMemConfig( cudaSharedMemBankSizeEightByte )
,共享内存布局如下所示:bank: B0 B1 B2 B3 B4 .. B31 ---------------------------------- index: D00 D01 D02 D03 D04 .. D31 D32 D33 D34 D35 D36 .. D63
现在,对于我的简单示例(使用
itot=16
),在一个warp中处理对例如前两行(threadIdx.y=0
,threadIdx.y=1
)的共享内存的写入/读取。 这意味着对于threadIdx.y=0
值D00..D15
存储在B0..B15
,则存在两个双精度的填充,之后在相同的变形值D18..D33
中存储在B18..B31+B00..B01
,导致B00-B01
发生银行冲突。 如果没有填充(ng=0
),第一行将写入D00..D15
中的B00..B15
,即D00..D15
中B00..B15
中的第二行,因此不会发生库冲突。对于
blockDim.x>=32
的线程块,不应该出现问题。 例如,对于itot=32
,blockDim.x=32
,ng=2
,第一行存储在存储体B00..B31
,然后存储两个单元格填充,第二行存储在B02..B31+B00..B01
等中。It turns out that I didn’t understand the Keppler architecture correctly. As pointed out in one of the comments above by Greg Smith, Keppler can be configured to have 32 shared memory banks of 8 bytes. In such a case, using
cudaDeviceSetSharedMemConfig( cudaSharedMemBankSizeEightByte )
, the shared memory layout looks like:bank: B0 B1 B2 B3 B4 .. B31 ---------------------------------- index: D00 D01 D02 D03 D04 .. D31 D32 D33 D34 D35 D36 .. D63
Now, for my simple example (using
itot=16
), the writing/reading to/from shared memory on e.g. the first two rows (threadIdx.y=0
,threadIdx.y=1
) is handled within one warp. This means that forthreadIdx.y=0
valuesD00..D15
are stored inB0..B15
, then there is a padding of two doubles, after which within the same warp valuesD18..D33
are stored inB18..B31+B00..B01
, which causes a bank conflict onB00-B01
. Without the padding (ng=0
) the first row is written toD00..D15
inB00..B15
, the second row inD16..D31
inB16..B31
, so no bank conflict occurs.For a thread block of
blockDim.x>=32
the problem shouldn’t occur. For example, foritot=32
,blockDim.x=32
,ng=2
, the first row is stored in banksB00..B31
, then two cells padding, second row inB02..B31+B00..B01
, etc.
相关问答
更多-
对于nvidia(和amd),gpus本地内存分为内存块。 每个银行一次只能处理一个数据集,所以如果一半warp尝试从/向同一个银行加载/存储数据,那么访问必须被序列化(这是一个银行冲突)。 对于gt200 gpus,有16个银行(32个银行为费米),16个银行或32个银行为AMD gpus(57xx或更高:32,所有以下:16)),它们与32位的粒度交错(所以字节0-3在银行1中的银行1,4-7,银行1中的...,64-69等)。 为了更好的可视化,它基本上看起来像这样: Bank | 1 ...
-
你实际上有一个结构数组(AoS)。 这对于GPU编程来说是规范的。 您可以使用标准AoS-> SoA数据重组方法来修复访问,以便相邻线程访问相邻元素(这将防止存储体冲突)。 以较大的块加载数据,例如重组您的结构,使其可以表示4个float4数量而不是16 float数量。 编译器可能能够将负载组织成float4加载,这将减少银行冲突。 如果你真的需要两种访问方法,你甚至可以在结构中使用union。 根据评论中的问题,让我们看出第二种情况。 对于每个结构16个float数量,无论是存储为float数组还是fl ...
-
当我编译你现在在linux上的代码时,我得到以下警告: t614.cu(55): warning: __shared__ memory variable with non-empty constructor or destructor (potential race between threads) 这种警告不应该被忽略。 它与这行代码相关联: __shared__ double3c blockmean[THREADS_PER_BLOCK]; 由于存储在共享内存中的这些对象(通过构造函数)的初始化将以 ...
-
让我们来看看它产生的ptx。 //Declare some registers .reg .s32 %r<5>; .reg .s64 %rd<4>; // demoted variable .shared .align 4 .u32 _Z6kernelv$__cuda_local_var_35411_30_non_const_mywarp; //load tid in register r1 mov.u32 %r1, %tid.x; //multiple tid ...
-
正如@RobertHarvey所说,它被记录在案。 编程指南指出了16个银行的计算能力1.x ,以及32个银行的计算能力2.x和3.x。 因此,您可以根据设备属性中返回的计算能力(主要版本)做出任何决定。 cuda在线文档的常用链接包含在cuda标签的info链接中。 As @RobertHarvey says, it's documented. The programming guide indicates 16 banks for compute capability 1.x, and 32 banks ...
-
针对64位线程独立共享内存最小化银行冲突的策略(Strategy for minimizing bank conflicts for 64-bit thread-separate shared memory)[2022-02-16]
tl; dr:使用与32位值相同的交错类型。 在晚于开普勒的微体系结构(至Volta)上,我们理论上可以得到的最好结果是2个共享内存事务,用于读取单个64位值的完整内存事务(因为单个事务最多可为每个通道提供32位)。 在实践中,您可以通过您为32位数据描述的类似布局模式来实现这一点。 也就是说,对于T* arr , i有通道i读取idx元素为T[idx + i * 32] 。 这将编译,以便发生两个事务: 较低的16个通道从T中的前32 * 4个字节获得它们的数据(利用所有的库) 较高的16从T中的连续32 ... -
银行冲突CUDA共享内存?(Bank conflict CUDA shared memory?)[2023-06-11]
事实证明,我没有正确理解Keppler架构。 正如Greg Smith上面的评论中指出的那样,Keppler可以配置为具有8个字节的32个共享存储体。 在这种情况下,使用cudaDeviceSetSharedMemConfig( cudaSharedMemBankSizeEightByte ) ,共享内存布局如下所示: bank: B0 B1 B2 B3 B4 .. B31 ---------------------------------- index: D00 ... -
CUDA中的共享内存分配(Shared memory allocation in CUDA)[2022-11-06]
Nsight Visual Studio Edition CUDA调试器有几个选项来控制可变监视窗口中出现的表达式的评估和可视化。 阵列扩展的默认设置为64.限制是为了避免限制评估大型阵列的成本。 要更改设置 从顶级Nsight菜单执行命令Options ... 在NVIDIA Nsight选项对话框中 在左侧窗格中选择调试器 在右侧窗格中,将Max array expansion elements的设置更改为128 您的用例的另一种解决方案是打开四个内存窗口中的一个并配置columns = 64和type ... -
因为SM可以由于其他因素(例如不同的分支逻辑)而重放指令。 因此,我可以假设60%的代码由于分支而重新发布,20%由于全局内存而重新发布。 你可以发一个片段吗? 从Cuda 4.0 Profiler的F1帮助菜单中: 重播指令(%)这给出了在内核执行期间重放的指令的百分比。 重放指令是硬件实际发出的指令数与内核要执行的指令数之间的差异。 理想情况下,这应该为零。 计算为100 *(发出指令 - 执行指令)/发出指令 全局内存重放(%)由于全局内存访问而导致的重放指令的百分比。 这被计算为100 *(l1全局 ...
-
从我所看到的,在循环迭代之间的应用程序中可能存在危险。 对循环迭代frame_idx+1 deltas[lex_index_block]的写入可以映射到与迭代时不同线程中的deltas[lex_index_2D(threadIdx.x, threadIdx.y -1, blockDim.x)]的读取相同的位置frame_idx 。 这两个访问是无序的,结果是不确定的。 尝试使用cuda-memcheck --tool racecheck运行应用程序cuda-memcheck --tool racecheck ...