0
\$\begingroup\$

How would you rewrite this code which I have now:

uint32_t Utils::stringToInt(unsigned char const* buffer, int len)
{
    uint32_t result = buffer[0] - '0';
    for (int i = 1; i < len; i++) {
        result *= 10;
        result += buffer[i] - '0';
    }
    return result;
}

I need the lowest possible latency. It's very simple, and we don't need to care about '-', so I expect some faster implementation is possible.

\$\endgroup\$
1
\$\begingroup\$

Well, the largest number you can possibly yield for a uint32_t is 0xFFFFFFFF, or 4294967295. This has 10 digits. So let's just go with that:

uint32_t Utils::stringToInt(unsigned char const* buffer, int len)
{
    uint32_t res = 0;
    switch (len) {
    case 10: res = res * 10 + (*buffer++ - '0');
    case  9: res = res * 10 + (*buffer++ - '0');
    case  8: res = res * 10 + (*buffer++ - '0');
    case  7: res = res * 10 + (*buffer++ - '0');
    case  6: res = res * 10 + (*buffer++ - '0');
    case  5: res = res * 10 + (*buffer++ - '0');
    case  4: res = res * 10 + (*buffer++ - '0');
    case  3: res = res * 10 + (*buffer++ - '0');
    case  2: res = res * 10 + (*buffer++ - '0');
    case  1: res = res * 10 + (*buffer++ - '0');
    default: return res;
    }
}

Of course this is trading readability (your original code is very easy to understand, verify correctness of, etc.) for what is likely to be a very marginal gain in performance. But hey, sometimes that matters...

\$\endgroup\$
  • \$\begingroup\$ This seems like a variation of the "Duff's device". Might be worth adding the reference... \$\endgroup\$ – glampert Jul 14 '15 at 18:08
  • \$\begingroup\$ i don't understand how this should work. there are no loop at all, res is 0, so "res * 10" always 0 \$\endgroup\$ – javapowered Jul 15 '15 at 16:06
  • \$\begingroup\$ i'm afraid that on small numbers this may be slower than loop, because you have to "miss" a lot of cases. \$\endgroup\$ – javapowered Jul 15 '15 at 17:04
1
\$\begingroup\$

Be careful, this code assumes that there is at least one digit in the buffer. To make it work with empty buffer too:

uint32_t result = 0;
for (int i = 0; i < len; i++) {
    result *= 10;
    result += buffer[i] - '0';
}
return result;

In terms of performance, I don't think it's possible to achieve better using clean techniques. There are some optimizations that might help performance, but hurt readability and maintainability. For example you could try loop unrolling:

if (len == 0) return 0;

if (len == 1) {
    return buffer[0] - '0';
}

if (len == 2) {
    return (buffer[0] - '0') * 10 + buffer[1] - '0';
}

// ... and so on, until the max supported length

Yes it's ugly. But loop unrolling tends to improve performance. Tends, the result may depend on your compiler, platform, or the weather. You'll have to measure performance before and after to see if this optimization actually helps our not.

\$\endgroup\$
0
\$\begingroup\$

It's a minor thing but instead of using an index you can increment the pointer:

uint32_t Utils::stringToInt(unsigned char const* buffer, int len)
{
    uint32_t result = *buffer - '0';
    unsigned char* end = buffer+len;

    for (; buffer != end; buffer++) {
        result *= 10;
        result += *buffer - '0';
    }
    return result;
}

But this is something that you need to profile and benchmark.

There are other optimizations like doing multitple characters per iteration to help with pipelining.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.