|
Examples like this really make the advantages of C++'s templates apparent.
That could all be done by recursion - evaluated at compile-time, and be generalised to any N-tuple.
It also makes the advantage of RAII apparent - use multiple returns safely with the guarantee of deterministic cleanup.
"If you don't fail at least 90 percent of the time, you're not aiming high enough."
Alan Kay.
|
|
|
|
|
This is C# code, (although I have a SparseArray template for C++ article on codeproject). This code does look just like C++ though.
The code was generated by a program. I had to create ComparableTuple generics for so many sizes, I wrote a program that generates the code. Still, I would want the code to be readable afterward.
If you meant the code generator could use recursion, I agree, although the code I wrote uses iteration. I expect that is almost certainly what you meant.
The run-time code shouldn't use recursion as that would be too inefficient, and this needs to be as fast as possible.
|
|
|
|
|
No, I was not referring to runtime recursion you'll be pleased to know. That would be horrendously inefficient - and in my experience when you're writing code like this efficiency is often an issue.
I am talking about code generation, but letting the compiler do the work for you using template metaprogramming. Modern C++ supports variadic templates, so a Tuple class can be declared something like:
template <typename T, typename... Args>
class Tuple : public Tuple<Args...>
{
};
That is a tuple of any number of arguments, each of any type.
Your CompareTo function can then be written using recursion that occurs entirely at compile-time. This does require a detailed knowledge of template meta-programming though, and is certainly beyond me explaining in a short response here.
Luckily, the standard library already includes such a class named std::tuple . Here's a short example of its use:
#include <tuple>
#include <iostream>
int main()
{
auto t1 = std::make_tuple(1, 2, 3, 4, 5);
auto t2 = std::make_tuple(1, 2, 3, 4, 6);
if (t1 < t2)
std::cout << "Less" << std::endl;
else if (t1 > t2)
std::cout << "Greater" << std::endl;
else
std::cout << "Equal" << std::endl;
return 0;
}
Given two tuples, this will actually generate at compile-time code more or less equivalent to the example you gave above.
Indeed, given the two tuples defined above, a good compiler will simply realise that all the values are known at compile time, using Visual C++ 2013, I checked this. The program I just gave generates assembly that is equivalent to writing:
int main()
{
std::cout << "Less" << std::endl;
}
Sadly, you need to be using a really modern compiler for this goodness - VC++ just added support in 2013 - but it least its now supported by the most recent versions of all the major compilers.
PS. If you really want to see the dirty details involved in this sort of metaprogramming let me know, it just may take me a bit longer to compose - I'm still finding my feet with this stuff myself.
"If you don't fail at least 90 percent of the time, you're not aiming high enough."
Alan Kay.
|
|
|
|
|
I have read "Modern C++ Design" by Andrei Alexandrescu, which covers template metaprogramming in great detail.
I use it, however, I don't use it when there is a simpler solution. In this case, the code is auto-generated by another program that writes the entire file. Also, the code I posted is written in C#, not C++.
modified 11-Nov-13 22:28pm.
|
|
|
|
|
I was aware this was C#, I was simply saying that this is an area where templates (as opposed to generics) pay dividends.
"If you don't fail at least 90 percent of the time, you're not aiming high enough."
Alan Kay.
|
|
|
|
|
I remember that when I started learning programming in 1985 or before that the version of the language I started with was build with no loop statements, except the FOR loop. This made it relied heavily on GOTO statement in the case of conditional loops. I think for languages,if any, with such structure one HAVE to use it, with care.
|
|
|
|
|
I have never needed to use a GOTO statement in any language since BASIC. And when I wrote a recursive extension to the GOSUB function (along with a couple other snazzy enhancements) in Commodore PET's BASIC, I never needed one again either.
Marc
|
|
|
|
|
I'll repeat an edit to my reply to Harold up above:
The problem with goto is not, and never really has been, with exiting a context abruptly, especially with RAII, but in entering another context at an arbitrarily point.
|
|
|
|
|
Because it's so easy to misuse and because other constructs make it much easier to write readable code.
Christian Graus
Driven to the arms of OSX by Vista.
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.
|
|
|
|
|
I use it and feel good when it is in my code because so many reputable sources are saying "do not do it" but not really showing any good reason why it is bad.
|
|
|
|
|
It's often hard to understand the reasons if all you do is look at the code you've just written. You have to "look" at the code that it will grow into after a year or more of maintaining and extending that code however!
I've seen some huge 15-20 year old functions with several hundreds or even thousands of lines of code. After so many years, I could still guess the general shape and design of these functions when they were first written, and at that time, using goto was neither looked down upon quite as much, nor did it seem unreasonable in that particular piece of code. Over the years however, tons of features were added who required additional blocks of code; dozens of corner cases were detected that needed to be treated separately; and at least half a dozen developers added their differing visions of how the code should be formatted and designed.
One of the things I tried over the years is split up that code into smaller, better maintainable chunks, but I've found there is no easy way to ensure this won't break the multitude of corner cases handled by this code. I could move some of it into initialization functions, and extract a few of the special feature code blocks. But it was nigh impossible to disentangle the mass of conditional code and goto statements (few as there were) while ensuring that the code would behave exactly like it did before. With none of the original programmers around to help determine what exactly the code was supposed to do, breaking it was too high a risk to take.
I'm not saying that the goto statements were the sole reason for the sorry, unmaintainable state of the code, but they were the main reason why I was unable to transform it into something maintainable!
tl;dr:
If all you do is write short lived functions and programs then use goto at your hearts desire. But if that code that you're writing will go into a professional application that may live on and be maintained and exxtended for many years, then you better think ahead and don't introduce a legacy that is hard to bear and harder to kill!
|
|
|
|
|
Take a look at this decades old code - I bet you can easily read it: http://www.literateprogramming.com/adventure.pdf
|
|
|
|
|
My point wasn't about code that died decades ago, but code that continued to live and be modified over decades! Readability is just one aspect, maintainability is more important.
Besides, the author himself stated in the comments that his reason for using goto was implementing multistate transitions. Come on! I've used tools to generate those automatically from UML 10 years ago! And I could even choose if I wanted to generate the statemachine using swicth or inheritance! In other words, there are valid alternatives and even tools that help you generate the code.
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|
OCaml is a live, thriving project with many new contributors. It's perfectly maintainable, and, in my books, an example of the perfect C coding style.
Linux has megabytes of commits every week. Far from being "dead for decades". So, what are you talking about?
What's the point of generating code which is perfectly readable anyway? State transition is, essentially, goto. Any higher level language will feature the semantically equivalent construction to represent state transitions. Yes, arrows in your UML are not any different from goto.
|
|
|
|
|
of ocaml is your idea of perfect coding style we have very different views how good codes looks or reads.
|
|
|
|
|
Ah, let me guess, your brain is mutilated by that pathetic OOP thingy?
Ok. Carry on writing your buggy, slow, unreadable code. But do not lecture the real programmers on how to do things the right way. You'll never be able to implement a bytecode interpreter faster than this one.
|
|
|
|
|
hrhrhr it gets better by the post
pathetic oop vs "real programmers" - mmd
|
|
|
|
|
vl2 wrote: You'll never be able to implement a bytecode interpreter faster than this one.
One however might suppose that there are at least a couple of programming jobs out there where one does not need to implement a byte code interpreter though.
Might even be a few lines of code from those jobs that maybe one or two other developers might need to maintain in the future as well. And none of which where optimal performance is a goal or even a requirement.
|
|
|
|
|
Look, I disagree with the absolutist position too, but I agree that using goto's is generally bad. I just acknowledge there might be exceptions.
But, having worked on code with gotos, which I did not write, but I had to maintain it, and having worked on object-oriented code, in my experience the code with gotos had both many more bugs and also worse bugs. I've had this same experience in more than one job too, so I see that as a recurring pattern.
And, I've also seen object-oriented code that was faster than the older C code. Efficiency has to do with many other factors than the language used.
If only encapsulation is used in C++, there is no calling penalty and the generated code is essentially C code.
And, 'often' the overhead of calling virtual functions is typically less than using a 'switch' statements, so if the setting that is being tested is in a loop, doing the switch statement outside of the loop and choosing an object with a virtual function based on the switch results can be much faster.
In C, you can do that same thing with function pointers, but it's more complicated, and people don't typically write code that way in C. In C++. it's very simple to do that, plus, there are other benefits.
Here's another issue about performance unrelated to the language used:
I once unrolled a loop for a floating point routine and the code got significantly faster. I did the same thing for a routine that did the same calculations, but was for a different processor. That routine used integer arithmetic. When I unrolled the loop, the routine got much slower! The integer routine required shifting the products down after every multiplication. These extra shift instructions made the code grow to over 4Kbytes, and 4Kbytes is the size of the instruction cache. The code was cache-thrashing.
I've seen the pattern of people imagining their code was faster than some other implementation, when a profiler later showed that the other implementation was actually faster. Modern systems do multiple levels of caching for both instructions and data, do branch prediction, and even do instruction reordering based on both the instruction types and register pressure. Predicting how fast code will run is more complicated today than it ever was. Without using a profiler, it's usually just guesswork.
Finally, languages are tools. Use the right tool for the job. If you think object-oriented languages are slow and buggy, then you definitely don't understand them.
Also, performance is not always the most important attribute of code. If the code takes 10 microseconds instead of 5 microseconds, and the requirement is that the code runs in 1 millisecond, then I would rather the simpler, easier to maintain, implementation, and that is typically (as in almost always) the code written in C++. C is more portable than C++, so C has that going for it, although if one restricts the features they use in C++, it's probably as portable as C.
But, when you suggest that object-oriented languages are inherently more buggy than C, well, that flies in the face of both research and experience. Well-written C++ hides the data, and that definitely tends to make code less buggy, not more.
|
|
|
|
|
Bill_Hallahan wrote: Without using a profiler, it's usually just guesswork.
That point needs clarification.
I don't deliver code. I deliver systems.
The customers that use my systems don't care if a for loop is faster or not. They do care how fast the business functions of the system are though.
There is no way that I can guess which piece of code is going to prove to be a bottleneck in a system. And I haven't worked with anyone that can do that either. So profiling is always required.
Not to mention of course that profiling systems is unlikely to substantially increase the speed of the application. The only time it does lead to substantial increases is when it identifies points which were poorly designed or architected in the first place.
Although to be fair I haven't worked on anything that I would consider a small system for years (perhaps 2 decades.) So that probably colors my experiences.
|
|
|
|
|
jschell:
"Not to mention of course that profiling systems is unlikely to substantially increase the speed of the application. The only time it does lead to substantial increases is when it identifies points which were poorly designed or architected in the first place."
I have found that profiling the code and subsequently optimizing the code can, in some cases, cause significant speedups even in a well-designed and well-implemented system.
However, I don't doubt that it's not desirable to optimize the code in whatever problem domain you worked in. Some things can't be made significantly faster. And, even when code can be sped up a lot, it's not always necessary.
Digital filters, which are used in audio codecs, video codecs, imaging, and sometimes graphics, can be sped up dramatically, such as running from 1.5 to 3 times faster, or rarely even faster, by writing specialized assembly routines that use parallel (SIMD) instructions. Modern Intel and AMD processor have wide registers that allow simultaneous multiples and adds of multiple numbers at the same time. These are Single Instruction Multiple Data (SIMD) instructions. Even Intel's parallel compiler cannot handle the special parallel instructions in an optimal fashion in all cases. There's still a need for hand-written assembly code in some cases.
The C or C++ code to implement the algorithms is not badly designed. The compiler often can't generate the best code using these SIMD instructions today. Compilers can handle much more than in the past, but they still aren't as good as a person yet for some things.
Intel now sells various routines that do mathematical operations, such as a dot-product, and even a modern video codec, and these routines use the SIMD instructions internally, so less SIMD code has to be written today. The routines don't cover all cases though.
In a previous job, I wrote code to rotate an color image using bicubic filtering. I then sped up rotating the image on a page by a factor of 1.7 by writing writing specialized assembly code using SSE1 instructions to do part of the calculations. This was only part of the calculations, but that speedup was very significant and resulted in a competitive product.
Not all the optimizations that I have done involved writing assembly language. For one audio application, I unrolled a loop and that caused a significant speedup. That does make the code harder to read, but I added comments. In this case, the speedup was critical to the application.
For another hypothetical example that is similar to something I did, imagine the profiler shows most of the time is spent sorting the data and the code is using a radix sort algorithm. Quicksort is generally better than radix sort, but if sorting 7 or fewer items, quicksort, heapsort, a radix sort, and other advanced sort algorithms are actually slower than a very simple sort routine. A specialized sort routine written for just 7 items will be very, very, fast.
If the problem domain usually had fewer than 8 items to sort, then a good optimization might be to choose the appropriate sort routine based on the number of items to be sorted. I have also seen significant improvements with that type of optimization, although this specific case is hypothetical. The actual case where I did something similar to this is difficult to explain.
My point is, while most of the time the code I worked on didn't need to be optimized, occasionally I've seen optimization result in significant improvements in performance.
To quote Knuth again, ""We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil""
Of course, Knuth was guessing those numbers. 86.3% of statistics are made up on the spot.
modified 14-Nov-13 0:25am.
|
|
|
|
|
Bill_Hallahan wrote: My point is, while most of the time the code I worked on didn't need to be
optimized, occasionally I've seen optimization result in significant
improvements in performance.
I have not doubt that optimization can make applications significantly faster. However that is different than saying that you managed this only after profiling.
Bill_Hallahan wrote: Digital filters, which are used in audio codecs, video codecs, imaging, and
sometimes graphics, can be sped up dramatically, such as running from 1.5 to 3
times faster, or rarely even faster, by writing specialized assembly routines
that use parallel (SIMD)...
Sounds quite reasonable and are you saying that you came across this need, all of it, by profiling the existing application?
|
|
|
|
|
jsachell wrote:
"I have not doubt that optimization can make applications significantly faster. However that is different than saying that you managed this only after profiling."
I usually profiled the system, but not always. Sometimes I just timed how long it took to do something. These times, I did know what the bottleneck would be without profiling because it was obvious, even to the casual observer, even without experience and history, both of which I have in this area.
For just one such case, in an embedded processor, I was getting data from an Analog to Digital Converter (ADC) using DMA, and then doing millions of calculations per second on the data, and then writing the data to an in-memory buffer, and then the processed data would be written to a Digital To Analog Converter (DAC). I knew that the majority of the time would be spent doing the algorithm that does millions of calculations per second, because the input and output buffering would be many orders of magnitude faster.
Sometimes in simple systems, it is obvious what needs to be optimized even without profiling.
I do agree with you, in large systems, profiling is needed. Very simple systems can be understood almost completely, particularly when someone is repeatedly implementing similar simple systems.
|
|
|
|
|
Stefan_Lang wrote: And I could even choose if I wanted to generate the statemachine using swicth or
inheritance! In other words, there are valid alternatives and even tools that
help you generate the code.
None of which however addresses whether those generated solutions are optimal performance implementations.
In my experience performance optimization, based on actual bottleneck analysis, can often lead to code that is less than ideal in some other aspect such as design and/or maintenance.
|
|
|
|
|
Please tell us about your experience.
In some projects I worked on, there was UI code, audio code, and video code. The video code was the bottleneck. I can't imagine it making any sense to change the design of the system to fix the video code. Nor was the overall design of the video codec redesigned. Only minor implementation changes were made.
And, ironically, using a goto for performance optimization is often an example of code that is less than ideal. (Note, I didn't write "always" - I can't know that because I don't know all possible situations - and sometimes that last tiny bit of performance gain does matter - but such situations are certainly extremely rare).
As an aside, to make a generalization (and most generalizations are false, including this one!), the overall concern for code should be maintainability. Typically, 85% of the cost (or time) for software is maintenance, so making code easy to maintain usually should overrides all other concerns.
Also, for most software, performance isn't an issue.
So, if a goto is ever justified, it would have to be in a fringe case.
|
|
|
|
|