1

Back in the day when I was learning C and assembly we were taught it is better to use simple comparisons to increase speed. So for example if you say:

if(x <= 0)

versus

if(x < 1)

which would execute faster? My argument (which may be wrong) is the second would almost always execute faster because there is only a single comparison) i.e. is it less than one, yes or no.

Whereas the first will execute fast if the number is less than 0 because this equates to true there is no need to check the equals making it as fast as the second, however, it will always be slower if the number is 0 or more because it has to then do a second comparison to see if it is equal to 0.

I am now using C# and while developing for desktops speed is not an issue (at least not to the degree that his point is worth arguing), I still think such arguments need to be considered as I am also developing for mobile devices which are much less powerful than desktops and speed does become an issue on such devices.

For further consideration, I am talking about whole numbers (no decimals) and numbers where there cannot be a negative number like -1 or -12,345 etc (unless there is an error), for example, when dealing with lists or arrays when you cant have a negative number of items but you want to check if a list is empty (or if there is a problem, set the value of x to negative to indicate error, an example is where there are some items in a list, but you cannot retrieve the whole list for some reason and to indicate this you set the number to negative which would not be the same as saying there are no items).

For the reason above I deliberately left out the obvious

if(x == 0)

and

if(x.isnullorempty())

and other such items for detecting a list with no items.

Again, for consideration, we are talking about the possibility of retrieving items from a database perhaps using SQL stored procedures which have the functionality mentioned (ie the standard (at least in this company) is to return a negative number to indicate a problem).

So in such cases, is it better to use the first or the second item above?

nawfal
  • 70,104
  • 56
  • 326
  • 368
Francis Rodgers
  • 4,565
  • 8
  • 46
  • 65
  • 1
    Did you try it? Your first two examples probably both compile to a single instruction/flag test. A lot of architectures have more than one compare instruction variety. – Carl Norum Jan 17 '13 at 17:11
  • The first two examples you've given are vastly different and will get you different results, and are not suitable examples for your question. – PiousVenom Jan 17 '13 at 17:13
  • I believe that it's better to use the comparison form which makes more sense to the human reader. Saving one computer instruction is never worth a human misunderstanding. – Gilbert Le Blanc Jan 17 '13 at 17:14
  • It is posible that comparing with 0 at machine code level is even faster ?? (trying to remember my time with 8086 machine code learning) – qPCR4vir Jan 17 '13 at 17:16
  • If people are going to negate my question, at least have the courtesy to explain why in a comment. It is costing me points and this is unfair to do without explanation. – Francis Rodgers Jan 17 '13 at 17:56
  • Rodgers, I did my best trying to help, see the answer – qPCR4vir Jan 18 '13 at 11:13
  • @Francis Rodgers, I think you can use a more general tag, like optimization or so. – qPCR4vir Jan 18 '13 at 11:46
  • Hi. Sorry to not have accepted an answer. However, I was not convinced that any of the answers are correct or at least not explained in a way I found easy to understand if they are correct, and it would be remiss of me to accept an answer until I was sure it was correct. So I decided to wait a while and see how the community voted. I'll come back down the road and read the answers again but my time is quite limited right now. Thanks everyone for your answers so far. – Francis Rodgers Jul 27 '14 at 16:14

9 Answers9

20

They're identical. Neither is faster than the other. They both ask precisely the same question, assuming x is an integer. C# is not assembly. You're asking the compiler to generate the best code to get the effect you are asking for. You aren't specifying how it gets that result.

See also this answer.

My argument (which may be wrong) is the second would almost always execute faster because there is only a single comparison) i.e. is it less than one, yes or no.

Clearly that's wrong. Watch what happens if you assume that's true:

< is faster than <= because it asks fewer questions. (Your argument.)

> is the same speed as <= because it asks the same question, just with an inverted answer.

Thus < is faster than >! But this same argument shows > is faster than <.

"just with an inverted answer" seems to sneak in an additional boolean operation so I'm not sure I follow this answer.

That's wrong (for silicon, it is sometimes correct for software) for the same reason. Consider:

3 != 4 is more expensive to compute than 3 == 4, because it's 3 != 4 with an inverted answer, an additional boolean operation.

3 == 4 is more expensive than 3 != 4, because it's 3 != 4 with an inverted answer, an additional boolean operation.

Thus, 3 != 4 is more expensive than itself.

An inverted answer is just the opposite question, not an additional boolean operation. Or, to be a bit more precise, it's with a different mapping of comparison results to final answer. Both 3 == 4 and 3 != 4 require you to compare 3 and 4. That comparison results in ether "equal" or "unequal". The questions just map "equal" and "unequal" to "true" and "false" differently. Neither mapping is more expensive than the other.

Community
  • 1
  • 1
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 2
    +1 nice discussion on `<` and `>` are faster than each other :). – Alexei Levenkov Jan 17 '13 at 17:21
  • Can you explain where > is the same as <= because this doesn't make sense in my logical world. For example, <=0 is not the same as >0 in fact, totally opposite. I would just like an example so I can understand your answer better. Thanks for your answer. – Francis Rodgers Jan 17 '13 at 17:52
  • "just with an inverted answer" seems to sneak in an additional boolean operation so I'm not sure I follow this answer. – Mike Zboray Jan 17 '13 at 18:00
  • @FrancisRodgers `<= 0` is the same as `> 0` because both involve a comparison that produces an answer of either "less than or equal to zero" or "not less than or equal to zero". They just map the answer to "true" and "false" differently. See updates. – David Schwartz Jan 17 '13 at 18:42
  • There might be a minor argument in favor of `<= 0` (or maybe the other, depending on the requirements of the application) in the case where `x` might someday be changed to a data type other than an integer, in which case the two operations would no longer be equivalent. Probably rare, but still worth thinking about. – Jeremy Todd Jan 17 '13 at 18:57
  • Rodgers, I did my best trying to help, see the answer – qPCR4vir Jan 18 '13 at 11:12
6

At least in most cases, no, there's no advantage to one over the other.

A <= does not normally get implemented as two separate comparisons. On a typical (e.g., x86) CPU, you'll have two separate flags, one to indicate equality, and one to indicate negative (which can also mean "less than"). Along with that, you'll have branches that depend on a combination of those flags, so < translates to a jl or jb (jump if less or jump if below --the former is for signed numbers, the latter for unsigned). A <= will translate to a jle or jbe (jump if less than or equal, jump if below or equal).

Different CPUs will use different names/mnemonics for the instructions, but most still have equivalent instructions. In every case of which I'm aware, all of those execute at the same speed.

Edit: Oops -- I meant to mention one possible exception to the general rule I mentioned above. Although it's not exactly from < vs. <=, if/when you can compare to 0 instead of any other number, you can sometimes gain a little (minuscule) advantage. For example, let's assume you had a variable you were going to count down until you reached some minimum. In a case like this, you might well gain a little advantage if you can count down to 0 instead of counting down to 1. The reason is fairly simple: the flags I mentioned previously are affected by most instructions. Let's assume you had something like:

   do {
       // whatever
   } while (--i >= 1);

A compiler might translate this to something like:

loop_top:
    ; whatever

    dec i
    cmp i, 1
    jge loop_top

If, instead, you compare to 0 (while (--i > 0) or while (--i != 0)), it might translate to something like this instead;

loop_top:
    ; whatever

    dec i
    jg loop_top
    ; or: jnz loop_top

Here the dec sets/clears the zero flag to indicate whether the result of the decrement was zero or not, so the condition can be based directly on the result from the dec, eliminating the cmp used in the other code.

I should add, however, that while this was quite effective, say, 30+ years ago, most modern compilers can handle translations like this without your help (though some compilers may not, especially for things like small embedded systems). IOW, if you care about optimization in general, it's barely possible that you might someday care -- but at least to me, application to C# seems doubtful at best.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
5

Most modern hardware has built-in instructions for checking the less-than-or-equals consition in a single instruction that executes exactly as fast as the one checking the less-than condition. The argument that applied to the (much) older hardware no longer applies - choose the alternative that you think is most readable, i.e. the one that better conveys your idea to the readers of your code.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
4

Here are my functions:

public static void TestOne()
{
    Boolean result;
    Int32 i = 2;

    for (Int32 j = 0; j < 1000000000; ++j)
        result = (i < 1);
}

public static void TestTwo()
{
    Boolean result;
    Int32 i = 2;

    for (Int32 j = 0; j < 1000000000; ++j)
        result = (i <= 0);
}

Here is the IL code, which is identical:

L_0000: ldc.i4.2 
L_0001: stloc.0 
L_0002: ldc.i4.0 
L_0003: stloc.1 
L_0004: br.s L_000a
L_0006: ldloc.1 
L_0007: ldc.i4.1 
L_0008: add 
L_0009: stloc.1 
L_000a: ldloc.1 
L_000b: ldc.i4 1000000000
L_0010: blt.s L_0006
L_0012: ret 

After a few testing sessions, obviously, the result is that neither is faster than the other. The difference consists only in few milliseconds which can't be considered a real difference, and the produced IL output is the same anyway.

Tommaso Belluzzo
  • 23,232
  • 8
  • 74
  • 98
  • I'm just starting with C#, hence the question. What tool did you use to get at the IL code. That would be very useful to me. :) Thanks for your answer. You put a lot of work into it. – Francis Rodgers Jan 17 '13 at 17:30
  • 1
    .NET Reflector, but you can also use Ildasm.exe which is automatically installed with Visual Studio. – Tommaso Belluzzo Jan 17 '13 at 17:33
  • +1 extra brownie points for you two! I need something like this so thanks again. – Francis Rodgers Jan 17 '13 at 17:47
  • I am debating another answerer who is telling me that <= is the same as >. Which perhaps at the assembly level they may be (I no longer know much assembly). Maybe I will use this tool to see if they are compiled to the same instructions. However, you do answer my point which was regarding the speed of one over the other, and then showed me they were both actually the same code. But this is because we are discussing (< vs <=) (LESS than vs LESS than or equal to). Not (<= vs >) (LESS than or equal to vs GREATER than), which to me are logically opposite. This is close to becoming the accepted ans – Francis Rodgers Jan 23 '13 at 10:33
3

Both ARM and x86 processors will have dedicated instructions both both "less than" and "less than or equal" (Which could also be evaluated as "NOT greater than"), so there will be absolutely no real world difference if you use any semi modern compiler.

shenles
  • 562
  • 5
  • 19
  • Is this definitely the case also with mobile processors (forgive my ignorance as I don't know a lot about which processors are used in smart phones (and a range is important, not just Iphone, but android and win phone too)). Could I be guaranteed that even the cheapest single core smart phone, would have this functionality built in. Thanks for your answer. – Francis Rodgers Jan 17 '13 at 17:22
  • 1
    Pretty much every modile CPU is going to be an ARM, while every desktop/laptop style will have an x86. Lately there is some crossover with Intel trying to scale the Atom line (x86) down to fit Modile needs, and the latest crop of ARMs (A9/A15) are starting to be used by previous x86 territory like the Google Chromebook and WindowsRT tablets. Even the lower end micros I've worked with (8051, HCS08, Coldfire, ARM Cortex-M, MSP430) will have no difference in execution time for this case. – shenles Jan 18 '13 at 19:19
1

While refactoring, if you change your mind about the logic, if(x<=0) is faster (and less error prone) to negate (ie if(!(x<=0)), compared to if(!(x<1)) which does not negate correctly) but that's probably not the performance you're referring to. ;-)

mcalex
  • 6,628
  • 5
  • 50
  • 80
  • can you explain what you mean with negate. Is it to negate the number X or 0, or to negate the comparison turning true to false or vice versa. its not clear. Thanks for your answer. – Francis Rodgers Jan 17 '13 at 17:41
  • Your right, its not the performance I was referring to, however, your answer is definitely (in my opinion) a very good example of why not to use the x<1 version. In my book, clarity over efficiency always comes first. Thanks for this answer +1. – Francis Rodgers Jan 17 '13 at 18:03
  • I'm not following. Are you trying to imply `!(x<1)` is not the negation of `x<1`? – jerry May 10 '13 at 04:41
0

IF x<1 is faster, then the modern compilers will change x<=0 to x<1 (assuming x is an integral). So for modern compilers, this should not matter, and they should produce identical machine code.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
0

Even if x<=0 compiled to different instructions than x<1, the performance difference would be so miniscule as to not be worth worrying about most of the time; there will very likely be other more productive areas for optimizations in your code. The golden rule is to profile your code and optimise the bits that ARE actually slow in the real world, not the bits that you think hypothetically may be slow, or are not as fast as they theoretically could be. Also concentrate on making your code readable to others, and not phantom micro-optimisations that disappear in a puff of compiler smoke.

Polyfun
  • 9,479
  • 4
  • 31
  • 39
  • I do agree with your point. I know even if such operations did make a difference (I am being convinced I am wrong about this), there would be much better ways to optimise your program. Also readability is important, and another answer explained the readability factor very well with regard to negating x<=1 so it looks like !(x<=1) which is very hard to read and most likely doesn't do what was intended. thanks for your answer. – Francis Rodgers Jan 23 '13 at 10:22
0

@Francis Rodgers, you said:

Whereas the first will execute fast if the number is less than 0 because this equates to true there is no need to check the equals making it as fast as the second, however, it will always be slower if the number is 0 or more because it has to then do a second comparison to see if it is equal to 0.

and (in commennts),

Can you explain where > is the same as <= because this doesn't make sense in my logical world. For example, <=0 is not the same as >0 in fact, totally opposite. I would just like an example so I can understand your answer better

You ask for help, and you need help. I really want to help you, and I’m afraid that many other people need this help too.

Begin with the more basic thing. Your idea that testing for > is not the same as testing for <= is logically wrong (not only in any programming language). Look at these diagrams, relax, and think about it. What happens if you know that X <= Y in A and in B? What happen if you know that X > Y in each diagram? enter image description here Right, nothing changed, they are equivalent. The key detail of the diagrams is that true and false in A and B are on opposite sides. The meaning of that is that the compiler (or in general – de coder) has the freedom to reorganize the program flow in a way that both question are equivalent.That means, there is no need to split <= into two steps, only reorganize a little in your flow. Only a very bad compiler or interpreter will not be able to do that. Nothing to do yet with any assembler. The idea is that even for CPUs without sufficient flags for all comparisons the compiler can generate (pseudo) assembler code with use the test with best suit it characteristics. But adding the ability of the CPUs to check more than one flag in parallel at electronic level, the job of the compiler is very much simpler. You may find curios/interesting to read the pages 3-14 to 3-15 and 5-3 to 5-5 (the last include the jump instructions with could be surprising for you) http://download.intel.com/products/processor/manual/325462.pdf

Anyway, I’d like to discuss more about related situations.

Comparing with 0 or with 1: @Jerry Coffin has a very good explanation at assembler level. Going deeply at machine code level the variant to compare with 1 needs to “hard code” the 1 into the CPU instruction and load it into the CPU, while the other variant managed to not do that. Anyway here the gain is absolutely small. I don’t think it will be measurable in speed in any real live situation. As a side comment, the instruction cmp i, 1 will made just a sort of subtractioni-1 (without saving the result) but setting the flags, and you end up comparing actually with 0 !!

More important could be this situation: compare X<=Y or Y>=X with obviously are logicaly equivalent, but that could have severe side effect if X and Y are expression with need to be evaluated and could influence the result of the other! Very bad still, and potentially undefined.

Now, coming back to the diagrams, looking at the assembler examples from @Jerry Coffin too. I see here the following issue. Real software is a sort of linear chain in memory. You select one of the conditions and jump into another program-memory position to continue while the opposite just continues. It could make sense to select the more frequent condition to be the one that just continues. I don’t see how we can give the compiler a hint in these situations, and obviously the complier can’t figure it out itself. Please, correct me if I’m wrong, but these sort of optimization problems are pretty much general, and the programmer must decide himself without the help of the complier.

But again, in fast any situation I’ll write my code looking at the general still and readability and not at these local small optimizations.

qPCR4vir
  • 3,521
  • 1
  • 22
  • 32
  • +1 for the explanation and time. Thanks for the answer. I have to think about this answer a lot before I can agree it is right. In the meantime, I will edit the English for you as thanks. – Francis Rodgers Jan 22 '13 at 13:55
  • This is spread across 2 comments, apologies for the length. Part 1: Given you have taken the time to write a detailed answer, I've actually taken the time to try this out in Excel. With the results I expect: Q1. if x = 2 and Y = 1 then A does ins2 while B does ins1. Q2. if x = 1 and y = 2, then A does ins1 while B does ins2. Therefore they are not the same, they are opposite. So I disagree with your first point. However, thanks for your detailed answer. Perhaps I am missing something. – Francis Rodgers Jan 22 '13 at 15:40
  • Part 2: Given I disagree with the first point, I found it difficult to believe the other points as they require me to believe the first point in order to accept the others. Again, your third point i very much disagree with (X<=1 is the same as X>=Y) again out of respect, I tried it. I don't understand the last sentence, so I am unable to correct it. While I do appreciate your time and effort in the answering of the question. I disagree with you. But thanks for your answer anyway. – Francis Rodgers Jan 22 '13 at 15:40
  • First, I apologize if my English sound not very polite, that is not my intention. And yes, you are missing the key detail of the diagrams: true and false in A and B are on opposite sides. The meaning of that is that the compiler (or in general – de coder) has the freedom to reorganize the program flow in a way that both question are equivalent. – qPCR4vir Jan 22 '13 at 20:59
  • The idea is that even for CPUs without sufficient flags for all comparisons the compiler can generate (pseudo) assembler code with use the test with best suit it characteristics. But adding the ability of the CPUs to check more than one flag in parallel at electronic level, the job of the compiler is very much simpler. – qPCR4vir Jan 22 '13 at 21:26
  • You may find curios/interesting to read the pages 3-14 to 3-15 and 5-3 to 5-5 (the last include the jump instructions with could be surprising for you) http://download.intel.com/products/processor/manual/325462.pdf – qPCR4vir Jan 22 '13 at 21:41
  • Perhaps we are agreeing without knowing it. My question mentions assembly in passing which may introduce a level of detail I did not intend. At the assembly instruction level, or at the compiler optimisation level, these two might get "translated" to the same instruction, on this we can agree (my assembly language is long lost so to argue this point would be ignorance on my part). However, I was talking about the question from a high level view where logically <= is opposite to >. i.e. If you change nothing but the <= to > in C# then the else part will be executed instead of the if. Thanks. – Francis Rodgers Jan 23 '13 at 10:16