
在编程中,字符串通常表示为 8 位字数组。当且仅当所有 8 位字的最高有效位都为空时,该字符串才是 ASCII 字符串。换句话说,字节值不能大于 127(或十六进制的0x7F )。
下面是一个不错的 C 函数,用于检查字符串是否为 ASCII 字符串。
bool is_ascii_pessimistic ( const char * data , size_t length ) { for ( size_t i = 0 ; i < length ; i ++ ) { if ( static_cast < unsigned char > ( data [ i ]) > 0x7F ) { return false ; } } return true ; }我们逐个分析每个字符,并将其与……进行比较。bool is_ascii_pessimistic ( const char * data , size_t length ) { for ( size_t i = 0 ; i < length ; i ++ ) { if ( static_cast < unsigned char > ( data [ i ]) > 0x7F ) { return false ; } } return true ; }
如果该值不大于0x7F 0x7F则继续执行。如果您已扫描整个字符串且所有测试均通过,则说明该字符串为 ASCII 字符串。
注意我把这个函数命名为pessimistic 。这是什么意思呢?我的意思是,它在某种程度上预期会找到一些非ASCII字符。如果找到了,最好的做法是立即返回,而不是扫描整个字符串。
如果预期字符串几乎总是 ASCII 字符呢?一种替代方法是对字符串进行按位或运算:将所有字符按位或运算,然后检查一次结果是否在0x7F范围内。如果某个字符的最高有效位为 1,则所有字符按位或运算的结果的最高有效位也为 1。因此,您可以按如下方式编写函数。
bool is_ascii_optimistic ( const char * data , size_t length ) { unsigned char result = 0 ; for ( size_t i = 0 ; i < length ; i ++ ) { result |= static_cast < unsigned char > ( data [ i ]); } return result <= 0x7F ; }如果字符串全部都是纯 ASCII 字符,哪个函数速度最快?或许令人惊讶的是,乐观函数的速度可能会快好几倍。bool is_ascii_optimistic ( const char * data , size_t length ) { unsigned char result = 0 ; for ( size_t i = 0 ; i < length ; i ++ ) { result |= static_cast < unsigned char > ( data [ i ]); } return result <= 0x7F ; }
我编写了一个基准测试程序,并在英特尔 Ice Lake 处理器上使用 GCC 15 运行了该程序。我得到了以下结果。
| 功能 | 速度 |
|---|---|
| 悲观 | 1.8 GB/s |
| 乐观的 | 13 GB/s |
为什么乐观模式速度更快?主要是因为编译器能够更好地对其进行优化。例如,它可以利用自动向量化来自动使用数据级并行化(例如,SIMD 指令)。
哪个功能最好取决于您的使用场景。
如果您更倾向于使用悲观函数(即遇到非 ASCII 字符时就提前返回的函数),但仍然希望速度很快,该怎么办呢?那么您可以使用像 simdutf 这样的专用库,我们已经在其中手工编写了逻辑。在 simdutf 中,悲观函数名为validate_ascii_with_errors 。您的结果可能会有所不同,但我发现它的速度与乐观函数相同。
| 功能 | 速度 |
|---|---|
| 悲观 | 1.8 GB/s |
| 悲观的(simdutf) | 14 GB/s |
| 乐观的 | 13 GB/s |
因此,虽然需要一些谨慎,但还是有可能将悲观和乐观的优点结合起来的。
原文: https://lemire.me/blog/2025/12/20/performance-trick-optimistic-vs-pessimistic-checks/