Communication vs. documentation

Caporegime
Joined
18 Oct 2002
Posts
32,623
Thanks to this thread I managed to get a massive speed up in some production code of mine by employing some ternary operators:

I have a large vector of coordinates and need to find the bounding box of them. This was the old code:
Code:
//First initialize bbox to first value in vecotr points (not shown)
/// Loop...
 for (size_t i = 1; i < points.size(); ++i)
   {
      const Point& p = points[i];
      
      if (p.m_lon < m_min.x) 
         m_min.x = p.m_lon;
      else if (p.m_lon > m_max.x)
         m_max.x = p.m_lon;
      
      if (p.m_lat < m_min.y)
         m_min.y = p.m_lat;
      else if (p.m_lat > m_max.y)
         m_max.y = p.m_lat;
   }

Simple enough, I changed the code to this:

Code:
 for (size_t i = 1; i < points.size(); ++i)
   {
      const Point& p = points[i];
      
       m_min.x = p.m_lon < m_min.x ? p.m_lon : m_min.x;
       m_max.x = p.m_lon > m_max.x ? p.m_lon : m_max.x;
       m_min.y = p.m_lat < m_min.y ? p.m_lat : m_min.y;
       m_max.y = p.m_lat > m_max.y ? p.m_lat : m_max.y;
    
   }

I tested 10,000 loops of an example vector.
The old code took: 142.6 seconds (average of 3 trials)
The new code took: 27.3 (average of 5 trials)

Co,piled with gcc 4.81 and O3 optimization
5x speed up and to me the code is still very readable.
 
Man of Honour
Joined
13 Oct 2006
Posts
92,059
Yeah where you can optimise the whole function around that it does have some good benefits and in that case IMO is more readable but thats due to the nature of that function - its why I tend to avoid C++ mostly as knowing potentially obscure stuff like that can dramatically change the viability of code :S
 
Last edited:
Caporegime
Joined
18 Oct 2002
Posts
32,623
Yeah where you can optimise the whole function around that it does have some good benefits and in that case IMO is more readable but thats due to the nature of that function - its why I tend to avoid C++ mostly as knowing potentially obscure stuff like that can dramatically change the viability of code :S
I find the issues tend to be worse in higher level languages though. I often do stuff in python and I like java for casual coding but there are things that you do in these languages that can be very costly while looking innocent so you often have to be even more careful.

Of curse you don't choose a language like python if you are concerned about speed so the differences may not matter but they can certainly be bigger.

An example in python:
Code:
# String concatenation, very slow since strings are immutable
out = "<html>" + head + prologue + query + tail + "</html>"

# Very fast concatenation
out = "<html>%s%s%s%s</html>" % (head, prologue, query, tail)
 
Soldato
Joined
18 Oct 2002
Posts
3,926
Location
SW London
Can you tell me what this code is and where the bug is?

All variables names use standard terminology to express the appropriate mathematical variable names from the known physical equations.
Without documentation this would be a night to debug, with the appropriate documentation it is dead easy to debug, maintain and modify.
Code:
*wall of equations *

This is exactly the point I was trying to make.
Obviously if you're coding in Brain**** or assembly and you aren't able to name variables and structure things to be readable then comment what it is doing.
The example you posted above is a prime example of code that should be structured better.
I can't find the bug in there because I have no idea what it's supposed to be doing.
That would be helped massively if it was structured a bit better.

Also, be aware that the example you posted about converting if else to ternary operator has changed the logic.

In the if else case you only execute the second check only if the first one doesn't pass. In the ternary operator case you execute both regardless, which is equivalent to removing the else's from the first snippet.
Now, given that the two things you're checking against are called m_min and m_max, I would assume the situation would never arise where both cases would be true, but for for the general case you wouldn't be able to do it that simply in a safe manner.

Having said all that, regardless of using a ternary operator or if/else that code is still something that in my mind could do with refactoring.

You're basically getting the minimum or two values, then max, min and max.
Presumably it's working with latitute and longitute from the variable names.
Effectively it looks like you want to get a bounding box that covers all of the latitudes and longitudes in the points array.
However there's nothing in that code that tells me in the slightest that's what your intent is

*EDIT* just looked at what you posted above the code snippet. Seems I was right in what I thought it was doing.
The code itself should tell me that though.

*EDIT2*

As a quick 2 minute job I personally find this much more readable, the code is doing the same thing but telling me much more about the job it's performing
(In C# as that's what I have access to right now)

Code:
         public void GetBoundingBox(LongLatCoordinate[] points)
        {
            foreach (var p in points)
            {
                ExtendBoundsX(p.m_lon);
                ExtendBoundsY(p.m_lat);
            }
        }

        private void ExtendBoundsX(decimal longitude)
        {
            m_min.x = GetMin(longitude, m_min.x);
            m_max.x = GetMax(longitude, m_max.x);
        }

        private void ExtendBoundsY(decimal latitude)
        {
            m_min.y = GetMin(latitude, m_min.y);
            m_max.y = GetMax(latitude, m_max.y);
        }

        private int GetMin(int first, int second)
        {
            return first < second ? first : second;
        }

        private int GetMax(int first, int second)
        {
            return first > second ? first : second;
        }
 
Last edited:
Caporegime
Joined
18 Oct 2002
Posts
32,623
Yes, the ternary operator version is doing doing slightly different, the if-else can be optimized distance as you said, a value can't be both a min and a max. But that doesn't actually matter, what matters is the end result being correct which it is.

Apart form the bugs, with all those extra function calls will be much slower.
I did a quick translation to C++ and your code is 5x slower than my optimized code.


10K iterations took 21.8seconds, yours took 103.445 seconds.

And I don't agree that your code is particularly clearer. What was the purpose of wrapping the ternary operator in a method, it hasn't made anything easier but added overhead Do you wrap the increment operator in a method?

Code:
int increment(int val)
{
 val++;
 return val;
}



Now I have to say this bounding box code is part of a class that does indeed have most of the same methods you coded so one doesn't have to always manually do the work e.g. extendBoundingBox(Point& point). Our code base also has supprt functions for finding mins and maxes in vectors etc.
However, for internal methods that have a high computational need avoiding too much function overhead has been important in achieving the performance bounds needed.

if you don't deal with embedded, real-time or HP/big data systems then you can take more leeway in designing slower code.
 
Last edited:
Associate
Joined
9 Jun 2004
Posts
423
sse4 has min / max integer instructions, __asm { } gooo!

For me, in this specific case with the min/max stuff, the ternary version in post #21 is more readable than the refactored one in post 24.

Often factoring out functions is a great idea, but sometimes it just doesn't make it more readable, and makes the compiler's job harder (if perf. is a concern). Compilers and assemblers aren't magical.
 
Caporegime
Joined
18 Oct 2002
Posts
32,623
sse4 has min / max integer instructions, __asm { } gooo!

For me, in this specific case with the min/max stuff, the ternary version in post #21 is more readable than the refactored one in post 24.

Often factoring out functions is a great idea, but sometimes it just doesn't make it more readable, and makes the compiler's job harder (if perf. is a concern). Compilers and assemblers aren't magical.

I'm glad someone agrees.

And yes, factoring out functions often helps readability and maintainability but it is has cost and can be abused by factoring trivial code away so it is harder to see what the code is doing. Anything that is running either an inner lop needs care.

That big nasty function I posted with all the math - that is tor un on a smartphone, get called thousands of times a frame and one needs to aim for 60FPs for a playable game. It isn't my code but computes a spectral function for some very cool ocean simulation graphics. Factoring it would slow performance and not necessarily improve anything. It only does one single thing, non of the components are needed elsewhere in the code and with soem cleaning and commenting is very easy to follow. Factoring and changing the variable names wont actually change the readability. It is just a load math equations, know the math and it is easy. Add the math as comments and the code is easy.
 
Man of Honour
Joined
13 Oct 2006
Posts
92,059
Seems to be a performance hogging way to do an ocean simulation. (As an aside is it even needed to be called every frame? I usually have stuff like that update at say 10Hz or 30Hz discretely depending on the target hardware/functionality/visual impact).


EDIT: As another aside I do like the way in C you can iterate through struct members - something that a lot of programmers seem to miss and do some horrendous hack job with next/previous chains, etc. and nasty "housekeeping" functions to support it.
 
Last edited:
Associate
Joined
14 May 2010
Posts
1,136
Location
Somerset
And I don't agree that your code is particularly clearer. What was the purpose of wrapping the ternary operator in a method, it hasn't made anything easier but added overhead

I think this question demostrates that you don't understand the concept of self documenting code. The ternary has been wrapped in a method purely to describe what that operation is doing. Someone reading the code for the first time knows instantly what that method is designed to do without having to decode the details of what the ternary is doing.

C# has an advantage in this regard since the compiler is suitably advanced so that it doesn't matter if you write all your statements in separate methods or in one block, the compiled output will be the same since the compiler knows it's doing the same thing.
 
Caporegime
Joined
18 Oct 2002
Posts
32,623
I think this question demostrates that you don't understand the concept of self documenting code. The ternary has been wrapped in a method purely to describe what that operation is doing. Someone reading the code for the first time knows instantly what that method is designed to do without having to decode the details of what the ternary is doing.

C# has an advantage in this regard since the compiler is suitably advanced so that it doesn't matter if you write all your statements in separate methods or in one block, the compiled output will be the same since the compiler knows it's doing the same thing.

If someone doesn't understand a ternary operator then they shouldn't be in software development IMO. Should you wrap A = B * C in a special multiply function or leave the operator to it? If there is more things going on then for sure but a basic ternary assignment, is dead simple - the operator exists for a reason, and proven by the speed differences of 500%.
 
Last edited:
Caporegime
Joined
18 Oct 2002
Posts
32,623
Seems to be a performance hogging way to do an ocean simulation. (As an aside is it even needed to be called every frame? I usually have stuff like that update at say 10Hz or 30Hz discretely depending on the target hardware/functionality/visual impact).


EDIT: As another aside I do like the way in C you can iterate through struct members - something that a lot of programmers seem to miss and do some horrendous hack job with next/previous chains, etc. and nasty "housekeeping" functions to support it.

Its a very accurate ocean simulation with very realistic lighting. Might be too much for mobile platforms but is great on PCs.


For the interested the code is form this paper:
http://hal.inria.fr/docs/00/44/40/80/PDF/article-1.pdf

https://www.youtube.com/watch?v=jL9BKTkTMY4

This guy replicated the code in his own project.
https://www.youtube.com/watch?v=OiGADgezjC8

I also want to get his atmospheric scattering working.



That stuff is just for fun though.
 
Associate
Joined
14 May 2010
Posts
1,136
Location
Somerset
If someone doesn't understand a ternary operator then they shouldn't be in software development IMO. Should you wrap A = B * C in a special multiply function or leave the operator to it? If there is more things going on then for sure but a basic ternary assignment, is dead simple - the operator exists for a reason, and proven by the speed differences of 500%.

It's not a case of not understanding the ternary operator, it's a way to make your code more understandable for future developers who will not want to pick their way through every line in detail. Calling the method GetMin(a, b) perfectly describes what that method does and removes any ambiguity as to it's behavour.

It's definately a situational thing. I would not advocate that every multiply option is wrapped up in every case, but you posted example code which is very difficult to follow without it being refactored out into smaller, understandable, sections and therefore is a perfect candidate.

This is a very common thing to do. Take a look at some of the methods available in the .NET framework for the string object. Microsoft have provided stuff like 'string.IsNullOrEmpty()'. I'm guessing that you can tell what that method does without seeing a line of code underneath it, or even having to think about it at all. The code underneath is a simple one-liner, but it increases the readability of the code if it's used.

It's all about giving future developers of your code the best possible chance of understand what you are doing after the documenation has either been lost in time or falls out of line with the code (which *will* happen. Have you updated the documenation with the code changes you've made in this thread yet? :) )

In regards to the performance increase... get a better compiler. A modern compiler should generate the same asm no matter if you write a ternary or the equivalent 'if' statement (more evidence of this here - http://stackoverflow.com/questions/...nce-between-if-else-and-ternary-operator-in-c)
 
Associate
Joined
9 Jun 2004
Posts
423
Rather than the constant pontificating about code style, the actually interesting thing is here why the compiler is generating different code in the "if" and the ternary cases (gcc -S please for your code D.P.!)
 
Soldato
Joined
18 Oct 2002
Posts
3,926
Location
SW London
If someone doesn't understand a ternary operator then they shouldn't be in software development IMO. Should you wrap A = B * C in a special multiply function or leave the operator to it? If there is more things going on then for sure but a basic ternary assignment, is dead simple - the operator exists for a reason, and proven by the speed differences of 500%.

As ZombieFan says it's not a lack of understanding of a ternary operator, it's about making it immediately obvious what you're trying to do.

Code:
x < y ? x : y;
x % 2 == 0 ? true : false;
x != null ? x.foo : null;

are all simple ternary operators, but they're doing very different things and I would at least consider about wrapping things like that in methods (especially if they're used in multiple places throughout the code)
It's all about communicating intent, as Martin Fowler says "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

Granted I don't really write code for embedded systems (I don't think any of the stuff I'm current working on runs on anything with less than 8 cores and 16GB of RAM!), so maybe if I was doing similar stuff to you I'd have a different viewpoint.
For the stuff I do I'll always write it in such a way as to try and communicate as much as possible from the code itself. There may be a need to optimise things for performance at some point, but for 95% of the things I do the original way is good enough.

I am surprised at a 5x slowdown just by wrapping a few things in separate methods though.
 
Caporegime
Joined
18 Oct 2002
Posts
32,623
Won't lie that looks good* - but a fast approximate version would be all but identical in a video game or similiar context as at all matters.



* makes my own attempt look a bit amateur

http://aten-hosted.com/images/cldtest.jpg

http://aten-hosted.com/images/cldtest2.jpg

It is quite fast though. Have a look at http://proland.inrialpes.fr/ and http://www.outerra.com/
Proland is by the same author, outerra uses very similar algorithms. I expect cryengine 3 uses similar techniques.

that function was just what was provided in the source code, optimization are possible.
 
Caporegime
Joined
18 Oct 2002
Posts
32,623
It's not a case of not understanding the ternary operator, it's a way to make your code more understandable for future developers who will not want to pick their way through every line in detail. Calling the method GetMin(a, b) perfectly describes what that method does and removes any ambiguity as to it's behavour.

It's definately a situational thing. I would not advocate that every multiply option is wrapped up in every case, but you posted example code which is very difficult to follow without it being refactored out into smaller, understandable, sections and therefore is a perfect candidate.

This is a very common thing to do. Take a look at some of the methods available in the .NET framework for the string object. Microsoft have provided stuff like 'string.IsNullOrEmpty()'. I'm guessing that you can tell what that method does without seeing a line of code underneath it, or even having to think about it at all. The code underneath is a simple one-liner, but it increases the readability of the code if it's used.

It's all about giving future developers of your code the best possible chance of understand what you are doing after the documenation has either been lost in time or falls out of line with the code (which *will* happen. Have you updated the documenation with the code changes you've made in this thread yet? :) )

In regards to the performance increase... get a better compiler. A modern compiler should generate the same asm no matter if you write a ternary or the equivalent 'if' statement (more evidence of this here - http://stackoverflow.com/questions/...nce-between-if-else-and-ternary-operator-in-c)



The thing is, if I wanted a function to getMin then i would want one available across classes, better still I would just use the built in min function.;)
But if don't want the function overhead then using the ternary operator as it is, is going to be much faster. If performance matters, then the over head is too expensive, if the function is rarely called then the overhead is acceptable.

Methods like the ExtendBoundsY are very useful and have always been in this class, but there isn't always a good reason to call those methods internally
In my example a developer just needs to know that the bounding box of a vector of points is computed by iterating over all points and assigning the min and max of the latitude and longitude to the x, and y respectively. that is self evident from the ternary operators, especially with the variables names min.x = ???? is obviously assigning the min on the x dimension based on the condition. If the user is wanting to extend the bounding box to include a new point P I don't expect them to use 4 new ternary operators, because yes they might make a mistake. Instead they just use use the Extend(const Point& p) class method.

This might be different if the comparisons were some more complex function liable to coding errors but simple 1 line single op, no, not needed. As I said, you would never write a function to increment an index For example, what you seem to be advocating is something like this:

Code:
int addOne(int i)
{
 return i + 1;
}
int getZero()
{
 return 0;
}
bool isLessThan(int i, int j)
{
 return i < j;
}
...
...
vector<int> vec = ........./// contains some times
for (int i = getZero(); isLessThan(i,vec.size()); i = addOne(i))
{
 // do something with vec[i]
}

You can go far too far factoring code to the point that it reduced readability and massively impacts performance.





Containers and strings (strings are just vectors of underlying datatypes) have simple methods like size) and empty() because the underlying datastructure ultimately has a private int size value that it is making available through an accessor.
just have a look at some of the math functions or hashing and the like, hundreds and hundreds of lines of dense code, complex logic, taking advantage of peculiarities in integer or floating point instruction sets and loads of general trickery that outsiders can never easily understand. Why, because it provides highly performant code

have a look here to see some trig code from libC:
https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/ieee754/dbl-64/s_sin.c;hb=HEAD




And lastly, you have no guarantee what the compiler does, compilers can be very stupid. Some If statements might be compiled into the same conditional assignment instruction, but you have no guarantee. One reason why my example code was 5x slower using IF statements was that dependent on the result of the min test the max value was conventionalized. Therefore this was compiled as a branch, and branches are far slower than a specialized single instruction.
The code was compiled with the latest GCC version 4.8.1 with O3 optimizations. This effect has nothing to do with a bad compiler.
 
Caporegime
Joined
18 Oct 2002
Posts
32,623
As ZombieFan says it's not a lack of understanding of a ternary operator, it's about making it immediately obvious what you're trying to do.

Code:
x < y ? x : y;
x % 2 == 0 ? true : false;
x != null ? x.foo : null;

are all simple ternary operators, but they're doing very different things and I would at least consider about wrapping things like that in methods (especially if they're used in multiple places throughout the code)
It's all about communicating intent, as Martin Fowler says "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

Granted I don't really write code for embedded systems (I don't think any of the stuff I'm current working on runs on anything with less than 8 cores and 16GB of RAM!), so maybe if I was doing similar stuff to you I'd have a different viewpoint.
For the stuff I do I'll always write it in such a way as to try and communicate as much as possible from the code itself. There may be a need to optimise things for performance at some point, but for 95% of the things I do the original way is good enough.

I am surprised at a 5x slowdown just by wrapping a few things in separate methods though.



You would be surprised at the overhead of a function:
http://www.angelcode.com/dev/callconv/callconv.html
Code:
[LIST=1]
[*] Arguments are pushed on the stack in reverse order.
[*]  The caller pops arguments after return.
[*] Primitive data types, except floating point values, are returned in EAX or EAX:EDX depending on the size.
[*]  float and double are returned in fp0, i.e. the first floating point register.
 [*]   All structures and classes are returned in memory regardless of complexity or size.
 [*]   When a return is made in memory the caller passes a pointer to the memory location as the first parameter (hidden). The callee populates the memory, and returns the pointer. The callee pops the hidden pointer from the stack when returning.
 [*]   Classes that have a destructor are always passed by reference, even if the parameter is defined to be by value. 
[/LIST]
   

Compilers might inline a function. They might not, even if you try to force them to inline wiht the inline keyword they are free to ignore you. You have no control, and the reasoning for not inlining is not well documented.

Therefore if you want to guarantee no function over head then manually inline simple functions and don't over factorize your code. This is only really important inside inner lops that called numerous times. e.g.,  my example function my run other hundred of millions of points in our real world data set - that adds up to a serious amount of overhead if  you can't guarantee inlining.
 
Back
Top Bottom