Sign in to follow this  
Nicolas Guillemot

Gamedevs presenting language problems to C++ standards working group

Recommended Posts

Seems reasonable.

 

It's kinda hard to propose a fixes for a language that has to be compiled down to assembly.

So far... all the complaints seem perfectly valid... except for the SoA/AoS translator. To me that just seems like laziness.

 

Plus... isn't SIMD controlled by hardware companies? Usually you need a library for that. Or at least a compiler.

Share this post


Link to post
Share on other sites

Thanks to other posters for helping summarize. smile.png

 

There are actually currently ongoing proposals for SIMD types and static reflection, so it's not as sci-fi as you may think. Mainly we're interested in making sure that those features are implemented in a way that make it possible to solve common game development problems. For example, great care has to be taken with alignment because C types only need alignment up to 8. If the language was aware of 16-aligned SIMD values, then that could become a default minimum alignment for things like new/malloc, which fixes many problems related to new and allocators and function calling and etc.
 

The SoA/AoS is just an idea for a use case of static reflection, and it happens to mesh well with SIMD. Some other languages make this possible (eg: ISPC), and the result is that programmers can easily experiment with different layouts to find optimal configurations.

Do note that N4456 is not a proposal, it's just a paper where we tried to summarize some ideas. Actual proposals or proofs of concept might come out of discusssions with this. flat_map is a candidate for making an actual proposal out of.

Share this post


Link to post
Share on other sites

Didn't want to comment TOO early after going through that list, but here's my take on them:

 

 

Very few large applications (such as games) use the global new and delete, so I don't think it is a realistic complaint. Often they use OS-specialized allocators coupled with logging or debug-tacking systems. There are so many good debug memory libraries out there, each with their own specific set of solutions. Even boost has a large number of allocation libraries. Whatever general-purpose solution the standards committee were to implement, you are certain to find groups who are unhappy with it.

 

Those issues are not new. The EASTL paper addressed most of those issues already.  The problem is one of specialization. The serious difficulty is that whenever you build a general-purpose library there will always be solutions that are a bad fit.

 

Boost does a lot of stuff with memory, but even all those variants are not a good fit universally. Sure it can handle alignment needs, but they offer allocation systems for interprocess allocators, pool allocators, small object allocators, adaptive pool allocators, synchronized allocators, and a few more; yet people still need more for custom needs.   eastl did some minor modifications to various libraries but in order to implement many of them they ended up needing to customize their allocators as well. And eastl needed to work in a world without exceptions, meaning some small but high-impact changes to how the c++ standard library handles things.

 

The desire for a few classes to be implemented in the standard library, or for some utility functions to be in the standard library, I don't see those as a good fit.  They are too special purpose. Better to throw them into a more mainstream -- but not universal -- library.  A SoA/AoS translator, SIMD types, and the rest seem like they could be better vetted through boost rather than jumping directly to the language core.

 

And as for the vtable thing, I'm not particularly certain what you would do with the vtable handle since calling functions or specializations with the vtable is implementation defined.

 

The complaint about RTTI is real but very minor, since there is a small but measurable cost to both RTTI and C++ Exceptions just by their very existence.  Unfortunately there are some edge-cases within C++ that require those solutions. There are not a lot of these problems, but even so, they exist. So asking the C++ Standards Committee to turn off the features won't happen since it breaks those corner-cases of the language.  If the feature needs RTTI and you need a compiler option to disable RTTI, those features just won't be accessible.  

 

The desire for a reflection of enumerations isn't new either, and a few suggestions have been made over the years.  But the compilation model that dates back from the early 1960s is contrary to that mode of compilation. Enumerations are named constant values that vanish during compilation. Wanting source names to survive compilation is a common feature of JIT compilation, but not the C++ object file model where ultra-compact reductions are the goal.  Microsoft managed to implement a good chunk of that in their C++/CLR compiler, but they spent a fortune doing it, it required a massive systems rewrite, they took a lot of flack for it and even with all that it still does not work universally. In a few places it is at directly cross purposes with the compilation model, so the result would be contrary to existing C++, making it a language fork.

Share this post


Link to post
Share on other sites
As they say, the first step is recognizing that there's a problem.

Michael Wong (representative of the Canadian National Body on the committee) was taking input from various game developers back at CppCon to let us air our complaints. I then put together an unofficial mailing list for a handful of us to discuss out plans for a proper paper. The original efforts centered around analyzing the old EASTL paper, which we got through most of, but still need public benchmarks. Then Work(tm) happened and I got insanely busy and didn't follow through, but Michael, Nicholas, and others thankfully took up the mantle and got this initial paper put together.

The primary focus of the paper is mostly to raise awareness in the committee and to possibly help spark the formation of a proper sub-group. Such an SG would investigate these and related issues and try to get more involvement from game companies in the committee. We are some of the biggest and most important users of the language yet we have near no representation on the committee!

There's talk of possibly combining with the real-time and embedded development sub-group in the works, since our needs are pretty similar. If all goes well, we might see a new said group formed to help shepherd related papers and foster better community interaction.

In the meantime, though, yes we need to start writing up papers for real solutions. I've got a small stack of papers that are either half-written or in early draft forms that I didn't have the time or energy to finish off for the Lenexa committee meeting, but there's always next time. Hopefully others - maybe some of you fine forum goers - step up to bat and push even more proposals and fixes!

You can get involved with the committee at https://isocpp.org/. The site includes public forums frequented by a number of committee members and has information on how to submit a paper for the committee to review.

Share this post


Link to post
Share on other sites

I'm pretty new to this sort of thing (so I might be totally wrong), but it really does seem like most of those "problems without solutions", have largely the solution of "implement it"; i.e. not very complex solutions. It seems more like philosophy or opinion gets in the way more than any lack of "proposed solutions".

Share this post


Link to post
Share on other sites

 Many of the complaints in the paper are out of date( as the follow up comment often shows), why include that stuff?

 

Wanting the language to remove global new is abit silly  

 

Regarding customizing vtables, I think typeclasses would be an improvement over the inheritance crap we have today for runtime polymorphism, so a proposal for that would be nice

 

I think the biggest improvement for games in c++17 would be modules, most of the rest of this stuff like flat_map we can already do ourselves(although it would be nice to standardize it)

 

Maybe we need to switch to Rust, it has almost none of these problems..

 

 

 

 

 

 

Share this post


Link to post
Share on other sites

Many of the complaints in the paper are out of date( as the follow up comment often shows), why include that stuff?


The original discussion that much of the paper was cut-n-paste from was an analysis pass I did over the EASTL paper, which I hadn't intended to be part of any paper, but since I disappeared from the mailing list for a while I didn't exactly give Michael much to work with.

Michael and Nicolas did a fantastic thing in putting the paper together and deserve lots of thanks for helping getting game developers' issues in front of the committee at all. Same goes to the other contributors on the unofficial real time list.

If y'all have complaints with the paper or its contents, come help contribute to the next round of papers and proposals for the meeting after Lenexa. smile.png
 

Regarding customizing vtables, I think typeclasses would be an improvement over the inheritance crap we have today for runtime polymorphism, so a proposal for that would be nice


I look forward to reading such a proposal. Perhaps you'll write one? smile.png

Share this post


Link to post
Share on other sites

since there is a small but measurable cost to both RTTI and C++ Exceptions just by their very existence

 
I would not say that exceptions have a small cost. GCC is still sometimes utterly incapable of producing code that works if you decide to use them. I would call that a serious cost, but it does not happen for everyone, just the unlucky ones, so statistically it might be small.
 
 
My proposals:
 
--- 1 ---
 
For vtables, I would suggest implementing non-export vtables. The advantage is that you would know all the implementation functions and thus be able to use a smaller integer to point to a vtable, allowing some of the functions to be compiled into devirtualized functions with top-level switch statements for implementation resolution, if they're small enough for that.
 
The downside is, of course, that you can't override such classes across dynamic module boundaries. Or, if you do, the overrides would perform as usual, not having the benefits of this optimization.
 
--- 2 ---
 
Inline calls. This can be worked around but since it's such a small issue, why not do it the right way - by adding a special attribute to the call that would inline just the function call, if possible.
 
--- 3 ---
 
Manual struct layouts. Something like...

struct large_int
{
    uint32_t high @ 0;
    uint32_t low @ 4;
    uint64_t wide @ 0; // overlaps both previous members
};

This would simplify data structures and their reinterpretation, allow to define interface structs (such as event type/data structs) easier.
 
--- 4 ---
 
An "I know what I'm doing" cast: "jiggabyte* ptr = %% &car_object.private_thing;" I realize that auto solves many of those problems (the horrible reinterpret_cast type retyping) but it doesn't solve all. For example, it is still impossible to cast from member function pointers and access private member variables. This is necessary sometimes as it happens to be a better solution than messing with the source. And, of course, an optional compiler warning could be created that prints all occurrences of this cast.
 
--- 5 ---
 
Setters and getters: defining a function with a __set__ or __get__ prefix would create a function that is triggered on a property. __get__ could return either a reference or value, to help avoid the issues that are present in C# and similar languages. Additionally, a prefix could be added for accessing such properties, to make the user acknowledge the presence of a getter (and the performance hit that may come with it).
 
--- 6 ---
 
Make externally defined operators contextually equal to internally defined ones in that they're able to return "temporary" arguments passed by reference. This is useful for logging system extensions, such that there's an externally defined operator:

Logger& operator << ( Logger& me, const Thing& t ){ magic; return me; }

It works if the operator is defined inside the class, why shouldn't it work outside? Perhaps this has been fixed and I just don't know the way to do this right but I haven't managed to find anything on this except people saying "C++ can't do this". Because it should be able to. But perhaps the next proposal would solve this.
 
--- 7 ---
 
"this" arguments. Make their member variables equivalent to local ones, as if they were coming from the called class of a method.

Logger& operator << ( this Logger& me, const Thing& t ){ me.magic(); magic(); /* < equivalent to prev. call */ return me; }

--- 8 ---

 

Standard data dumping functions, automatically generated for structs and other types that don't have them. I'd like to be able to write "dump( x )" and it would print everything about "x", whatever it is, without a debugger. Additionally, I'd like to be able to specify my own class instance of a dumper, to be able to dump wherever I want. And it's important here not to lean on RTTI or anything that would add complexity and make dumping error-prone or unavailable for some.

 

Can't think of anything else ATM. Will add it when I can.

Edited by snake5

Share this post


Link to post
Share on other sites

Related to the vtable... I always wondered why thunked virtual functions are assembled into this:

base.function:
   ...
   ret


derived1.function:  // derived1 class is derived from base
   add ecx, 8
   jmp base.function

derived2.function: // derived2 class is derived from derived1
   add ecx, 8
   jmp derived1.function

 and so on...

instead of this;

and so on... upwards
.
.
.
derived2.function: // derived2 class is derived from derived1
   add ecx, 8
derived1.function:  // derived1 class is derived from base
   add ecx, 8
base.function:
   ...
   ret

(ecx is the "this" pointer)

 

At least the MS compiler does this, no matter which optimization mode is used.

 

Also, what's the point of thunking functions imported from another library, instead of accessing them directly from the import table? It only helps hackers who might want to hook one of those functions.

Edited by tonemgub

Share this post


Link to post
Share on other sites
My major problem with the paper is that it seems to harp on the standards committee for things it does not control and intentionally does not standardize.

However before I start criticizing - I want to thank all of you for putting the time in to make a paper in the first place. We need more representation in the standard and a lot of us are under too tight NDAs and legal red tape to feel like we can contribute much (or I'm just paranoid)

Global new and delete:
This one is hard. The language needs to support allocations some how. I think it's worth pointing out that the standard library does not use global new and delete, but allows you to specify allocaters that can do a lot of what you want. So you should probably be going to your middleware providers and tell them to provide allocator support - and design your own libraries in such a way rather then asking the committee to do something they've already done.

std::function and lambdas doing allocations
I've never seen a lambda do allocations, and the implementation of both are defined by the implementer, not the standard. The standard just defines how std::function and lambdas should work. If you don't like the implementation, then talk to your compiler vendor/STL provider.

Keep in mind that type erasure always has a cost, and you literally cannot get rid of allocations in something like std::function because it had to be able to hold an arbitrarily-sized object. If you are concerned about the allocations, then use what has already been provided and give std::function a custom allocator (which we've done on our project).

RTTI
I think it's wrong to state that game developers don't use it. What would be accurate is that game developers usually write their own because the "general" solution provided by C++ has too high a cost because it has to account for language features that are rarely used (or should be rarely used, like MI).

A better solution here would be a way to customize C++ RTTI rather then telling the committee to remove it or not produce features reliant on it.

Vtables
Vtables are not standard. Nothing for the committee to do here - they simply say "virtual dispatch" has to exist and how it behaves, not how it is implemented. This is a compiler vendor concern.

Alignment
Pretty sure C++11 or 14 has had some support for this. Or maybe I'm just thinking of aligned_storage and the supporting type traits to get something's alignment.

Debuggability
Again, not something the standards committee covers. Talk to your vendors.

Small object optimizations
I don't want the standards committee to standardize these. Different platforms have different memory and cache statistics that the compiler/STL vendors should be using to inform the sizes of their objects. I don't want the committee to force something on vendors that impairs performance on current (or future) platforms.

Maybe we can make it a policy - but I'm not sure how the templating would work on that...

STL documentation/implementation details
Um... yeah... not the standards committee's job. And relying on implementation details is never a good idea. (Sometimes necessary, if you can ensure it will never change during life of the product, but still)

No allocation containers
I feel like this could be a constructor parameter - like std::noalloc or something passed in. Not default behavior.

Disabling exceptions
This might be worth some discussion - but I still feel this is a tooling question/concern. C++ has exceptions. They generally have low cost on modern compilers and hardware. And in some cases there is no other option to return error codes then using an exception (constructors).

That being said, it is probably an important discussion to have in terms of if an alternate solution for error propagation was proposed. But with a huge swath of C++ libraries and applications already built on using them I can also imagine huge pushback.

Reflection
Hrm... I like this, but I'd want it to be some form of template extensions so the reflection is available at compile time and removed at runtime. Something that could replace the stringize preprocessor operation would be nice.

SIMD
I agree that this is common enough that it should be standardized in some manner - similar to how the committee has finally gotten around to recognizing that threads are a thing. However this will not be easy as several platforms can have widely varying implementations (i.e. Intel CPU vs. PS3 SPU vs. NVidia GPU)

Share this post


Link to post
Share on other sites

I love your reply, but again several of the points can be reduced to this one line:

 


I agree that this is common enough that it should be standardized in some manner - similar to how the committee has finally gotten around to recognizing that threads are a thing. However this will not be easy as several platforms can have widely varying implementations

 

That is why nearly all of those complaints won't be addressed.  Threading was particularly interesting and has always been on the radar, but they intentionally did not want to standardize it even in the first drafts in the '90s because so many systems had wildly different behaviors.  

 

Allocators and custom behavior are system specific and generally should not be forced through the standard onto every use case.  Just like you don't want things that corporate database developers are asking for, they don't want what games are asking for.  If you don't like the behavior, build something different.  If enough people from enough industries gravitate to the solution, which is common in the boost libraries, then it will get incorporated to the standard. An example is the c++ regular expression engine. Back in the early '90s there were many different needs and many different regex language definitions. Over time people from many industries gravitated to a specific regex syntax, a common implementation emerged, and that was eventually added to the language in TR1.  The hash map and hash set similarly were discussed in early versions but were difficult to generalize to everybody; one group wants one way to hash, another wants another, a third wants a different interface. After boost had a widely-accepted implementation that multiple industries had widely adopted, it was added to the standard.

 

 

As for RTTI and exception handling, those have always been an interesting point.

 

The rules regarding exceptions and RTTI are being relaxed at many companies.  Twenty years ago when the rules were being introduced everywhere it was because exception handlers had all kinds of negative properties. Compilers generated a little bit of code in every function call for stack tracking and unwinding, and RTTI required very large tables of data and were slow to process. Since several sub-components of both share the same technology, and since the cost was present even if exceptions were never called and dynamic casts never used, both were typically off by default.  Skip to today, 2015, and compilers have advanced. There is a small table of data needed by the executable, but nowhere near as large as two decades ago. Exceptions no longer need every function call to include stack unwinding information, instead there is a lookup cost when they are used -- but games can be written in ways that never trigger c++ exceptions except through what are normally fatal bugs anyway. RTTI has been improved so that comparisons against the exact class and comparisons against the root class are nearly free, just an integer comparison, and that represents nearly all use of dynamic type info. The storage cost for the RTTI data has shrunk dramatically for nearly all compilers.  In other words, the reasons behind the commonplace ban 20 years ago has been nearly completely resolved.  

 

There are a small number of bad behaviors that can trigger slowness, but they are well known and fall into the "don't do that" category.  Making it a policy to disable both exceptions and RTTI are a strong way to enforce a DDT situation, but coupled with the advances in compiler details that have solved most of the problems, as the number of DDT situations shrinks the reason behind the policy has eroded to being nearly non-existent.

 

 

The other is concern is reflection.  If you want reflection, don't use C++. Reflection requires several tasks that are the exact opposite of what c++ is attempting to do. C++ is actively attempting to eliminate and remove the things reflections depends on.  If you need reflection compile the code that needs reflection with a different language. 

Share this post


Link to post
Share on other sites

 

--- 3 ---
 
Manual struct layouts. Something like...

struct large_int
{
    uint32_t high @ 0;
    uint32_t low @ 4;
    uint64_t wide @ 0; // overlaps both previous members
};

This would simplify data structures and their reinterpretation, allow to define interface structs (such as event type/data structs) easier.
 
--- 4 ---
 
An "I know what I'm doing" cast: "jiggabyte* ptr = %% &car_object.private_thing;" I realize that auto solves many of those problems (the horrible reinterpret_cast type retyping) but it doesn't solve all. For example, it is still impossible to cast from member function pointers and access private member variables. This is necessary sometimes as it happens to be a better solution than messing with the source. And, of course, an optional compiler warning could be created that prints all occurrences of this cast.
 
--- 5 ---
 
Setters and getters: defining a function with a __set__ or __get__ prefix would create a function that is triggered on a property. __get__ could return either a reference or value, to help avoid the issues that are present in C# and similar languages. Additionally, a prefix could be added for accessing such properties, to make the user acknowledge the presence of a getter (and the performance hit that may come with it).
 

 

3. We already have this with unions.

4. We already have "I know what I'm doing" casts with C-style casts and (I think?) function-style casts.

5. Is this really that useful? How many getters/setters are you even writing? If you really need a getter/setter, why not just write methods with the name of the variable, ie. "X/X" instead of having "GetX()"/"SetX"?

 

On lambdas and std::function:

A lot of developers seem to think that capturing lambdas allocate because std::function can heap-allocate and lambdas are often passed around with std::function. I recently had just that belief challenged in a code review at work. But lambdas and std::function aren't the same thing (the type returned by a lambda isn't a std::function, it's an anonymous type), and lambdas themselves don't heap-allocate. They don't need to since defining the lambda is defining a new type which has a size known at compile time. Using auto and lambda to define a local function, or passing a lambda to a std::algorithm should never allocate as far as I'm aware.

 

If std::function allocating is a problem, could it be useful to allow one to specify the maximum size of a captured lambda object in the std::function instance declaration at compile time? Or even better, have the compiler infer it somehow? 

Edited by Oberon_Command

Share this post


Link to post
Share on other sites

Allocators and custom behavior are system specific and generally should not be forced through the standard onto every use case. Just like you don't want things that corporate database developers are asking for, they don't want what games are asking for. If you don't like the behavior, build something different. If enough people from enough industries gravitate to the solution, which is common in the boost libraries, then it will get incorporated to the standard.


I agree here - and it is up to the library provider to provide a good "default".

But I do think that sometimes we need discussion as to putting more "customization points" into the library. For example, can we add something to a container to tell it to not allocate in the constructor without changing the type of the container, or making the container slower. (Similar to how we currently have allocators as a customization point in many containers)

I don't want to have to write a custom vector or map - but I don't mind writing a custom allocator to plug into them, cause that's much less work.

The other is concern is reflection.  If you want reflection, don't use C++. Reflection requires several tasks that are the exact opposite of what c++ is attempting to do. C++ is actively attempting to eliminate and remove the things reflections depends on.  If you need reflection compile the code that needs reflection with a different language.


I think we DO need some more compile-time reflection capabilities. I want to be able to ask an enum for it's max value without having to make a MAX_VALUE element in the enum, for example. I also want to be able to generate a string from a name of something at compile time without having to use the preprocessor stringize operator.

I don't care about actually accessing a function at runtime by name especially when that comes with the cost of not inlining a function. (I can set up a map of strings to std::function and do that if I really wanted to)

Though I'd be ok if I could flag thing as "reflected" with the expectation that it increases code size, but allows the compiler to inline/eliminate during static compiliation. But certainly not everything in my code base.
 

On lambdas and std::function:
A lot of developers seem to think that capturing lambdas allocate because std::function can heap-allocate and lambdas are often passed around with std::function. I recently had just that belief challenged in a code review at work. But lambdas and std::function aren't the same thing (the type returned by a lambda isn't a std::function, it's an anonymous type), and lambdas themselves don't heap-allocate. They don't need to since defining the lambda is defining a new type which has a size known at compile time. Using auto and lambda to define a local function, or passing a lambda to a std::algorithm should never allocate as far as I'm aware.
 
If std::function allocating is a problem, could it be useful to allow one to specify the maximum size of a captured lambda object in the std::function instance declaration at compile time? Or even better, have the compiler infer it somehow?


You could probably specify a size like you do with std::array. The problem is now the size becomes part of the type and your function objects no longer do complete type erasure. In other words, I don't want to have to increase the size of my class because I'm storing a std::function<100> on the off chance someone passes me a big function object - and you also have additional complication of copying functions between sizes - potentially causing allocations when casting to a lower size.

In our codebase we can give std::function a custom allocator which is designed for short-term small allocations which almost eliminates the allocation cost as long as you're not storing the function inside a long-lived object (and in most cases, we aren't, we use it when we can't use a templated functor type - like passing through a virtual call).

Share this post


Link to post
Share on other sites


The rules regarding exceptions and RTTI are being relaxed at many companies.  Twenty years ago when the rules were being introduced everywhere it was because exception handlers had all kinds of negative properties. Compilers generated a little bit of code in every function call for stack tracking and unwinding, and RTTI required very large tables of data and were slow to process. Since several sub-components of both share the same technology, and since the cost was present even if exceptions were never called and dynamic casts never used, both were typically off by default.  Skip to today, 2015, and compilers have advanced. There is a small table of data needed by the executable, but nowhere near as large as two decades ago. Exceptions no longer need every function call to include stack unwinding information, instead there is a lookup cost when they are used -- but games can be written in ways that never trigger c++ exceptions except through what are normally fatal bugs anyway.

 

Why aren't more console game developers using exceptions then? I'm aware that the "zero-cost" exception handling model increases the executable speed a bit and thrown exceptions are really slow, but surely that is better than locking up the console and forcing the user to reboot the entire system when an error condition occurs? From what I've seen, the typical error handling strategy is basically non-existent. Using asserts and trying to catch all the criticial bugs in debug builds is not an error handling strategy. C++ without exceptions is a broken language as far as I'm concerned.

Share this post


Link to post
Share on other sites

3. We already have this with unions.
4. We already have "I know what I'm doing" casts with C-style casts and (I think?) function-style casts.
5. Is this really that useful? How many getters/setters are you even writing? If you really need a getter/setter, why not just write methods with the name of the variable, ie. "X/X" instead of having "GetX()"/"SetX"?

 
You're not reading what I wrote (especially about #4). Besides, why in the world would you assume that I want to make C++ language proposals and not know about unions?
 
And yes, setters are useful. Mainly for being able to change implementation without changing the interface. Something C++ is horrible at. I have to modify half my code after the interface is changed, even though it was completely unnecessary.
 

Why aren't more console game developers using exceptions then?

 
Because they break builds. They just do not work at all times. I've seen function argument overwrites because of exceptions. I've seen lots and lots of internal compiler errors because of exceptions, especially with gcc -O2. That might change with LLVM/Clang because it supports them much better, but until recently, Clang for consoles was simply not an option.
 

surely that is better than locking up the console and forcing the user to reboot the entire system when an error condition occurs?

 
If a game locks up without additional information, how do you know it was from an error? Could've just been an infinite loop or a deadlock. I've seen that sometimes consoles restart themselves automatically after some time. I haven't had one of the new consoles crash on me but I do not see how the old ones, with so little RAM, could reasonably recover from a crash. Who knows what parts of it the game has reprogrammed, or if there is any chance of recovery after a crash (hardware fault). Easier to just restart.

 

 

P.S. Added #8 in my first post.

Edited by snake5

Share this post


Link to post
Share on other sites

Regarding exceptions... This is anecdotal evidence, but I've made some toy graphics applications that use exceptions, and noticed that weaker PCs would grind down to a halt if a bug caused an exception to be thrown at every frame of gameplay. I didn't figure out why, but I assume something is being totally thrown off. Makes me think games are better off just ignoring errors and just logging/asserting in debug builds instead.

I asked some emscripten people about best practices for exceptions, and the answer was something like "no dude... just... just don't." so it seems like you can't safely assume exceptions are okay universally.

Share this post


Link to post
Share on other sites

Regarding exceptions... This is anecdotal evidence, but I've made some toy graphics applications that use exceptions, and noticed that weaker PCs would grind down to a halt if a bug caused an exception to be thrown at every frame of gameplay. I didn't figure out why, but I assume something is being totally thrown off. Makes me think games are better off just ignoring errors and just logging/asserting in debug builds instead.

I asked some emscripten people about best practices for exceptions, and the answer was something like "no dude... just... just don't." so it seems like you can't safely assume exceptions are okay universally.

Exceptions should only be used in exceptional situations. Modern (x64) zero-cost exceptions are truly free (so much cheaper than return value checking) as long as no exception is thrown. As soon as an exception is thrown, it becomes a thousandfold more expensive than return checking due to massive icache misses, multiple indirections etc.

Share this post


Link to post
Share on other sites

Exceptions should only be used in exceptional situations.

 

Except nobody really uses them like that. Boost is a "great" example here. File exists? Throw! File doesn't exist? Throw! But it's not just Boost, many projects use them like that. Every action can lead to an unexpected exception, followed by a crash/slowdown.

 

If you can't "defuse" an exception quickly, what's the point? Errors can be thrown with plain functions too. Would be much cheaper/safer as well, plus you'd have access to the call stack.

 

On a side note, I wish there was some standardized way to access the call stack. It's a solved issue in any half-decent, modern language.

Edited by snake5

Share this post


Link to post
Share on other sites

 

Exceptions should only be used in exceptional situations.

 

Except nobody really uses them like that. Boost is a "great" example here. File exists? Throw! File doesn't exist? Throw! But it's not just Boost, many projects use them like that. Every action can lead to an unexpected exception, followed by a crash/slowdown.

 

If you can't "defuse" an exception quickly, what's the point? Errors can be thrown with plain functions too. Would be much cheaper/safer as well, plus you'd have access to the call stack.

 

On a side note, I wish there was some standardized way to access the call stack. It's a solved issue in any half-decent, modern language.

 

If you are talking about Boost.Filesystem, most functions in Filesystem support either using an error code or throwing an exception, see the third bullet point on http://www.boost.org/doc/libs/1_57_0/libs/filesystem/doc/index.htm

Edited by l0calh05t

Share this post


Link to post
Share on other sites

The rules regarding exceptions and RTTI are being relaxed at many companies.  Twenty years ago when the rules were being introduced everywhere it was because exception handlers had all kinds of negative properties. Compilers generated a little bit of code in every function call for stack tracking and unwinding, and RTTI required very large tables of data and were slow to process. Since several sub-components of both share the same technology, and since the cost was present even if exceptions were never called and dynamic casts never used, both were typically off by default.  Skip to today, 2015, and compilers have advanced. There is a small table of data needed by the executable, but nowhere near as large as two decades ago. Exceptions no longer need every function call to include stack unwinding information, instead there is a lookup cost when they are used -- but games can be written in ways that never trigger c++ exceptions except through what are normally fatal bugs anyway.

 
Why aren't more console game developers using exceptions then? I'm aware that the "zero-cost" exception handling model increases the executable speed a bit and thrown exceptions are really slow, but surely that is better than locking up the console and forcing the user to reboot the entire system when an error condition occurs? From what I've seen, the typical error handling strategy is basically non-existent. Using asserts and trying to catch all the criticial bugs in debug builds is not an error handling strategy. C++ without exceptions is a broken language as far as I'm concerned.


They aren't used because they used to be bad. And since they didn't used to be used, large swaths of existing code isn't written to handle them. None of that code was written with exception safety in mind (heck, it's still hard to get people to follow proper RAII practices).

Throw exceptions into the mix and you have a recipe for disaster as functions can start exiting in places the code didn't expect, leaking memory, and all sorts of other things. I would imagine that most of the "bugs" people chalk up to the compiler or "broken" exceptions are actually due to not coding in an exception-safe manner.

So until someone is willing to go through an entire code base and make sure exceptions can propagate cleanly - they will probably continue to be unused.

And no one is going to be willing to pay for a coder's time to do that - at least until someone takes their free time to remove all the "if (error-that-never-happens)" code and notices the speedup they get from removing lots of branches. (Or lack thereof)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this