Daniel Lemire's blog

, 8 min read

Quickly identifying a sequence of digits in a string of characters

10 thoughts on “Quickly identifying a sequence of digits in a string of characters”

  1. Sam Hardwick says:

    It would be interesting to know why there’s an almost factor of two difference between gcc and clang in the conventional case!

    1. Travis says:

      Loop unrolling. Newer gcc doesn’t unroll at all at -O2, but clang unrolls aggressively. The -O2 loop on gcc contains only a single check and is about half loop overhead. If you use -O3 they end up equivalent.

      This observation applies to other types of optimizations as well: -O2 in clang is in no way equivalent to -O2 in gcc. For example gcc never vectorizes at -O2 but clang vectorizes all the time, etc.

      The above doesn’t apply if you use PGO.

      Definitely makes it hard to do apples to apples comparison.

  2. Travis says:

    How about:

    ( val & (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030

    ?

    Cuts out two operations. It is usually faster than the existing branchless version, although nothing ever beat “branchy” for the compilers I tried.

    1. Blog post updated, you have been given credit for this better approach. It is not gigantically faster, but it does seem to help a bit.

      1. Travis says:

        It seems highly compiler dependent, but I saw speedups of between 1.0 (no speedup) and 1.3x or so (clang-5.0).

  3. Travis says:

    I posted another comment with a possibly improved “branchless” solution, but it never showed up.

    Anyways, is either test supposed to lead to unpredictable results? As far as I can tell the generatefloats and generatevarfloats routines both generate floats with random values but predictable length (16 vs 12) but don’t otherwise differ?

    The branch solution is favored here because the results follow a predictable pattern, and because the failure (the non-digit results) shortcut most of the work. If you create variable length floats, with this one line change:

    pos += sprintf((char *)stroutput + pos, "%.*f,", rand() % 20 + 1, output);

    Then branchy starts to lose.

    It is also possible that the compiler compiles branchy to branchless code: but all the ones I checked went half-way: they do the first check branchy and the second check branchless. Since the first check usually fails for any non-digit character (except for 6 characters right below 0), this works well in the predictable case!

  4. Travis says:

    Here’s a relevant link – the approach there is general, but the condition is slightly different (it looks for any byte in the range, rather than all bytes in the range, but you could transform one to the other in a straightforward way.

    The idea is more or less the same (exploit carries to do a specific range check), but a bit slower since it’s more general.

    The “Determine if a word has a byte greater than n” is interesting since its only three op, if you add one op to subtract ‘\0’ then it’s 4 ops, just either tried or one more than my suggestion above, depending on if you treat the implicit == 0 which is not counted on that page as an op or not. On x86 it is probably “not an op” since compare against zero is automatic, unlike comparison against other values: you just use the ZF after your last arithmetic op.

  5. I replied to this on Twitter, but for the record… I think there is a version of this that works with a add/compare on SIMD. Specifically one would add a magic number (70, IIRC) that pushes ‘0’ to 118 in the result and ‘9’ to 127.

    +127 is the maximal signed byte, so signed-gt comparison against 117 should do the trick.

    This doesn’t necessarily seem faster than the best SWAR variant but might be handy if you are looking for more than 8 chars.

  6. Amaury Séchet says:

    i was wondering if a similar approach could be used to validate hexadecimal strings. I tried today, but I’m afraid I failed. Any help, tips or trick appreciated!

    1. It seems more difficult to do it efficiently in portable C code. In our routine, we use the fact that the digits are consecutive (0x30 to 0x39) in ASCII. It is no longer true if you want to consider hexadecimal ‘digits’ which include a,b, A,B… etc. It suggests that the check for hexadecimal strings could be more expensive.