如何有效地实现任意序列的按位循环?

Posted

技术标签:

【中文标题】如何有效地实现任意序列的按位循环?【英文标题】:How to efficiently implement bitwise rotate of an arbitrary sequence? 【发布时间】:2019-03-28 11:59:51 【问题描述】:

“由索引排列 p(i) = (i + k) mod n 定义的 n 个元素的排列 p 称为 k-旋转。” -- Stepanov & McJones

std::rotate 已经成为众所周知的算法,这要归功于Sean Parent,但是如何有效地为任意位序列实现它呢? 高效,我的意思是最小化至少两件事,i) 写入次数和 ii) 最坏情况下的空间复杂度。

也就是说,输入应该类似于std::rotate,但按位特定,我猜是这样的:

    指向位序列开始的内存的指针。 三位索引:firstmiddlelast

指针的类型可以是任何无符号整数,并且可能越大越好。 (Boost.Dynamic Bitset 称之为“块”。)

请务必注意,索引可能都从块的开头偏移了不同的量。

根据 Stepanov 和 McJones 的说法,随机访问数据的轮换可以在 n + gcd(n, k) 分配中实现。反转每个子范围然后反转整个范围的算法需要 3n 个分配。 (但是,我同意下面的 cmets,它实际上是 2n 个分配。)由于可以随机访问数组中的位,我假设适用相同的最佳界限。由于不同的子范围块偏移,每个分配通常需要两次读取,但我不太关心读取而不是写入。

该算法的有效或最佳实现是否已经存在于开源领域? 如果没有,怎么办?

我浏览了 Hacker's Delight 和 Knuth 的第 4A 卷,但找不到适合它的算法。

【问题讨论】:

算法确实存在,它是std::rotate 你只需要正确的容器/迭代器 @user463035818 怎么能避免一一写入? “避免逐位写入”是什么意思?你还怎么旋转位? 通过写入适当的字节/字。 但是在你知道要写入哪个字节之前,你需要读取和写入位,不是吗? 【参考方案1】:

例如,使用vector<uint32_t>,您可以轻松且相当高效地在一次通过中完成旋转的小数元素部分(shift_amount%32),然后调用std::rotate 完成其余部分。小数部分很简单,只对相邻的元素进行运算,除了末端,所以你只需要在工作时记住一个部分元素。

如果你想自己做整个事情,那么你可以通过颠倒整个向量的顺序来做旋转,然后颠倒前后部分的顺序。有效地做到这一点的诀窍在于,当您反转整个向量时,您实际上并没有对每个元素进行位反转——您只是认为它们处于相反的顺序。前后部分的反转比较棘手,需要您在工作时记住 4 个部分元素。

就写入内存或缓存而言,上述两种方法都会进行 2N 次写入。您在问题中提到的最佳旋转需要 N,但如果您将其扩展为使用小数词旋转,则每次写入跨越两个单词,然后需要 2N写道。它没有任何优势,而且我认为它会变得很复杂。

也就是说...我相信您可以通过一次执行 m 个字来接近具有固定寄存器存储量的 N 次写入,但仅此而已不过,用于简单轮换的大量代码,您的时间(或至少 我的时间 :) 最好花在其他地方。

【讨论】:

感谢您的建议。我对两者的担忧是,尽管它们在实现方面可能很简单,但它们在分配方面效率不高。我会在问题中加入更多理论来解释。 @JeremyW.Murphy:现实世界的效率不同于理论效率。您的目标是尽量减少写入次数,但这无法区分寄存器写入、L1 高速缓存写入、L2 高速缓存写入和主存储器写入。 Matt 的算法可以将小数部分保存在寄存器中,相邻的写入会进入 L1 缓存。 @JeremyW.Murphy 我写了一个关于内存效率的注释 是的,我同意实际最优算法可能与理论上最优算法完全不同,但我想保持问题相对简单。 我想我需要更多细节来说服第一个想法——转变然后std::rotate——实际上有效。假设fml 的索引模块大小分别为1、2 和5。旋转后,第一个子范围相对于块右移了 5 - 2 = 3,而第二个子范围相对于块左移了 2 - 1 = 1。这些转变不能就地完成(在std::rotate 之前),因为它们会导致m 重叠。也许我误解了你的想法,所以更多细节将不胜感激。

以上是关于如何有效地实现任意序列的按位循环?的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地将元素插入数组的任意位置?

matlab 如何实现按位异或 g=01001011 q=10100010 如何实现g q的按位异或 结果也是8位

子序列的按位或 Bitwise ORs of Subarrays

XMM 寄存器的按位取反

并行/组合多个 64 位值的按位排列

常量的按位或