View Single Post
  #4   Spotlight this post!  
Unread 09-07-2016, 08:08
calcmogul's Avatar
calcmogul calcmogul is offline
WPILib Developer
AKA: Tyler Veness
FRC #3512 (Spartatroniks)
Team Role: Mentor
 
Join Date: Nov 2011
Rookie Year: 2012
Location: Santa Maria, CA
Posts: 51
calcmogul is just really nicecalcmogul is just really nicecalcmogul is just really nicecalcmogul is just really nice
Re: The OpenRIO C++ Coding Guidelines for FRC

Given that this a technical discussion of C++, I thought I'd give my thoughts at length.

Quote:
Originally Posted by Jaci View Post
We're aware that the overhead is fairly small, and that they are a wrapper.
I'd say std::unique_ptr has no overhead since it's a wrapper for what one would be doing with raw owning pointers anyway. The only member variable a std::unique_ptr has is the pointer itself, and it simply calls new in the constructor, calls delete in the destructor, and copies a pointer in the move assignment operator. These are inlined by the compiler. Another benefit of using std::unique_ptr is the explicit ownership semantics which help avoid dangling pointers and double-frees (raw pointers are considered non-owning). The Rust programming language takes that explicit ownership/lifetime concept to the extreme by building it into the type system.

In general, all dynamic memory allocations and resource acquisitions (like a locking a mutex) should be contained within an RAII wrapper to handle cleanup. In modern C++, one typically never needs to use new and delete or C's malloc() and free() directly.

Quote:
Originally Posted by Jaci View Post
We've found that when developing software specifically for embedded systems, especially those on a periodic loop, we want to try and reduce the use of both locking functions and the C++ STL. The main reasoning behind this is that compilers are specifically terrible at optimizing templates and the STL.
I agree that blocking functions can be bad in real-time applications and should be minimized (what's "good enough" depends on one's deadlines and what "blocking" entails). However, poor performance of templates is a myth. When a template is instantiated with a type, all the code generation happens at compile time, and the resulting code is exactly the same as if the developer had written that implementation the normal way without templates.

It is possible to see linear growth in the executable size when instantiating a template with different types because the compiler emits an implementation for each. However, that would also happen if the code was written without templates due to the developer writing two implementations themselves. If the instantiation of two templates is the same, the compiler won't emit a second identical copy (inlining notwithstanding).

Quote:
Originally Posted by Jaci View Post
You'll often find this mindset also carried in Game Development and other applications where performance (and memory usage) is a key factor.
The STL is designed to provide generic (as in not special-purpose) implementations of common containers. Companies have developed their own STLs with custom allocators to optimize heap memory usage (like https://github.com/electronicarts/EASTL).

In addition, optimizations can be made to avoid heap allocations. For example, the GCC 5 ABI std::string supports the small string optimization, which allows small character strings to be stored on the stack inside the std::string object. LLVM also has several useful containers, and ntcore and WPILib use some of them on occasion. SmallVector (statically allocated vector), and DenseMap (map which uses contiguous storage) are a few.

Quote:
Originally Posted by Jaci View Post
The main goal around this is to avoid allocating memory after a one-time task initialization (Rule 4: heap memory). The main idea is to "allocate what you will need", even if that means over-allocating a static array.
When allocation of large objects is needed during runtime, one strategy is to allocate a block of heap memory up front, then treat that like a mini-heap to make allocations and frees from that constant time, or at least determinant. Don't forget that one can reserve space in a std::vector with reserve() so heap allocations won't occur upon insertion and deletion up to the reserved size.

std::array is an iterable replacement for a static C array.

Quote:
Originally Posted by Jaci View Post
... the code startup time during a match is far from desirable.
For C++, it only takes a few seconds at most to restart the robot code executable after it has crashed, which isn't horrible. I'm not saying that crashing is good, but FRC is more forgiving than the average hard real-time environment.

Regarding rule 6, std::lock_guard is an RAII wrapper for a mutex.
Reply With Quote