我很想知道MSVC是否使用了bitset::count
编译器内在的__popcnt。
环顾四周,我发现这是VS2017 std::bitset::count
的实现:
size_t count() const _NOEXCEPT
{ // count number of set bits
const char *const _Bitsperbyte =
"\0\1\1\2\1\2\2\3\1\2\2\3\2\3\3\4"
"\1\2\2\3\2\3\3\4\2\3\3\4\3\4\4\5"
"\1\2\2\3\2\3\3\4\2\3\3\4\3\4\4\5"
"\2\3\3\4\3\4\4\5\3\4\4\5\4\5\5\6"
"\1\2\2\3\2\3\3\4\2\3\3\4\3\4\4\5"
"\2\3\3\4\3\4\4\5\3\4\4\5\4\5\5\6"
"\2\3\3\4\3\4\4\5\3\4\4\5\4\5\5\6"
"\3\4\4\5\4\5\5\6\4\5\5\6\5\6\6\7"
"\1\2\2\3\2\3\3\4\2\3\3\4\3\4\4\5"
"\2\3\3\4\3\4\4\5\3\4\4\5\4\5\5\6"
"\2\3\3\4\3\4\4\5\3\4\4\5\4\5\5\6"
"\3\4\4\5\4\5\5\6\4\5\5\6\5\6\6\7"
"\2\3\3\4\3\4\4\5\3\4\4\5\4\5\5\6"
"\3\4\4\5\4\5\5\6\4\5\5\6\5\6\6\7"
"\3\4\4\5\4\5\5\6\4\5\5\6\5\6\6\7"
"\4\5\5\6\5\6\6\7\5\6\6\7\6\7\7\x8";
const unsigned char *_Ptr = &reinterpret_cast<const unsigned char&>(_Array);
const unsigned char *const _End = _Ptr + sizeof (_Array);
size_t _Val = 0;
for ( ; _Ptr != _End; ++_Ptr)
_Val += _Bitsperbyte[*_Ptr];
return (_Val);
}
看起来它使用查找表来获取任何给定字节的位数,然后计算每个字节的1的数量。
According to this answer here,GCC就像这样实现它(按照我的想法):
/// Returns the number of bits which are set.
size_t
count() const { return this->_M_do_count(); }
size_t
_M_do_count() const
{
size_t __result = 0;
for (size_t __i = 0; __i < _Nw; __i++)
__result += __builtin_popcountl(_M_w[__i]);
return __result;
}
虽然我没有对任何事情进行基准测试,但我敢打赌,GCC的实施在这里要快得多。
因此,有没有令人信服的理由说明为什么MSVC会像这样实施std::bitset::count
?我的猜测是MSVC有一个全能的“STL中没有编译器内在函数”的策略,或者我忽略的两个平台之间存在差异。
答案 0 :(得分:1)
GCC中__builtin_popcountl
的内部实现并不是更好,取决于体系结构,如下所示。
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
仅适用于从2006年开始仅支持AMD CPU的SSE4a指令集,__builtin_popcountl
由一个汇编指令POPCNT
组成。
这些内在函数中的每一个都会生成popcnt指令。 popcnt指令返回的值的大小与其参数的大小相同。在32位模式下,没有64位通用寄存器,因此没有64位popcnt。
要确定对popcnt指令的硬件支持,请使用InfoType = 0x00000001调用__cpuid内在函数,并检查CPUInfo [2](ECX)的第23位。如果支持该指令,则该位为1,否则为0。如果您运行的代码在不支持popcnt指令的硬件上使用此内在函数,则结果是不可预测的。
我认为MSVC团队不希望使用具有条件的内在函数来支持一种独立于CPU和体系结构的通用解决方案。
答案 1 :(得分:0)
没有“ STL中没有编译器内部函数”策略,但是有一个要求STL应该在所有受支持的CPU上运行,并且最低CPU版本由最低OS版本的最低要求来选择。因此,只有在为较旧的CPU提供回退功能的情况下,才有可能使用内部指令向较新的CPU注入指令。
当前,VS 2019中的C ++ 20 std::popcount
使用运行时CPU检测,它会退回到位计数。
std::bitset::count
也可以开始使用相同的方法。 STL GitHub中有一个issue,等待维护者或贡献者来实施。