Reputation: 667
Doing a comparison in C++ with an int is x >= 0
more efficient than x > -1
?
Upvotes: 6
Views: 780
Reputation: 145269
Remember: measure. And don't optimize prematurely!
There is no alternative to measuring.
You need to keep in mind that what you're measuring is just that: that a single set of measurements does not necessarily tell you anything in general, but just a specific result. Of course it might sound patronizing to mention such obvious things. So, OK, let that be it: just measure.
Or, should I perhaps mention that most processors have a special instruction for comparing against zero, and yet that that does not allow one to conclude anything about performance of your code snippets?
Donald Knuth, author of the classic three volume work The Art of Computer Programming, once wrote[1],
“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil”
How efficient x >= 0
is compared to x > -1
is most often irrelevant. I.e. it’s most likely a wrong thing to focus on.
How clearly it expresses what you want to say, is much more important. Your time and the time of others maintaining this code is generally much more important than the execution time of the program. Focus on how well the code communicates to other programmers, i.e., focus on clarity.
Clarity affects the chance of correctness. Any code can be made arbitrarily fast if it does not need to be correct. Correctness is therefore most important, and means that clarity is very important – much more important than shaving a nano-second of execution time…
And the two expressions are not equivalent wrt. clarity, and wrt. to their chance of being correct.
If x
is a signed integer, then x >= 0
means exactly the same as x > -1
. But if x
is an unsigned integer, e.g. of type unsigned
, then x > -1
means x > static_cast<unsigned>(-1)
(via implicit promotion), which in turn means x > std::numeric_limits<unsigned>::max()
. Which is presumably not what the programmer meant to express!
Another reason why the focus is wrong (it’s on micro-efficiency, while it should be on clarity) is that the main impact on efficiency comes in general not from timings of individual operations (except in some cases from dynamic allocation and from the even slower disk and network operations), but from algorithmic efficiency. For example, writing …
string s = "";
for( int i = 0; i < n; ++i ) { s = s + "-"; }
is pretty inefficient, because it uses time proportional to the square of n, O(n2), quadratic time.
But writing instead …
string s = "";
for( int i = 0; i < n; ++i ) { s += "-"; }
reduces the time to proportional to n, O(n), linear time.
With the focus on individual operation timings one could be thinking now about writing '-'
instead of "-"
, and such silly details. Instead, with the focus on clarity, one would be focusing on making that code more clear than with a loop. E.g. by using the appropriate string
constructor:
string s( n, '-' );
Wow!
Finally, a third reason to not sweat the small stuff is that in general it’s just a very small part of the code that contributes disproportionally to the execution time. And identifying that part (or parts) is not easy to do by just analyzing the code. Measurements are needed, and this kind of "where is it spending its time" measurement is called profiling.
Twenty or thirty years ago one could get a reasonable idea of efficiency of individual operations, by simply looking at the generated machine code.
For example, you can look at the machine code by running the program in a debugger, or you use the approiate option to ask the compiler to generate an assembly language listing. Note for g++: the option -masm=intel
is handy for telling the compiler not to generate ungrokkable AT&T syntax assembly, but instead Intel syntax assembly. E.g., Microsoft's assembler uses extended Intel syntax.
Today the computer's processor is more smart. It can execute instructions out of order and even before their effect is needed for the "current" point of execution. The compiler may be able to predict that (by incorporating effective knowledge gleaned from measurements), but a human has little chance.
The only recourse for the ordinary programmer is therefore to measure.
Measure, measure, measure!
And in general this involves doing the thing to be measured, a zillion times, and dividing by a zillion.
Otherwise the startup time and take-down time will dominate, and the result will be garbage.
Of course, if the generated machine code is the same, then measuring will not tell you anything useful about the relative difference. It can then only indicate something about how large the measurement error is. Because you know then that there should be zero difference.
Let’s say that theoretical considerations in an SO answer indicated that x >= -1
will be slower than x > 0
.
The compiler can beat any such theoretical consideration by generating awful code for that x > 0
, perhaps due to a contextual "optimization" opportunity that it then (unfortunately!) recognizes.
The computer's processor can likewise make a mess out of the prediction.
So in any case you then have to measure.
Which means that the theoretical consideration has told you nothing useful: you’ll be doing the same anyway, namely, measuring.
References:
[1] Knuth, Donald. Structured Programming with go to Statements, ACM Journal Computing Surveys, Vol 6, No. 4, Dec. 1974. p.268.
Upvotes: 6
Reputation: 6117
short answer: no.
longer answer to provide some educational insight: it depends entirely on your compiler, allthough i bet that every sane compiler creates identical code for the 2 expressions.
example code:
int func_ge0(int a) {
return a >= 0;
}
int func_gtm1(int a) {
return a > -1;
}
and then compile and compare the resulting assembler code:
% gcc -S -O2 -fomit-frame-pointer foo.cc
yields this:
_Z8func_ge0i: .LFB0: .cfi_startproc .cfi_personality 0x0,__gxx_personality_v0 movl 4(%esp), %eax notl %eax shrl $31, %eax ret .cfi_endproc
vs.
_Z9func_gtm1i: .LFB1: .cfi_startproc .cfi_personality 0x0,__gxx_personality_v0 movl 4(%esp), %eax notl %eax shrl $31, %eax ret .cfi_endproc
(compiler: g++-4.4)
conclusion: don't try to outsmart the compiler, concentrate on algorithms and data structures, benchmark and profile real bottlenecks, if in doubt: check the output of the compiler.
Upvotes: 20
Reputation: 156434
I doubt there is any measurable difference. The compiler should emit some assembled code with a jump instruction such as JAE
(jump if above or equal) or JA
(jump if above). These instructions likely span the same number of cycles.
Ultimately, it doesn't matter. Just use what is more clear to a human reader of your code.
Upvotes: 1
Reputation: 54242
Your compiler is free to decide how to implement those (which assembly instructions to use). Because of that, there is no difference. One compiler could implement x > -1
as x >= 0
and another could implement x >= 0
as x > -1
. If there is any difference (unlikely), your compiler will pick the better one.
Upvotes: 3
Reputation: 18056
They should be equivalent. Both will be translated by the compiler into a single assembly instruction (neglecting that both will need to load x into a register). On any modern day processor there is a 'greater-than' instruction and a 'greater-than-or-equal' instruction. And since you are comparing it to a constant value, it will take the same amount of time.
Don't fret over the minute details, find the big performance problems (like algorithm design) and attack those, look at Amdahls Law.
Upvotes: 2
Reputation: 20272
You can look at the resulting assembly code, which may differ from architecture to architecture, but I would bet that the resulting code for either would require exactly the same cycles.
And, as mentioned in the comments - better write what's most comprehensible, optimize when you have real measured bottlenecks, which you can identify with a profiler.
BTW: Rightly mentioned, that x>-1
may cause problems if x
is unsigned
. It may be implicitly cast into signed
(although you should get a warning on that), which would yield incorrect result.
Upvotes: 7