CPU|Visual Studio 2019新特性:位操作


CPU|Visual Studio 2019新特性:位操作
新特性C++ 20标准中添加了两个用来进行位操作的库:
1) 位翻转和计数
2) 两项操作的整数幂
大部分新添加的函数模板都是比较简单的数值计算函数 , 以及一个对应现代CPU里的一些通用指令的简单映射 。
在Visual Studio 2019 v16.8 Preview 2中 , 我们实现了它们 。 这篇文章主要是简单介绍我们的实现和一些你可以使用得到的针对处理器级别的优化 。
所有的函数都已经启用了constexpr特性 , 也就是说 , 它们可以用来方便地计算查找表(lookup table) 。 我知道 , 在其他一些数值计算代码库中 , 这些函数已经被实现过了 , 希望这里的标准版本能对广大开发者带来益处 。
下面是新的函数 。
std::countl_zero 和 std::countr_zero这两个函数用来分别计算一个操作数从左边(从MSB到LSB)或者右边(从LSB到MSB)开始的0的数目 。
在x86和x64平台上 , 它们分别调用LZCNT和TZCNT这两个CPU指令 。 在默认情况下 , 这两个指令的可用性会在运行时检查 。 如果运行时检查失败时 , 会使用BSF和BSR指令 。 当使用/arch:AVX2或者更高版本进行编译时 , 会忽略这个运行时检查 , 因为只要处理器支持AVX2 , 则它们就会支持LZCNT和TZCNT 。
【CPU|Visual Studio 2019新特性:位操作】如果是ARM和ARM64 , 则countl_zero函数会调用CLZ指令 , countr_zero函数在当前这个版本中不会调用任何ARM或者ARM64指令 。
有意思的是 , LZCNT和TZCNT在x86和x64平台上会包含一些比较古怪的指令 , 它们被编码为REP BSF和REP BSR 。 在不支持LZCNT和TZCNT指令的CPU上 , REP前缀会被简单地忽略 。 这意味着 , 在不支持LZCNT或TZCNT的CPU上执行代码仍将运行 , 但TZCNT不会为零提供正确的输出 , LZCNT的零输出都是错误的 , 并从最低有效位开始返回第一个设置位的索引 , 这与它在实际支持指令的cpu上所做的相反 。
这并不是一个非常有用的回退(Fallback) , 虽然我们试图在中使用它来简化一些代码 , 但最终它带来的麻烦却超出了它的价值 。
请注意 , 在Visual Studio 2019 v16.8 Preview 1中包含一个关于LZCNT使用在只支持BSR指令CPU上的Bug 。 此问题将会在Visual Studio 2019 v16.8 Preview 3中修复 。
std::popcountstd::popcount将会计算给定输入数值中的设置位的数目 。
在x86和x64平台上 , popcount将会调用POPCNT指令 , 同样地 , 运行时将会检查POPCNT指令的可用性 。 如果使用/arch:AVX或者更高进行编译 , 则不会进行此运行时检查 , 因为支持AVX的CPU已经支持POPCNT指令了 。 在当前的版本中 , 如果是ARM或者ARM64平台上 , 则不会调用任何给定的指令 。
std::countl_one和std::countr_one此函数会计算给定输入的从左边或者右边算起的逻辑1的个数 。 这两个函数的实现依赖于上面提到的countl_zero和countr_zero 。
std::has_single_bit以上函数等效于popcount(x) == 1 , 它没有使用到任何特定的处理器指令 。
std::bit_ceil和std::bit_floor这两个函数将会查找给定输入的最近的2个操作数的幂 。 如果输入已经是两个操作数的幂了 , 则直接返回原值 。 如果计算出来的结果不能按照输入的类型进行表示 , 则此函数的行为是未定义的 。
例如:bit_ceil(static_cast(0b11111111)
如果这种未定义的行为没有发生 , 则bit_ceil只能以常量表达式来使用 。
当输入值是0的时候 , bit_floor将返回0 。
std::rotl和std::rotr以上函数按位对输入进行向左或者向右翻转 。 这两个函数在当前的版本没有使用特定的处理器指令 。
最后Microsoft Visual C++团队的博客是我非常喜欢的博客之一 , 里面有很多关于Visual C++的知识和最新开发进展 。 大浪淘沙 , 如果你对Visual C++这门古老的技术还是那么感兴趣 , 则可以经常去他们那(或者我这)逛逛 。


推荐阅读