views:

363

answers:

4

The title is a bit of a misnomer, what I mean really is "C with classes".

Let me explain, recently I bought the book ShaderX7 which came with a lite (and old) copy of the Unigine engine for one of the articles about shadow mapping techniques.

I was dabbling through the code when I realized that, while the author was using C++ and inheritance and all the C++ goodness, most if not all the method content was essentially C-style code; for example:

int Shader::get_param(const char *name,char *src,String &dest) const {
    char *s = src;
    int size = (int)strlen(name);
    while(*s) {
        if(!strncmp(s,name,size)) {
            if((s == src || strchr("\n\r",*(s - 1))) && strchr(" \t",*(s + size))) {
                src = s;
                s += size;
                dest.clear();
                while(*s && strchr(" \t",*s)) s++;
                while(*s && strchr(" \t\n\r",*s) == NULL) dest += *s++;
                if(dest.isEmpty()) {
                    Log::error("Shader::get_param(): can't get \"%s\" \n",name);
                    return 0;
                }
                memmove(src,s,strlen(s) + 1);
                return 1;
            }
        }
        s++;
    }
    return 0;
}

I am not saying this is bad, it does the job and that's what matters but I'm more used to C++ style constructs, with std::string, vectors etc... and I make big use of the Boost libraries; so this kind of style come as a bit of a surprise to me.

Is it really better/faster to use this kind of code than to go the generic, OO way ?

Did I fall into the "too much abstraction" trap ?

Edit: corrected method name to make it clear what it is doing.

+2  A: 

If it:

  1. Works and you're happy with it, don't touch it.
  2. Doesn't work, change it the way you like, as long as it's fixed afterwards.
  3. Works and it's not fast enough, make it faster (NOT without profiling first).

I'll guess that you're falling into case 1, so I'd advise to leave it alone. If you need to change things, and don't have serious performance concerns, by all means dish out some C++isms on it. In the general case I find it difficult to read C-style C++ programmers, they either:

  1. Haven't had time or don't care to learn the C++ way to do things (which really isn't acceptable from a safety perspective),
  2. are cherry-picking the parts of C++ they actually need (very wise),
  3. or they honestly need the performance characteristics of C (very rare).

Looking at the code it could be any of these cases. Either way it doesn't matter now, the code is in your hands.

Matt Joiner
I was not going to re-write his code, I was just exploring his code base and discovered a totally different approach that I had never seen before.I also find it difficult to read C-style code, but bear in mind that the excerpt I posted is a pathological case; most of his code is actually quite readable.
LeSnip3R
I quite like reading C code. Less is hidden from you, particularly with the "fiddly string manipulation stuff", you don't have to go looking up what all the classes in use will do with all the implicit activity going on. Then BAM an exception falls from the skies when you didn't expect it.
Matt Joiner
I'd disagree, that's where good documentation come into play, you document code that might throw an exception, or document if your method has any exception safety (nothrow guarantee etc...).
LeSnip3R
@LeSnip3R: You are of course right here, but various language features can be troublesome in some situations. Sometimes exceptions are right for the job, sometimes they are not. C++ exceptions are just plain awful: they're overly verbose, slow, and very difficult to debug. This in my opinion, makes them the right solution not so often as say in Python.
Matt Joiner
+4  A: 

If you really want to be drawing graphics as fast as possible, and you start off by saying

int size = (int)strlen(name);

that looks like barking up the wrong tree.

What does get_something get? It doesn't appear to be graphical.

Optimization doesn't consist of doing the wrong thing very quickly. It's about cutting the fat and spending all the time on the important stuff. If your graphics engine wastes a significant number of cycles on string processing, it has an issue that changing languages probably won't solve.

So… write auxiliary functions as expediently, maintainably, and clearly as possible. When you need to micro-optimize the critical path, C++ still covers the low-level style.

Potatoswatter
Well I changed the name of the method, it was just to make a point.The excerpt is from a Shader class that is retrieving a Shader parameter, so it has to be fast.I do agree on the fact that one should get the functionality working first, in a manner that is maintainable then worry about performance; however when I stumbled upon that piece of code, that made me wonder if I was doing something wrong.
LeSnip3R
@LeSnip: Did you click save? The post has not been updated. Anyway, I don't know much about graphics, but how often do those parameters have to be parsed? At program initialization? Once per frame? Thousands of times a second? If that much, is there no way to cache the results and hash/checksum the arguments? If not, is it impossible to integrate tighter with whatever generates those strings on the fly? Just because something contains critical information doesn't necessarily mean it's on the critical path.
Potatoswatter
I just edited it. Ideally shader parameters are cached, e.g. you retrieve a handle to them on shader compilation and then use it when you need to upload the parameter to the GPU; which can happen several times per frames depending on the parameter.And yes, I use a hash for those parameters, so does he; but I would be using std::unordered_map while he cooked his own Map class.
LeSnip3R
+10  A: 

First and foremost I must admit that I'm not a games developer, even though I had developed a fully functional 3D game engine in the past.

That aside, I do have a few words about optimizations, "spoiling" languages and so on.

When developing an application - any application - the golden rule of optimizations is "don't optimize before you make it work". You need to fully support all the functionality you want in your application, and only then you should optimize it. The reasons vary; the most important one is that while you may believe something is "slow", it may not be your bottleneck. You may be spending a lot of time optimizing something that doesn't require optimization. Furthermore, optimizations often blur your code simplicity and maintainability, which may lead to bugs in the near or far future. If this piece of code didn't require optimizations, you've complicated the code for nothing.

While that is the golden rule of optimizations, there is one big exception. When you know, in advance, that your application will be stressed out to the max, you need to make some different choices in the beginning (in the architecture or in the design phases) and also along the way. Also, the general rule that the platforms are getting better and faster doesn't apply for games, where the competition between the developers is on the very edge of technology. Here are several points to consider:

  1. Some lingual features may be costly. Exceptions in C++ are a good example.
  2. Some lingual features may actually save you code and will cost little or nothing during runtime. C++ templates are a good example, though you still may wind up creating lots of code during compilation, leading to a bigger binary, and therefore lower performance, potentially.
  3. Infrastructures (STL for example) are generic. By making a solution specific to your domain, you MAY be improving your performance; A simple example - if you have a vector that will always be 3 items long, you will definitely be better than the stl::vector implementation. However, this is not always the case. For further reading, see chapter 11 of "Efficient C++" by Dov Bulka. This is an excellent book in general for your question.

In general, using C with classes is a good start for writing fast code while retaining a structural, well-designed project, but one should not neglect the whole of C++ excellent features - more often than not, trying to roll out your own solution to a problem covered by C++ (e.g., polymorphism) will be slower than the out-of-the-box solution, unless you can make the solution VERY specific to the problem at hand.

Eldad Mor
I also recommend Efficient C++. It is a well written book that emphisizes emperical measurements, and demonstrates how to write clear AND efficient code. Efficient code does not need to be ugly to read. I do have one qualm however: Exceptions may be inefficient, but so are "cascading if statements". If you are returning an error code, and every function up the chain checks that error code, just throwing the exception may be more efficient. If performance is important, implement both, and measure measure measure. Otherwise, premature optimization is the root of all evil.
Dragontamer5788
I agree with you, functionality and maintainability comes first, performance second; at least that's how I go about programming.However games programming at times may seem more like a black art, where everyone has their own recipe for success; I guess that's what I was trying to figure out.
LeSnip3R
@Dragontamer5788: The main problem I've found with using exceptions in C++ is that many programmers don't understand them well enough to use them. They often don't understand the incurred performance hits, the temporary objects that are created, etc. I do agree that cascading if statements are not good either.
Eldad Mor
@LeSnip3R: I once thought that games programming is a black art, but looking at about 20 engines, some of which are commercial, I was disappointed to find out they mostly contained mundane code that was not optimized at all. I was looking for loop unrolling and assembly code all over the place, and instead I found many inefficiencies. In certain places however there were some neat tricks - and I believe these were enough to make the magic happen.
Eldad Mor
@Eldad Mor: I could also use XOR to swap two variables, which is more efficient than using a temporary variable, but I wouldn't do it unless it was a bottleneck. Maybe the devs who made those engines optimized the parts that mattered. Or maybe they are just bad, who knows?
Ed Swangren
There is also the problem of exceptions crossing the dll boundary; for my games programming I tend to not use exceptions at all in performance critical part of the engine (the renderer for example). If an error occurs in, say, the loading of a texture, I output some error message and use a default "error" texture. That way it's clear that the texture didn't load, and the program still works.
LeSnip3R
@Ed Swangren: I couldn't agree more. My personal guideline is never to prematurely optimize, but if you have two options to choose from, you should give performance SOME consideration. Picking a suitable data structure is an excellent example - if you have sorted data and you want to binary search it, use a vector and not a list. I may be just stating the obvious here :-)
Eldad Mor
Yes, touche. I would have the same gripe in that case :P
Ed Swangren
@Eldad Mor: Yes, making sure that you (and your team) fully understand a feature, including the implied performance risks, is an important skill to have. There is no point using a language feature if no one else understands it........ And I may be nitpicking here, but if you have sorted data and want to binary search it, you should be thinking about std::map, std::set, and possibly std::tr1::unordered_map. IE: the data structures that are innately sorted, so that you don't have to run a "sort" all the time. :-)
Dragontamer5788
@Dragontamer5788: You're not nitpicking, you're obviously right, I was just thinking about a binary search vs. a sequential one. In any case, I only stated it because I've actually seen programmers abusing the "don't optimize prematurely" by throwing out any performance considerations. One time I remember I told someone that a specific function would be O(N) instead of being the correct O(1), and he seriously said "yeah, but it's a small N". While this MAY actually be true in some cases, in that specific case it wasn't :-)
Eldad Mor
+3  A: 

C++ without STL

As I have seen it in my professional history, what is mostly used in game development is not C with classes, but rather C++ without STL. Some parts of STL are considered too slow for a reasonable game engine performance, most notably the streams. Other parts provide reasonable general performance, but the way memory allocation is handled is mostly unacceptable for games - this applies to string and vector libraries in most scenarios. Excellent extensive discussion of this can be found in EASTL white paper. Game developers frequently use boost or even implement their own alternative to part of whole of STL (see Game STL or EASTL).

No exceptions

There is one particular language feature which is never used in any performance critical engine parts - the exception handling. This is because on the most important game development platform (Visual Studio x86/x64) the exceptions are quite costly, and you pay some cost even when no exceptions are hit. The exceptions are avoided into the extent some development console platforms even do not provide them, or the support is known to be incomplete, unreliable and basically "broken".

Used to C

Other than that, it often happens game programmers use of C instead of C++ simply because they are used to it.

Suma
Thanks for the links. Now that you mention it I remember that sometimes over-abusing the STL facilities can lead to code bloat and slow performance (comes to mind a model loader that took 5min to read a simple model in debug mode).
LeSnip3R