You asked whether it can go faster. Interesting question. Sometimes you have to play around with it, and the obvious tweaks are not always the best ones.
The first thing I tested was compiler optimization. The difference between "gcc" and "gcc -O3" was huge:
A billion iterations:
- Short string (3 chars), yours: 5.45 sec
- Short string (3 chars), mine: 2.77 sec
- Medium string (racecar), yours: 9.45 sec
- Medium string (racecar), mine: 4.30 sec
- Long string (52 chars), yours: 66.94 sec
- Long string (52 chars), mine: 18.14 sec
Optimized with gcc -O3:
- Short string (3 chars), yours: 2.47 sec
- Short string (3 chars), mine: 0.000001 sec
- Medium string (racecar), yours: 3.04 sec
- Medium string (racecar), mine: 2.42 sec
- Long string (52 chars), yours: 16.28 sec
- Long string (52 chars), mine: 12.45 sec
You can see the optimizer drastically narrowed the gap! In general, if you write clean code and let the compiler do its work, you'll get pretty good numbers without the need to spend a lot of effort optimizing. Still, some optimization is possible.
I tested with a short string to emphasize function call overhead and algorithm setup; a medium string for the typical case; and a very long string (the alphabet forward and backward) to test the tight loop within the function.
Here is my tweaked version of palindrome:
bool palindrome(register char *text, int len) {
register char *right = text + len - 1;
while (text < right && *text == *right) {
text++;
right--;
}
return (text >= right);
}
As others have suggested, lifting the strlen out of the loop and just passing in the length yielded about a 15% speedup for me.
The register keyword tells the compiler to use a CPU register if possible instead of a memory variable. It roughly doubled the speed in the un-optimized case, but had no effect with optimization, probably because the compiler is already doing that anyway. Register will only help if you have very few variables (in this case two).
On the surface it looks equivalent: the same number of tests, the same number of variables. But it runs significantly faster.
I played with a lot of variations, but the goal was to eliminate as many tests as possible. Most of my "clever innovations" proved to slow it down, but one yielded surprising results: I found that the compare and increment construction *text++ == *right--
ran significantly slower than separating the increment and placing it in the loop body. I suspect this is because it gave the compiler more flexibility to optimize things. This seems to be the largest source of improvement outside of the register keyword, at least for very long palindromes. Short strings saw very little improvement. Again, write clear code and let the compiler do the work. But definitely experiment!
I am not sure why the short (3 char) string test takes almost no time with my version. Perhaps the optimizer is clever enough to pre-compute everything and optimize the entire test loop away. That is always a problem in this sort of simple loop test: you may not be testing what you think you are testing, and the compiler may toss large chunks of your code. It can be tricky to force it to do unnecessary work for the sake of generating a benchmark.
It would be fun to try this on a really long palindrome!
clock()
calls, since the loop has no real effect. (As far as C is concerned, execution time is not observable behavior.) \$\endgroup\$