是否有类似于 std::bitset 的非拥有引用来为其他容器中的数据提供按位操作和计数?

fcc*_*fcc 4 c++ simd bitwise-operators std-bitset c++17

我正在尝试为 ORB 功能实现 C++17 可移植且高效的汉明距离函数,希望在为 x86 和 ARM 编译时自动使用 SIMD。

有问题std::bitset

std::bitset<N>提供了按位运算和计数的标准方式,并且它也优于__builtin_popcount. cv::Mat然而,它是一种拥有数据的容器类型,并且不容易从由 计算的存储的 256 位向量转换而来cv::ORB::detectAndCompute

该线程要求转换cv::Matstd::bitset<256>. 我认为memcpy它的答案不正确,因为我没有std::bitsethttps://en.cppreference.com/w/cpp/utility/bitset中找到内存布局。此外,std::bitset构造函数不支持初始化超过位sizeof(unsigned long long)

我的实施有问题

为了避免复制,我当前的实现使用类似 span 的类:

struct ORB_view {
  inline static constexpr int length = 256 / sizeof(uint32_t) / 8;
  const uint32_t* data;
};
Run Code Online (Sandbox Code Playgroud)

但是,这不能直接使用按位运算和 popcount,并且会导致在实现中出现显式的 SIMD 指令,我希望通过使用std::bitset<N>::count().

我的期望是什么

因此,在这种情况下,提供按位运算和计数的非拥有引用将非常有帮助。所需的功能是:

  • 没有数据副本,
  • 固定长度为std::bitset
  • 按位异或运算和 popcount
  • 没有针对 ARM 或 x86 的显式 SIMD 代码

据我所知标准库没有提供它。

Jan*_*tke 6

首先,如果性能很重要,那么您应该远离std::bitset大多数用途。它会在无效输入上引发异常,并且这些运行时检查对于高性能应用程序而言不是最佳选择。

其次,如果您能够使用 C++20,您就可以使用std::popcount. 在 C++20 之前,您可以使用__builtin_popcnt. 您已经指出了可移植性问题,但这些问题可以通过条件编译来克服:

#if __cpp_lib_bitops >= 201907L // C++20 std::popcount
#include <bit>
using std::popcount;
#elif defined(__GNUC__) // GCC and clang/LLVM
inline int popcount(unsigned char x) noexcept {
    return __builtin_popcount(x);
}
inline int popcount(unsigned short x) noexcept {
    return __builtin_popcount(x);
}
inline int popcount(unsigned int x) noexcept {
    return __builtin_popcount(x);
}
inline int popcount(unsigned long x) noexcept {
    return __builtin_popcountl(x);
}
inline int popcount(unsigned long long x) noexcept {
    return __builtin_popcountll(x);
}
#elif defined(_MSC_VER) // MSVC
#include <intrin.h>
__forceinline int popcount(std::uint8_t x) noexcept {
    return static_cast<int>(__popcnt16(x));
}
__forceinline int popcount(std::uint16_t x) noexcept {
    return static_cast<int>(__popcnt16(x));
}
__forceinline int popcount(std::uint32_t x) noexcept {
    return static_cast<int>(__popcnt(x));
}
__forceinline int popcount(std::uint64_t x) noexcept {
    return static_cast<int>(__popcnt64(x));
}
#endif
Run Code Online (Sandbox Code Playgroud)

请参阅编译器资源管理器中的实时示例

使用这个包装器(取自bitmanip),您可以popcount安全地调用 GCC、MSVC 和 clang。Android 原生编译器基于 LLVM,因此它也应该可以工作。

创建一个包装器非常容易,例如:

int popcount_256(const std::uint64_t nums[4]) {
    int sum = 0;
    for (std::size_t i = 0; i < 4; ++i) {
        sum += popcount(nums[i]);
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

显然,这也可能是您的ORB_view. 尽管 clang 会自动向量化此代码,但没有显式的 SIMD 代码。