- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在给定std::bitset<64> bits
的情况下设置了任意数量的位,并且设置了位位置X
(0-63)
最有效的方法是对X或更低位置的位数进行计数,或者如果未设置X的位,则返回0是最有效的方法
注意:如果该位置1,则返回值始终至少为1
蛮力方式很慢:
int countupto(std::bitset<64> bits, int X)
{
if (!bits[X]) return 0;
int total=1;
for (int i=0; i < X; ++i)
{
total+=bits[i];
}
return total;
}
count()
的
bitset
methof将为您提供所有位的
popcount
,但
bitset
不支持范围
最佳答案
此C++使g++发出very good x86 ASM (godbolt compiler explorer)。我希望它也可以在其他64位体系结构上有效地进行编译(如果有供std::bitset::count
使用的HW popcount,否则它将始终很慢;例如,请确保使用g++ -march=nehalem
或更高版本,或者如果您不想使用-mpopcnt
如果可以将代码限制为仅在支持该x86指令的CPU上运行,请启用其他功能):
#include <bitset>
int popcount_subset(std::bitset<64> A, int pos) {
int high_bits_to_eliminate = 63 - pos;
A <<= (high_bits_to_eliminate & 63); // puts A[pos] at A[63].
return (A[63]? ~0ULL : 0) & A.count(); // most efficient way: great code with gcc and clang
// see the godbolt link for some #ifdefs with other ways to do the check, like
// return A[BSET_SIZE-1] ? A.count() : 0;
}
63
做一些处理,并将移位计数的
& 63
掩码更改为更常规的范围检查。为了获得奇特大小的位集的最佳性能,请对目标计算机的
size <= register width
进行专门化的模板功能。在这种情况下,将位集提取为适当宽度的
unsigned
类型,然后移至寄存器的顶部而不是位集的顶部。
bitset<32>
生成理想的代码,但事实并非如此。 gcc/clang仍在x86-64上使用64位寄存器。
pos
的单词下面的单词进行弹出计数并在该单词上使用该单词慢。 (如果您可以假设SSSE3而不是
popcnt
insn硬件支持或针对32位目标,则这是矢量化popcount真正在x86上的亮点。AVX2256位
pshufb
是执行批量popcount的最快方法,但是如果没有AVX2,我认为64位
popcnt
很漂亮接近于128位
pshufb
实现。有关更多讨论,请参见评论。)
psadbw
,以在基于
pshufb
的popcnt单独生成每个字节的位数之后,对64位块中的水平和字节进行水平求和。 SSE/AVX没有64位算术右移,但是您可以使用另一种技术来混合每个元素的高位。
(1<<(pos+1)) -1
)并对其进行
&
。一种更有效的方法是通过
63-pos
左移,将要打包的位保留在寄存器的顶部。
popcnt
指令仅在Nehalem及更高版本上可用。我忘了AMD增加支持的时间。
popcnt
的后备资源来进行CPU调度。或者,制作独立的/不依赖于某些CPU功能的二进制文件。
popcnt
指令的popcount可以通过几种方法来完成。一个使用SSSE3
pshufb
来实现4位LUT。当在整个阵列上使用时,这是最有效的,而不是一次使用单个64b。标量bithack可能是这里最好的,并且不需要SSSE3(因此可以与具有64位但不包含pshufb的古老AMD CPU兼容)。
(A[63]? ~0ULL : 0)
要求编译器将高位广播到所有其他位位置,以便将其用作AND掩码,以将popcount结果归零(或不归零)。请注意,即使对于较大的位集大小,它仍然仅掩盖了
popcnt
的输出,而不是位集本身,因此
~0ULL
很好,我使用ULL来确保不再要求编译器仅将该位广播到ab的低32b。注册(例如,在Windows上使用
UL
)。
((int64_t)something) >> 63
并不是严格可移植的,因为带符号的右移是
implementation-defined as either arithmetic or logical。该标准未提供任何可移植的算术右移运算符。 (不过,它不是
undefined behaviour。)幸运的是,编译器足够聪明:一旦给了足够的提示,gcc就会找到最佳方法。
-O3 -march=nehalem -mtune=haswell
编译(较旧的gcc,如4.9.2,仍会发出此代码):
; the original ternary-operator version. See below for the optimal version we can coax gcc into emitting.
popcount_subset(std::bitset<64ul>, int):
; input bitset in rdi, input count in esi (SysV ABI)
mov ecx, esi ; x86 variable-count shift requires the count in cl
xor edx, edx ; edx=0
xor eax, eax ; gcc's workaround for popcnt's false dependency on the old value of dest, on Intel
not ecx ; two's complement bithack for 63-pos (in the low bits of the register)
sal rdi, cl ; rdi << ((63-pos) & 63); same insn as shl (arithmetic == logical left shift)
popcnt rdx, rdi
test rdi, rdi ; sets SF if the high bit is set.
cmovs rax, rdx ; conditional-move on the sign flag
ret
-x == ~x + 1
2的补码身份的背景信息,请参见
How to prove that the C statement -x, ~x+1, and ~(x-1) yield the same results?。 (并且
Which 2's complement integer operations can be used without zeroing high bits in the inputs, if only the low part of the result is wanted?切线地提到
shl
掩盖了移位计数,因此我们只需要
ecx
的低6位来保存
63 - pos
。主要是因为我最近写了它,而且仍然在阅读此段落的任何人都可能觉得它有趣。)
USE_mul
启用),gcc可以
shr rdi, 63
imul eax, edi
xor
/
test
/
cmovs
。
mov r,r
:1个融合域uop,0个延迟,无执行单元xor
-zeroing:1个融合域uop,无执行单元not
:p0/p1/p5/p6 1 uop,1c延迟,每0.25c吞吐量1个shl
(又名sal
),其中cl
计数:p0/p6为3 uops:2c延迟,每2c吞吐量1。 (奇怪的是,Agner Fog的数据表明IvyBridge仅需2 oups。)popcnt
:1 uop for p1,3c延迟,每1c吞吐量1 shr r,imm
:p0/p6为1 uop,延迟为1c。每0.5c吞吐量1个。 imul r,r
:p1为1uop,延迟为3c。 ret
shl
(2)->
popcnt
(3)->
imul
(3)。总共
8个周期。或准备好
pos
时的9c,因为
not
为其额外的1c延迟。
bitbroadcast
的
版本将
shr
替换为
sar
(相同的性能),并使用
imul
替换
and
(延迟为1c,而不是3c,可在任何端口上运行)。因此,唯一的性能变化是
,将关键路径延迟减少到6个周期。吞吐量仍然是前端的瓶颈。能够在任何端口上运行的
and
没什么区别,除非您将其与端口1上出现瓶颈的代码混合在一起(而不是在紧密循环中查看仅运行此代码的吞吐量)。
cmov
是2c延迟,对于p0/p1/p5/p6中的任何一个都是2微秒。)
test
/
cmovs
,它使用算术右移将符号位广播到寄存器的所有位置,从而生成全1或全0的掩码。我喜欢它:在英特尔上,使用
and
代替
cmov
更有效。但是,它仍然具有数据依赖性,并且可以在分支的两侧进行工作(这通常是cmov的主要缺点)。更新:通过正确的源代码,gcc也将使用此方法。
-O3 -Wall -march=nehalem -mtune=haswell
popcount_subset(std::bitset<64ul>, int):
mov ecx, 63
sub ecx, esi ; larger code size, but faster on CPUs without mov-elimination
shl rdi, cl ; rdi << ((63-pos) & 63)
popcnt rax, rdi ; doesn't start a fresh dep chain before this, like gcc does
sar rdi, 63 ; broadcast the sign bit
and eax, edi ; eax = 0 or its previous value
ret
sar / and
替换了
xor / test / cmov
,并且
cmov
是Intel CPU上的2 uop指令,因此非常好。 (对于三元运算符版本)。
sar / and
技巧而不是实际的
imul
。因此,这些帮助gcc不会损害clang。 (
sar/and
绝对比
shr/imul
更好:在关键路径上的延迟减少了2c。)
pow_of_two_sub
版本确实伤害了clang(请参阅第一个“godbolt”链接:此答案中省略了它,以避免困惑的想法没有被提出)。
mov ecx, 63
/
sub ecx, esi
实际上更快。这包括Intel之前的IvyBridge,但不包括更新的Intel和AMD CPU。
mov imm
/
sub
方法仅将
pos
的延迟周期放到关键路径上(除了bitset-> result延迟之外),而不是在
mov ecx, esi
具有1c延迟的CPU上将
not ecx
/
mov r,r
延迟两个周期。
mov
保存为
ecx
。其他所有功能都一样,因为
shlx
将其移位计数输入寄存器屏蔽为操作数大小,就像
shl
一样。
shl r, cl
解码为3 oups,但是BMI2
shlx r, r, r
只有1。因此,非常糟糕的是gcc仍然使用
sal
而不是
-march=haswell
发出
shlx
(在某些其他情况下会使用)。
// hand-tuned BMI2 version using the NOT trick and the bitbroadcast
popcount_subset(std::bitset<64ul>, int):
not esi ; The low 6 bits hold 63-pos. gcc's two-s complement trick
xor eax, eax ; break false dependency on Intel. maybe not needed when inlined.
shlx rdi, rdi, rsi ; rdi << ((63-pos) & 63)
popcnt rax, rdi
sar rdi, 63 ; broadcast the sign bit: rdi=0 or -1
and eax, edi ; eax = 0 or its previous value
ret
shlx
(1)->
popcnt
(3)->
and
(1)= 5c位集->结果。 (或
pos
-> result中的6c)。
xor eax, eax
。仅因为
popcnt
's false dependency on the output register (on Intel)而存在,并且我们需要
eax
的输出(调用者最近可能在较长的dep链上使用了该输出)。使用
-mtune=bdver2
或其他东西,gcc不会将要用于
popcnt
输出的寄存器清零。
popcnt
的源寄存器就已经准备就绪的输出寄存器,以避免出现此问题。当以后不需要源代码时,编译器将执行就地
popcnt rdi,rdi
,但这不是这种情况。相反,我们可以选择另一个在源之前已经准备好的寄存器。
popcnt
的输入取决于
63-pos
,我们可以破坏它,因此
popcnt rsi,rdi
的对rsi的依赖关系不会延迟它。或者,如果我们在寄存器中包含
63
,则可以
popcnt rsi,rdi
/
sarx rax, rsi, reg_63
/
and eax, esi
。否则,BMI2 3操作数转换指令也不会让我们在以后需要输入时费心。
63-pos
可以使用编译时常量进行优化,也可以优化到变量计数来自何处。)
shl
/
bt rdi, 63
/
jc
。它甚至以非常愚蠢的方式建立分支机构。它可以将eax设为零,然后根据
shl
设置的符号标志跳过popcnt或不跳过。
-O3 -march=corei7
的ICC13输出开始:
// hand-tuned, not compiler output
mov ecx, esi ; ICC uses neg/add/mov :/
not ecx
xor eax, eax ; breaks the false dep, or is the return value in the taken-branch case
shl rdi, cl
jns .bit_not_set
popcnt rax, rdi
.bit_not_set:
ret
A[pos] == true
案例有一个未采用的分支。但是,与无分支方法相比,它并没有节省太多。
A[pos] == false
情况更常见:跳过
ret
指令,转到
popcnt
/
ret
。 (或内联之后:跳转到执行
popcnt
的末尾的一个块并跳回)。
关于c++ - 在一个位置或更低的位置计数设置位的有效方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34407437/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!