cc:7.5 Windows:10.0 cuda:11.7
我正在对设备内存执行一系列原子操作。经线中的每根线都在一个连续的uint32_t上运行。块中的每个扭曲都会更新这些相同的值,然后它们都会移动到下一行。
由于我没有使用任何共享内存,我希望它能用于缓存设备内存,从而有效地对共享内存执行原子操作,而不会带来同步线程和复制数据的所有开销和麻烦。
但这一表现表明,事实并非如此。
事实上,看看NSight,它说一级缓存的命中率为0%。哎哟内存工作负载分析还显示全局原子ALU下的命中率为0%。
谷歌发现了一个热门话题(有些过时),表明原子能总是通过L2来实现设备内存。不完全是一个权威的来源,但它符合我所看到的。另一方面,这似乎表明它确实(确实?)通过了L1。一个更权威的消息来源,但并不完全正确。
我可能有什么误解了吗?也许我的代码没有按我想的那样运行?还是针对设备内存的原子操作总是通过L2?
- 我试着用RED代替原子,但没有任何区别
- 我还尝试使用atomicAnd_block而不仅仅是atomicAnd,不知怎么的,这让事情变得更慢了?不是我所期望的
- 我想试用redux,但cc 8.0还不是我的选择__shfl_sync结果令人失望(性能方面)
在这一点上,我倾向于相信在7.5中,设备内存上的原子总是通过L2。但如果有人有相反的证据,我可以继续挖掘。
与Nvidia一样,很难获得具体信息。但我们可以查看PTX文档并推断一些事情。
原子加载和存储
原子加载和存储使用其常规ld
和st
指令的变体,它们具有以下模式:
ld{.weak}{.ss}{.cop}{.level::cache_hint}{.level::prefetch_size}{.vec}.type d, [a]{, cache-policy};
ld.sem.scope{.ss}{.level::eviction_priority}{.level::cache_hint}{.level::prefetch_size}{.vec}.type;
st{.weak}{.ss}{.cop}{.level::cache_hint}{.vec}.type [a], b{, cache-policy};
st.sem.scope{.ss}{.level::eviction_priority}{.level::cache_hint}{.vec}.type [a], b{, cache-policy};
CCD_ 3的加载和存储是常规的存储器操作。cop
部分指定了缓存行为。出于我们的目的,有ld.cg
(全局缓存)只使用二级缓存,还有ld.ca
(全部缓存)使用一级和二级缓存。如文件所示:
全局数据在L2级别是一致的,但多个L1缓存对全局数据不一致。如果一个线程经由一个L1高速缓存存储到全局存储器,并且第二线程经由具有
ld.ca
的第二L1高速缓存加载该地址,则第二线程可能获得陈旧的L1高速缓存数据,而不是由第一线程存储的数据。驱动程序必须使并行线程的相关网格之间的全局一级缓存线无效。第一网格程序的存储然后由发出缓存在L1中的默认ld.ca
加载的第二网格程序正确地获取。
类似地,还有一个仅在L2中缓存的st.cg
。它";绕过L1高速缓存"措辞并不精确,但听起来似乎这会使一级缓存失效。否则,即使在一个线程中,ld.ca; st.cg; ld.ca
序列也会读取过时的数据,这听起来像是一个疯狂的想法。
用于写入的第二相关CCD_ 11是CCD_。文件中的措辞非常奇怪。我想这会写回一级缓存,以后可能会驱逐到二级及以上。
ld.sem
和st.sem
(其中sem是弛豫、获取或释放之一)是真正的原子负载和存储。范围给出了同步的范围,例如,意味着获取是在线程块内还是在整个GPU上同步。
请注意,这些操作没有cop
元素。因此,您甚至无法指定缓存层。您可以提供缓存提示,但我看不出这些提示如何足以指定所需的语义。CCD_ 16和CCD_。
只有eviction_priority
提到L1。但仅仅因为性能提示被接受并不意味着它有任何效果。我认为它适用于弱内存操作,但对于原子,只有L2策略有任何影响。但这只是猜测。
原子读取修改写入
atom
指令用于原子交换、比较交换、加法等。red
用于归约。它们具有以下结构:
atom{.sem}{.scope}{.space}.op{.level::cache_hint}.type d, [a], b{, cache-policy};
red{.sem}{.scope}{.space}.op{.level::cache_hint}.type [a], b{, cache-policy};
有了这些元素:
- sem:内存同步行为,如获取、释放或放松
- scope:内存同步范围,例如在CTA(线程块)或GPU内获取发布
- 空间:全局内存或共享内存
- 缓存策略、级别和提示:缓存驱逐策略。但L1没有选择,只有L2
由于无法指定一级缓存或写回行为,因此无法在一级缓存上使用原子RMW操作这对我来说很有意义。为什么GPU要在实现这一点上浪费晶体管?共享内存的存在正是为了允许线程块中的快速内存操作。