{"id":22386,"date":"2025-12-20T23:26:09","date_gmt":"2025-12-20T23:26:09","guid":{"rendered":"https:\/\/lemire.me\/blog\/?p=22386"},"modified":"2025-12-20T23:29:30","modified_gmt":"2025-12-20T23:29:30","slug":"performance-trick-optimistic-vs-pessimistic-checks","status":"publish","type":"post","link":"https:\/\/lemire.me\/blog\/2025\/12\/20\/performance-trick-optimistic-vs-pessimistic-checks\/","title":{"rendered":"Performance trick : optimistic vs pessimistic checks"},"content":{"rendered":"<p>Strings in programming are often represented as arrays of 8-bit words. The string is ASCII if and only if all 8-bit words have their most significant bit unset. In other words, the byte values must be no larger than 127 (or <code>0x7F<\/code> in hexadecimal).<\/p>\n<p>A decent C function to check that the string is ASCII is as follows.<\/p>\n<div class=\"highlight\" style=\"background: #f8f8f8;\">\n<pre style=\"line-height: 125%;\"><code><span style=\"color: #204a87; font-weight: bold;\">bool<\/span> <span style=\"color: #000;\">is_ascii_pessimistic<\/span><span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #204a87; font-weight: bold;\">const<\/span> <span style=\"color: #204a87; font-weight: bold;\">char<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">*<\/span><span style=\"color: #000;\">data<\/span><span style=\"color: #000; font-weight: bold;\">,<\/span> <span style=\"color: #204a87; font-weight: bold;\">size_t<\/span> <span style=\"color: #000;\">length<\/span><span style=\"color: #000; font-weight: bold;\">)<\/span> <span style=\"color: #000; font-weight: bold;\">{<\/span>\r\n  <span style=\"color: #204a87; font-weight: bold;\">for<\/span> <span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #204a87; font-weight: bold;\">size_t<\/span> <span style=\"color: #000;\">i<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">=<\/span> <span style=\"color: #0000cf; font-weight: bold;\">0<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span> <span style=\"color: #000;\">i<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">&lt;<\/span> <span style=\"color: #000;\">length<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span> <span style=\"color: #000;\">i<\/span><span style=\"color: #ce5c00; font-weight: bold;\">++<\/span><span style=\"color: #000; font-weight: bold;\">)<\/span> <span style=\"color: #000; font-weight: bold;\">{<\/span>\r\n    <span style=\"color: #204a87; font-weight: bold;\">if<\/span> <span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #000;\">static_cast<\/span><span style=\"color: #ce5c00; font-weight: bold;\">&lt;<\/span><span style=\"color: #204a87; font-weight: bold;\">unsigned<\/span> <span style=\"color: #204a87; font-weight: bold;\">char<\/span><span style=\"color: #ce5c00; font-weight: bold;\">&gt;<\/span><span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #000;\">data<\/span><span style=\"color: #000; font-weight: bold;\">[<\/span><span style=\"color: #000;\">i<\/span><span style=\"color: #000; font-weight: bold;\">])<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">&gt;<\/span> <span style=\"color: #0000cf; font-weight: bold;\">0x7F<\/span><span style=\"color: #000; font-weight: bold;\">)<\/span> <span style=\"color: #000; font-weight: bold;\">{<\/span>\r\n      <span style=\"color: #204a87; font-weight: bold;\">return<\/span> <span style=\"color: #204a87;\">false<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span>\r\n    <span style=\"color: #000; font-weight: bold;\">}<\/span>\r\n  <span style=\"color: #000; font-weight: bold;\">}<\/span>\r\n  <span style=\"color: #204a87; font-weight: bold;\">return<\/span> <span style=\"color: #204a87;\">true<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span>\r\n<span style=\"color: #000; font-weight: bold;\">}<\/span>\r\n<\/code><\/pre>\n<\/div>\n<p>We go over each character, we compare it with <code>0x7F<\/code> and continue if the value is no larger than <code>0x7F<\/code>. If you have scanned the entire string and all tests have passed, you know that your string is ASCII.<\/p>\n<p>Notice how I called this function <code>pessimistic<\/code>. What do I mean? I mean that it expects, in some sense, that it will find some non-ASCII character. If so, the best option is to immediately return and not scan the whole string.<\/p>\n<p>What if you expect the string to almost always be ASCII? An alternative then is to effectively do a bitwise OR reduction of the string: you OR all characters together and you check just once that the result is bounded by <code>0x7F<\/code>. If any character has its most significant bit set, then the bitwise OR of all characters will also have its most significant bit set. So you might write your function as follows.<\/p>\n<div class=\"highlight\" style=\"background: #f8f8f8;\">\n<pre style=\"line-height: 125%;\"><code><span style=\"color: #204a87; font-weight: bold;\">bool<\/span> <span style=\"color: #000;\">is_ascii_optimistic<\/span><span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #204a87; font-weight: bold;\">const<\/span> <span style=\"color: #204a87; font-weight: bold;\">char<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">*<\/span><span style=\"color: #000;\">data<\/span><span style=\"color: #000; font-weight: bold;\">,<\/span> <span style=\"color: #204a87; font-weight: bold;\">size_t<\/span> <span style=\"color: #000;\">length<\/span><span style=\"color: #000; font-weight: bold;\">)<\/span> <span style=\"color: #000; font-weight: bold;\">{<\/span>\r\n  <span style=\"color: #204a87; font-weight: bold;\">unsigned<\/span> <span style=\"color: #204a87; font-weight: bold;\">char<\/span> <span style=\"color: #000;\">result<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">=<\/span> <span style=\"color: #0000cf; font-weight: bold;\">0<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span>\r\n  <span style=\"color: #204a87; font-weight: bold;\">for<\/span> <span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #204a87; font-weight: bold;\">size_t<\/span> <span style=\"color: #000;\">i<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">=<\/span> <span style=\"color: #0000cf; font-weight: bold;\">0<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span> <span style=\"color: #000;\">i<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">&lt;<\/span> <span style=\"color: #000;\">length<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span> <span style=\"color: #000;\">i<\/span><span style=\"color: #ce5c00; font-weight: bold;\">++<\/span><span style=\"color: #000; font-weight: bold;\">)<\/span> <span style=\"color: #000; font-weight: bold;\">{<\/span>\r\n    <span style=\"color: #000;\">result<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">|=<\/span> <span style=\"color: #000;\">static_cast<\/span><span style=\"color: #ce5c00; font-weight: bold;\">&lt;<\/span><span style=\"color: #204a87; font-weight: bold;\">unsigned<\/span> <span style=\"color: #204a87; font-weight: bold;\">char<\/span><span style=\"color: #ce5c00; font-weight: bold;\">&gt;<\/span><span style=\"color: #000; font-weight: bold;\">(<\/span><span style=\"color: #000;\">data<\/span><span style=\"color: #000; font-weight: bold;\">[<\/span><span style=\"color: #000;\">i<\/span><span style=\"color: #000; font-weight: bold;\">]);<\/span>\r\n  <span style=\"color: #000; font-weight: bold;\">}<\/span>\r\n  <span style=\"color: #204a87; font-weight: bold;\">return<\/span> <span style=\"color: #000;\">result<\/span> <span style=\"color: #ce5c00; font-weight: bold;\">&lt;=<\/span> <span style=\"color: #0000cf; font-weight: bold;\">0x7F<\/span><span style=\"color: #000; font-weight: bold;\">;<\/span>\r\n<span style=\"color: #000; font-weight: bold;\">}<\/span>\r\n<\/code><\/pre>\n<\/div>\n<p>If you have strings that are all pure ASCII, which function will be fastest? Maybe surprisingly, the optimistic might be several times faster. <a href=\"https:\/\/github.com\/lemire\/Code-used-on-Daniel-Lemire-s-blog\/tree\/master\/2025\/12\/20\/optimistic\">I wrote a benchmark<\/a> and ran it with GCC 15 on an Intel Ice Lake processor. I get the following results.<\/p>\n<table>\n<thead>\n<tr>\n<th>function<\/th>\n<th>speed<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>pessimistic<\/td>\n<td>\u00a01.8 GB\/s<\/td>\n<\/tr>\n<tr>\n<td>optimistic<\/td>\n<td>\u00a013 GB\/s<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Why is the optimistic faster? Mostly because the compiler is better able to optimize it. Among other possibilities, it can use autovectorization to automatically use data-level parallelization (e.g., SIMD instructions).<\/p>\n<p>Which function is best depends on your use case.<\/p>\n<p>What if you would prefer a pessimistic function, that is, one that returns early when non-ASCII characters are encountered, but you still want high speed? Then you can use <a href=\"https:\/\/github.com\/simdutf\/simdutf\">a dedicated library like simdutf<\/a> where we have hand-coded the logic. In simdutf, the pessimistic function is called <code>validate_ascii_with_errors<\/code>. Your results will vary but I got that it has the same speed as optimistic function.<\/p>\n<table>\n<thead>\n<tr>\n<th>function<\/th>\n<th>speed<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>pessimistic<\/td>\n<td>\u00a01.8 GB\/s<\/td>\n<\/tr>\n<tr>\n<td>pessimistic (simdutf)<\/td>\n<td>\u00a014 GB\/s<\/td>\n<\/tr>\n<tr>\n<td>optimistic<\/td>\n<td>\u00a013 GB\/s<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>So it is possible to combine the benefits of pessimism and optimism although it requires a bit of care.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Strings in programming are often represented as arrays of 8-bit words. The string is ASCII if and only if all 8-bit words have their most significant bit unset. In other words, the byte values must be no larger than 127 (or 0x7F in hexadecimal). A decent C function to check that the string is ASCII &hellip; <a href=\"https:\/\/lemire.me\/blog\/2025\/12\/20\/performance-trick-optimistic-vs-pessimistic-checks\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Performance trick : optimistic vs pessimistic checks<\/span><\/a><\/p>\n","protected":false},"author":56,"featured_media":22387,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[84],"tags":[],"class_list":["post-22386","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-84"],"_links":{"self":[{"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/posts\/22386","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/users\/56"}],"replies":[{"embeddable":true,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/comments?post=22386"}],"version-history":[{"count":2,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/posts\/22386\/revisions"}],"predecessor-version":[{"id":22389,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/posts\/22386\/revisions\/22389"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/media\/22387"}],"wp:attachment":[{"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/media?parent=22386"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/categories?post=22386"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lemire.me\/blog\/wp-json\/wp\/v2\/tags?post=22386"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}