A Software Engineering Paper WTF



  • Woo, first post as a registered user.  Anyway, here goes.



    I am reading articles for a research paper for my Software Engineering
    class.  This is an exact quote for increasing the performance of
    your programs, word for word, out of an article published in an actual
    IT journal (Information Technology and Management to be specific).



    "Use low-cost operators as much as you can.
     
    Like C++ and C, Java supports a set of shorthand arithmetic operations,
    such as ++, - [sic], +=, -=, *=, and /=.  Since they actually
    generate faster code, use them whenever possible."



    I have since lost all respect for anything this article says.



  • They generate faster code because it, um, types faster?



  • @dhromed said:

    They generate faster code because it, um, types faster?




    Didn't you know? That's like the first law of programming. Fewer Keystrokes = Faster Code.






  • @Mike R said:

    @dhromed said:
    They generate faster code because it, um, types faster?


    Didn't you know? That's like the first law of programming. Fewer Keystrokes = Faster Code.


    I never allow my variable names to exceed 4 characters. My code is blazing fast.



  • They do generate faster code.



    They do this  because binary operations create temporary objects, while += etc don't.



    Ie to demonstrate (primitives etc work the same way) here is a 3 dimensional vector += operation:



    vector3
                 
    &operator += (const vector3 &rhs) {

        x += rhs.x;

        y += rhs.y;

        z += rhs.z;

        return *this;

    }



    Now, + has to do exactly the same thing, but it has to create a new
    temporary object and return them, so that a = b + c + d + e; will work
    and not effect the original value.


    • is implemented using += but has the extra temp overhead.



      vector3 operator + (const vector3 &lhs, const vector3 &rhs) {

          vector3 tmp(a);

          tmp += b;

          return tmp;

      }




  • Goon, is there even a unit of time that can measure the difference in the two chunks of code you wrote? I'm not sure we can measure fractions of a second that small. But since your brought it up, we'll dive into it.

    If you are trying to add on to an existing object, any sane programmer worth his salt will do:

    myVector += anotherVector;

    It's cleaner, more readable code. The only case where I'd have to use the + on its own would be:

    someVector = myVector + anotherVector;

    I don't want to change the value of myVector, so I have to do it this way. I suppose you could type

    someVector += myVector;

    someVector += anotherVector;

    But I'd be afraid of being laughed out of the office for claiming that such code makes my program run faster. If this is the only way you can speed up your program, then either you have some great, efficient code, or you're really missing something.



  • @HiredGoon said:

    They do generate faster code.



    They do this  because binary operations create temporary objects, while += etc don't.


    Any halfway decent compiler will optimize x = x + y to produce the same code as x += y. And if you're not using a halfway decent compiler, you really shouldn't be complaining about your code being slow.



  • @Manni said:

    Goon, is there even a unit of time that can measure the difference in the two chunks of code you wrote? I'm not sure we can measure fractions of a second that small. But since your brought it up, we'll dive into it.

    If you are trying to add on to an existing object, any sane programmer worth his salt will do:

    myVector += anotherVector;

    It's cleaner, more readable code. The only case where I'd have to use the + on its own would be:

    someVector = myVector + anotherVector;

    I don't want to change the value of myVector, so I have to do it this way. I suppose you could type

    someVector += myVector;

    someVector += anotherVector;

    But I'd be afraid of being laughed out of the office for claiming that such code makes my program run faster. If this is the only way you can speed up your program, then either you have some great, efficient code, or you're really missing something.

     

    I agree with Manni on this one. += is an awesome operator, and whereever possible should definately be used - when reasonable within readability constraints.

    In my opinion, 'fewer keystrokes = quicker code' holds stead.

    even if its not quicker, its easier to comment... if its on one line, then its only one comment ;-)

    because we have all learnt that we must comment every line, didnt we??.....

    and more comments add more overheads... thereby defeating the purpose...

     

    (warning: this post contains heavy sarcasm)



  • >> Goon, is there even a unit of time that can measure the difference in the two chunks of code you wrote? I'm not sure we can measure fractions of a second that small. But since your brought it up, we'll dive into it.

    Yes there is, it's called a load and assignment operation. A fraction of a second that small doesn't matter to us, but it might to a computer that does it a billion times in a loop.

    The C++ vector example given has the following overhead: 1 object stack allocation. 1 copy constructor (which in turn calls copy on 3 floats, x,y,z).

    >> Any halfway decent compiler will optimize x = x + y to produce the same code as x += y. And if you're not using a halfway decent compiler, you really shouldn't be complaining about your code being slow.

    x = x + y evaluates x twice. This cannot always be optimized away as x does not have to be a primitive, perhaps it is inside a collections and requires 2 lookups?

    It is not hard to make an example where javac doesn't optimize it away:

    int a[] = { 1, 2 };
    1. a[0] += a[1];

       0:    iconst_2
       1:    newarray int
       3:    dup
       4:    iconst_0
       5:    iconst_1
       6:    iastore
       7:    dup
       8:    iconst_1
       9:    iconst_2
       10:    iastore
       11:    astore_1
       12:    aload_1
       13:    iconst_0
       14:    dup2
       15:    iaload
       16:    aload_1
       17:    iconst_1
       18:    iaload
       19:    iadd
       20:    iastore
       21:    return

    2. a[0] = a[0] + a[1];

       0:    iconst_2
       1:    newarray int
       3:    dup
       4:    iconst_0
       5:    iconst_1
       6:    iastore
       7:    dup
       8:    iconst_1
       9:    iconst_2
       10:    iastore
       11:    astore_1
       12:    aload_1
       13:    iconst_0
       14:    aload_1
       15:    iconst_0
       16:    iaload
       17:    aload_1
       18:    iconst_1
       19:    iaload
       20:    iadd
       21:    iastore
       22:    return





  • doesnt that just put us back to manni's first sentence??

     

    Goon, is there even a unit of time that can measure the difference in the two chunks of code you wrote?



  • I'm not arguing that everyone should use += all the time. Premature
    optimization and all that. It's like putting a biscuit into your mouth
    in 1 go... sure it saves eating time, but it is messy...



    The only time I ever conciously use OP= is in tight loops using
    overloaded operators in C++ to avoid unneeded copy-constructor calls on
    big objects (ie a 4x4 matrix). The point is, the original poster
    pointed out a "WTF" thinking there is no speed gain, while there is a
    very small one.



    This WTF is busted.






  • @HiredGoon said:

    Yes there is, it's called a load and assignment operation. A fraction
    of a second that small doesn't matter to us, but it might to a
    computer that does it a billion times in a loop.




    No, sorry, I tested just to make sure right after I read the
    paper.  4 trillion loops and no  measured difference in
    execution time using "long" primitives. 



  • @HiredGoon said:

    They do generate faster code.



    They do this  because binary operations create temporary objects, while += etc don't.



    Ie to demonstrate (primitives etc work the same way) here is a 3 dimensional vector += operation:



    vector3
                 
    &operator += (const vector3 &rhs) {

        x += rhs.x;

        y += rhs.y;

        z += rhs.z;

        return *this;

    }



    Now, + has to do exactly the same thing, but it has to create a new
    temporary object and return them, so that a = b + c + d + e; will work
    and not effect the original value.


    • is implemented using += but has the extra temp overhead.



      vector3 operator + (const vector3 &lhs, const vector3 &rhs) {

          vector3 tmp(a);

          tmp += b;

          return tmp;

      }



    Write it, compile it, disassemble it, if your compiler is worth more than a nickel the assemblys generated will be strictly the same.


  • <FONT face=Arial>@HiredGoon said:

    I'm not arguing that everyone should use += all the time. Premature optimization and all that. It's like putting a biscuit into your mouth in 1 go... sure it saves eating time, but it is messy...

    The only time I ever conciously use OP= is in tight loops using overloaded operators in C++ to avoid unneeded copy-constructor calls on big objects (ie a 4x4 matrix). The point is, the original poster pointed out a "WTF" thinking there is no speed gain, while there is a very small one.

    This WTF is busted.


    </FONT>

    <FONT face=Arial>I don't think that's entirely what the original poster was pointing out. There certainly can be a speed gain in the correct use of +=, especially as x gets more complex. But </FONT><FONT face=Arial>the original article says to use the operators "whenever possible." When you start using them repeatedly instead of regular operators, you're now going the wrong direction.</FONT>

    <FONT face=Arial>For example:</FONT>

    <FONT face="Courier New" size=2>x = a + b + c;</FONT>
    <FONT face=Arial>   becomes</FONT>
    <FONT face="Courier New" size=2>x = a;
    x += b;
    x += c;</FONT>

    <FONT face=Arial>Or the formula most often memorized and then never used again:</FONT>

    <FONT size=2><FONT face="Courier New">x = (-b + Math.sqrt(b*b-4*a*c))/(2*a);
    </FONT></FONT><FONT size=2>   </FONT><FONT face=Arial size=3>becomes, uh...
    <FONT face="Courier New" size=2>x = b; x *= b; </FONT></FONT><FONT face="Courier New"><FONT size=3><FONT size=2>z = 4; </FONT></FONT><FONT size=3><FONT size=2>z *=a; z *=c; x -= z; z = Math.sqrt(x);
    </FONT></FONT></FONT><FONT face="Courier New"><FONT size=3><FONT size=2>x = -b; x += z; x /= 2; x /= a;
    </FONT></FONT></FONT>

    <FONT face=Arial size=3>Now, suppose x is a user control... Once the assignment operators are used improperly, speed benefits go out the window. And we've all seen enough literal-minded programmers to know this could conceivably happen. </FONT>

    <FONT face=Arial>I think the real problem is the emphasis the article has. If, instead of saying "... as much as you can" and "whenever possible", it just said "consider using for a minor performance increase", nobody would have cared. But the article seems to say that you WILL have faster code if you use 'low-cost' operators. In reality, you might have faster code, but only if it's used correctly, and even then, only by a slight amount.</FONT>



  • >> Write it, compile it, disassemble it, if your compiler is worth more
    than a nickel the assemblys generated will be strictly the same.



    I did. It wasn't and I posted it above. Though granted, Javac does cost less than a nickel....





  • @Krenn said:

    <font size="2"><font face="Courier New">x = (-b + Math.sqrt(bb-4ac))/(2a);
    </font></font><font size="2">   </font><font face="Arial" size="3">becomes, uh...
    <font face="Courier New" size="2">x = b; x *= b; </font></font><font face="Courier New"><font size="3"><font size="2">z = 4; </font></font><font size="3"><font size="2">z *=a; z *=c; x -= z; z = Math.sqrt(x);
    </font></font></font><font face="Courier New"><font size="3"><font size="2">x = -b; x += z; x /= 2; x /= a;</font></font></font>


    I see! Obviously the wtf here is that there's no √= operator, slowing the entire process way down. I'm going to get on the phone to my microsoft rep right now!


    Goon, as a multimedia programmer, let me tell you that the time spent on actual processing and disk-loads dwarfs fiddly fiddling; time spent nitpicking these things can usually be better spent refining the algorithm or writing an SSE2 implementation.

    Tight billion-repition loops are entirely i/o bound anyway and are almost always candidates for TDWTF, dropping a single memory load instruction will not make a difference. (ps, Java is a crappy compiler anyway, it doesn't have to be good because it dynamic recompiles. You want a true comparison, compare the hotspot debug dump, or just use C.)



  • Krenn I said above that I don't think it is worth it most of the time
    (except for my matrix example that avoids a copy-constructor call which
    can't be optimized away)



    INCONTESTABLE PROOF += is TWICE AS FAST! (disclaimer: Like many benchmarks, may be somewhat contrived)



    public class BogusBenchmark {

        public static int index(int i) {

            try {

                Thread.currentThread().sleep(1000);

            } catch (Exception e) { }

            return i;

        }

        public static void main(String[] args) {

            int[] a = { 0, 0 };

            long start = System.currentTimeMillis();

            for( int i=0 ; i<10 ; ++i ) {

                a[index(i%2)] = a[index(i%2)] + 42;

            }

            System.out.println("+  -> " + (System.currentTimeMillis() - start));



            int[] b = { 0, 0 };

            start = System.currentTimeMillis();

            for( int i=0 ; i<10 ; ++i ) {

                b[index(i%2)] += 42;

            }

            System.out.println("+= -> " + (System.currentTimeMillis() - start));

        }

    }



  • >>
    Krenn I said above....

    Whoops, I mean
    foxyshadis (who quoted Krenn)



  • Ok, people seem to think that there is no merit in using += over + asside from shorter code, and this simply isn't true.



    With basic
    types, yes, the compiler will optomize t += 1 and t = t + 1 down to the
    same code.  But when 't' is an instance of a class that defines
    it's own operators, the compiler usually can't say for certain that
    those two lines are equivalent, because they don't have to be. 
    The operators can be implemented to do whatever you want them to.



    But, in any case, take this example, where t = t + 1 and t += 1 are
    equivalent, and the compiler still cannot optomize away the temporary copy
    created by t  = t + 1:



    class T {

         std::vector<int> v;

         const T& operator += (int i) {

                 v.push_back(i);

                 return *this;

         }

    };



    // rhs isn't passed by reference, because we need a temporary copy anyways.

    T operator + (T rhs, int lhs) {

          return (rhs += lhs);

    }



    The operator+ causes a temporary variable to be created, which in this
    case, as in many real-world cases, involves a lot of memory
    allocation.  Not too expensive regularly, but if you have a very
    tight loop:



    T test;

    for (int i = 0; i < 50000; ++i)  test += i;



    and



    T test

    for (int i = 0; i < 50000; ++i) test = test + i;



    The first, using +=, finishes (on my P4 using g++4.0) in .006
    seconds.  The second example takes 22 seconds.  Granted, this
    is a somewhat contrived example, but the fact remains that for any
    remotely complex types, + will be more expensive then +=, and inside a tight loop, that can make a huge difference.



  • While we are pulling out real world examples i tried two gcc compiled c
    programs that rxd's two ints then adds them and outputs the result (
    this is done to ensure the the compiler cannot optimise out the code ),
    with one adding the recived ints using '+=' and the other '= x + ' .



    The both have the same md5.



    Hopefully i dont have to explain that executables with the same hash contain the same ASM and thus execute with the same speed.



    WRT the coding style of '+=' vs '= x +' :



    If if is easier to read when using one operation then use it. If it
    makes it harder to read then dont. The compiler doesnt ( shouldnt )
    care.



  • @meagar said:



    With basic
    types, yes, the compiler will optomize t += 1 and t = t + 1 down to the
    same code.  But when 't' is an instance of a class that defines
    it's own operators, <int>




    Keep in mind that the paper originally quoted was explicitly talking about Java... which has no operator overloading.

    </int>



  • @HiredGoon said:



    x = x + y evaluates x twice. This cannot always be optimized away as x
    does not have to be a primitive, perhaps it is inside a collections and
    requires 2 lookups?





    This might be easier if we write x and y as X and Y, to suggest
    arbitrary expressions, rather than variables.  Let's say they're
    of type T.



    In the language-machine, X = X + Y means "add (eval X) and (eval Y) in z and store z in (eval X as an lvalue)".  If:

      1. the compiler can make reasonable assumptions about the
    semantics of evaluating X as an lvalue (for example, if X is a
    variable, not some crazy function-result reference) and

      2. it's able to store that lvalue,

    then it should be able to optimize away the double evaluation of X.  Furthermore, if

      3. constructing and destructing objects of type T has no side-effects and

      4. the value of A + B is identical to the value of A += B

    then it ought to be able to optimize X = X + Y into X += Y.



    Statements 3 and 4 are undecidable in the general case, so it's
    unlikely that a C++ compiler would attempt them for anything except the
    built-in types -- but it's quite reasonable to attempt them for
    primitive types, which is why the gcc results compiled identically.



    Now, C++ lets you construct lvalues willy-nilly -- you can return a
    reference, for example, or simply deference a pointer -- but Java
    really only has two lvalue expressions:  non-final local variables
    and array-index expressions.  You can't really optimize anything
    about a local variable load/store, but you theoretically could optimize
    an array-index expression load/store by keeping &a[i] around --
    except that the JVM doesn't support that sort of thing.



    The only difference between your two java-assembler listings is that one does a dup2 to turn this stack:

      ..., a, 0

    into this one:

      ..., a, 0, a, 0

    and the other just does loads the local variable and does an
    iconst_0.  I doubt this has any significant performance effect,
    but a competent JIT compiler might fix it anyway.



  • @HiredGoon said:

    I'm not arguing that everyone should use += all the time. Premature
    optimization and all that. It's like putting a biscuit into your mouth
    in 1 go... sure it saves eating time, but it is messy...



    The only time I ever conciously use OP= is in tight loops using
    overloaded operators in C++ to avoid unneeded copy-constructor calls on
    big objects (ie a 4x4 matrix). The point is, the original poster
    pointed out a "WTF" thinking there is no speed gain, while there is a
    very small one.



    This WTF is busted.






    The real WTF is why there should be any objections to using += or any
    of the others. They're short, elegant, readable and unambiguous. And
    non-programmatically there's something funny-looking about writing a =
    a + 1, because in normal speak '=' means 'is equal to', and a is never
    equal to a + 1. The abstract += has no real-life equivalent, and is
    therefore devoid of such potential 'huh?' moments in the beginning
    programmer.



    Same with ++ and -- and such.




  • @rogthefrog said:

    The real WTF is why there should be any objections to using += or any
    of the others.




    No. The real WTF is advising one over the other for any reason at all
    other than clarity of code in a specific situation. Read the posting by
    Krenn early in the thread.




  • "Since they actually generate faster code, use them whenever possible."

    I agree with the second part but not the first. It should've read "Since *you* generate code faster using them, use them whenever possible."

    The a = a + 1 notation is actually more confusing than a += 1. The first looks like a mathematical impossibility, while the second simply says "add 1 to a" It's reminiscent of Asm and the x86 architecture where most instructions have two operands: something that is modified, and something that modifies it. a += b is an example of this: a is modified, and b modifies a by adding to it. The equivalent x86 instruction is "add ax bx".



  • @Krenn said:

    Or the formula most often memorized and then never used again:

    x = (-b + Math.sqrt(b*b-4*a*c))/(2*a);
       becomes, uh...
    x = b; x *= b; z = 4; z *=a; z *=c; x -= z; z = Math.sqrt(x);
    x = -b; x += z; x /= 2; x /= a;

    I find the second one easier to understand, since it's broken down into a series of simple operations rather than one long expression that takes a bit of mental parenthesis-matching to read. If you rearrange the terms a bit you can eliminate the x = -b from above, to get:

    x = b; x *= b; z = a; z *= c; z *= 4; x -= z; z = Math.sqrt(x); z -= b; x /= 2; x /= a;



  • @llxx said:

    @Krenn said:

    Or the formula most often memorized and then never used again:

    x = (-b + Math.sqrt(b*b-4*a*c))/(2*a);
       becomes, uh...
    x = b; x *= b; z = 4; z *=a; z *=c; x -= z; z = Math.sqrt(x);
    x = -b; x += z; x /= 2; x /= a;

    I find the second one easier to understand, since it's broken down into a series of simple operations rather than one long expression that takes a bit of mental parenthesis-matching to read. If you rearrange the terms a bit you can eliminate the x = -b from above, to get:

    x = b; x *= b; z = a; z *= c; z *= 4; x -= z; z = Math.sqrt(x); z -= b; x /= 2; x /= a;

    I meant z /= 2; and z /= a; in the last two ops.


Log in to reply