我正在尝试为 ORB 功能实现 C++17 可移植且高效的汉明距离函数,希望在为 x86 和 ARM 编译时自动使用 SIMD。
std::bitset
std::bitset<N>
提供了按位运算和计数的标准方式,并且它也优于__builtin_popcount
。然而,它是一种拥有数据的容器类型,并且不容易从存储为 cv::Mat
(由 cv::ORB::detectAndCompute
计算)的 256 位向量转换。
此线程要求将
cv::Mat
转换为 std::bitset<256>
。我不认为 memcpy
的答案是正确的,因为我在 https://en.cppreference.com/w/cpp/utility/bitset中没有找到
std::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;
};
但是,这不能直接使用按位运算和 popcount,并且会导致在实现中出现显式的 SIMD 指令,我希望通过使用
std::bitset<N>::count()
来避免这种情况。
因此,在这种情况下,提供按位运算和计数的非拥有引用将非常有帮助。所需的功能是:
std::bitset
据我所知标准库没有提供。
首先,如果性能很重要,则在大多数情况下您应该远离
std::bitset
。它会在无效输入上引发异常,并且这些运行时检查对于高性能应用程序来说不是最佳选择。
std::popcount
。在 C++17 之前,您可以使用 __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
请参阅编译器资源管理器中的实时示例。
使用此包装器(取自
bitmanip
库),您可以为 GCC、MSVC 和 clang 安全地调用 popcount
。 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;
}
显然,这也可能是您的
ORB_view
的成员函数。尽管 clang 自动向量化了此代码,但没有显式的 SIMD 代码。