What's next level for C++? I think it should be something help scaling the performance and robustness to industrial standard level...
Background
So this article just lists what I believe should be important for level 2 C++ programmers. Most of them are probably biased to game dev and are inspired a lot from either Boost, Folly, EASTL etc. Even though leaning to game dev, but I think most of cure for problem are commonly shared across similar problems in different field.
Language Tricks
You know what: branching can be optimized! For game developer, every bit count, even probability. [[likely]] newly added to C++ std will help compiler generate better asm code to predict branching. Some library can do that before this C++ std.
ScopeGuard<Function or Lambda> : use destructor to guarantee cleanup in throwing environment and use function to avoid overhead (0 overhead actually).
Compile-Time calculation: Meta programming allows you to get Fib<100>() like a constant after compiled; also it enable you to check type Traits.h without any runtime cost.
Copyable Function:
replacement new: new(pointer) Class, note you need to free your pointer (responeding to the way it created) and call destructor.
static_assert: check compile time values, template etc.
cache friendly programming tricks: abusing hardware. this is like dark alchemy: https://stackoverflow.com/questions/16699247/what-is-a-cache-friendly-code Note you need to know your hardware and compiler well and use it for bottleneck's bottleneck. It's often not transferrable when moving to different devices...
noexcept can be helpful in case of moving etc.
Memory Allocation
Arena Allocation. Common in game engine. Make memory allocation as raw as C. No destructor, only pointer moving! Reference: "New objects are allocated out of a large piece of preallocated memory called the arena. Objects can all be freed at once by discarding the entire arena, ideally without running destructors of any contained object (though an arena can still maintain a "destructor list" when required). This makes object allocation faster by reducing it to a simple pointer increment, and makes deallocation almost free. Arena allocation also provides greater cache efficiency: when messages are parsed, they are more likely to be allocated in continuous memory, which makes traversing messages more likely to hit hot cache lines."
For game, one arena per levels; while for server, one arena for one request session. This idea also works for IO like serialization, basically save the overhead of default C++.
Rellocation
realloc is raw, rellocable is to deal with the constructor/destructor/memcpy issues.
String
std::string
is simple but I've never seen a game engine in my life that use it 😆 (actually true for STL as well). I think game developer follows the string pool idea. It's so called interned string
, a huge hash table of string to make sure every string only have one copy (also see here). It's efficient for game's string which doesn't really change much. Combined with some C++ tricks you can basically treat any constant string in C++ as a HashID.
For runtime, library like Folly chooses to handle string in more wild use cases with three strategies based on size of string. Small string in static stack memory. Large size string is lazily copied or somehow interned. Medium string is equivalent to std::string
.
Lock Free
You should always use lock free or non-blocking concurrency strategy. That means better stay away from mutex, semaphore etc. Why? cause you don't want to spend cycles on thread context management (put them sleep and signal them awake) and many bugs like dead lock. But how? Use atomicity. Careful design your datastructure and logic. For competing resource e.g. multiple writers, spin lock it and try CompareAndSwap (or waitandwrite).
However, this is not Elixir. CompareAndSwap (CAS) fails is a usual things in high contention environment and if thread is trying to do heavy works for CAS, it'll be disaster to see failed threads repeating the heavy works due to CAS failure. Plus, the lock free
Future & Executor
Javascript is a good place to experience Future and Promise. It's able to chaining callback and help you manage worker thread easier.
The way to make it efficient is based on implementation of threadpool. A typical one would be Folly::CPUThreadPoolExecutor
, where caller just create tasks in priority queue and workers in threadpool keeps polling the queue and execute the tasks.
STL
To be short, don't use it in game runtime. It's too general to be fast enough. Use the something from e.g. Folly.
Exception
Return by value and exception happens! share_ptr
to rescue.
Intrusive vs Non-intrusive
Think about 2 different e.g. vector, one you store vector<MyClass>
and another you store vector<MyClass*>
. For latter one, you saved the cost of copy constructing elements by only copying pointers. Intrusive container works like this.
You basically inherit intrusive container node to create your object class or follows the interface.
class MyClass: IntrusiveContainerNode<MyClass>
{
// from parent: MyClass *next;
// from parent: MyClass *previous;
int value;
};
https://www.boost.org/doc/libs/1_35_0/doc/html/intrusive/intrusive_vs_nontrusive.html#id932296