假设我给你一组参考字符串(“ftp”、“file”、“http”、“https”、“ws”、“wss”)。给定一个新字符串,您想快速判断它是否是该集合的一部分。
一个明智的解决方案可能是创建一个集合,然后询问字符串是否在集合中。在 C++ 中,默认的集合类型是 unordered_set 因此您的代码可能如下所示:
static const std :: unordered_set < std :: string_view > special_set = { “ ftp ” 、 “文件” 、 “ http ” 、 “ https ” 、 “ ws ” 、 “ wss ” } ; bool hash_is_special ( std :: string_view 输入) { 返回special_set 。查找(输入) ! = special_set 。结束( ) ; }
你也可以更直接一点,做几个比较就可以了:
bool direct_is_special ( std :: string_view 输入) { 返回(输入== “ https ” ) | (输入== “ http ” ) | (输入== “ ftp ” ) | (输入== “文件” ) | (输入== “ ws ” ) | (输入== “ wss ” ) ; }
如果您查看代码的编译方式,您可能会注意到编译器被迫进行比较和跳转,因为它不允许读取超出其报告大小的提供的字符串。
如果您可以告诉您的编译器您收到的字符串是“填充的”以便您可以从中安全地读取八个字节,那么您可能会做得更好。我找不到一种非常优雅的方法来做到这一点,但以下代码有效:
静态内联uint64_t string_to_uint64 ( std :: string_view view ) { uint64_t 值; std :: memcpy ( & val , view.data ( ) , sizeof ( uint64_t ) ) ; 返回值; } uint32_t string_to_uint32 ( const char * data ) { uint32_t 值; std :: memcpy ( & val ,数据, sizeof ( uint32_t ) ) ; 返回值; } bool fast_is_special ( std :: string_view 输入) { uint64_t inputu = string_to_uint64 (输入) ; 如果( ( inputu & 0xffffffffff ) = = string_to_uint64 ( " https \0 \0 \0 " ) ) { 返回输入。尺寸( ) = = 5 ; } 如果( ( inputu & 0xffffffff ) = = string_to_uint64 ( " http \0 \0 \0 \0 " ) ) { 返回输入。尺寸( ) = = 4 ; } 如果( uint32_t ( inputu ) == string_to_uint32 ( “文件” ) ) { 返回输入。尺寸( ) = = 4 ; } 如果( ( inputu & 0xffffff ) = = string_to_uint32 ( " ftp \0 " ) ) { 返回输入。尺寸( ) = = 3 ; } 如果( ( inputu & 0xffffff ) = = string_to_uint32 ( " wss \0 " ) ) { 返回输入。尺寸( ) = = 3 ; } 如果( ( inputu & 0xffff ) = = string_to_uint32 ( " ws \0 \0 " ) ) { 返回输入。尺寸( ) = = 2 ; } 返回假; }
虽然我没有这样做,但您可以扩展比较,使其不区分大小写(只需将输入与字节 0xdf 而不是字节 0xff 进行 AND 运算)。
我相信有更快更聪明的选择!
无论如何,我的选择有多快?在 Intel Ice Lake 服务器上使用 GCC 11,我得到以下结果:
std::unordered_map | 20 纳秒/串 |
直接的 | 9.1 纳秒/串 |
快速地 | 3.0 纳秒/串 |
在带有 LLVM 12 的 Apple M2 上,我得到了类似(但更好)的结果:
std::unordered_map | 14 纳秒/串 |
直接的 | 5.5 纳秒/串 |
快速地 | 1.6 纳秒/串 |
优化此类小函数时需要小心:函数是否内联以及如何内联对于良好的性能至关重要。
原文: https://lemire.me/blog/2022/12/30/quickly-checking-that-a-string-belongs-to-a-small-set/