Mark M opened the meeting announcing that we will have three presenters, Ryan, Mark O and Wojciech, who will discuss specialized topics in C++. As it turned out, there was only time for Wojciech and Mark O to present and Ryan's presentation is deferred to a later specific topics meeting.

Wojciech presented first covering the topics of copy vs move, "rvalue vs lvalue" and constructors. Here are his slides:

Wojciech mentioned that these topics present an opportunity for improvements in the JEDI code where we can take advantage of move operations that will eliminate a lot of unnecessary copying. C++11 introduced the concept of move operations, including the notion of an rvalue reference, which provides the opportunity to replace copying operations with the lesser expensive moving operations.

There is a std::move() function that assists with enabling the move operations. Steve H asked about the example on slide 6 using the std::move() function where once the move takes place (transferring object C1 contents to object C4) will the C1 object then be emptied out presenting a risk to having it accessed downstream. Yes, that is the case so one has to be careful to not access C1 after the move.  The details of what happens to the original source of a move operation varies.  For example, in the case of a std::unique_ptr, the original object is set to null after the move.  However, in the case of a string, the move operation has no effect on the original object.  Still, Wojciech suggested that, as a general rule, you should not use the original object anymore after it is subject to a move operation.

In the final section of Wojciech's presentation (on constructor declarations) he mentioned that we have a lot of classes in JEDI where we can enable move operations with some simple changes. Common examples are to remove empty destructor declarations, and remove copy constructor declarations that were written to insert a tracing message (which eliminates the tracing message, but enables move operations). Two guidelines for constructor/destructor declarations is shown on the last slide and are called the Rule of Zero and the Rule of Five.   Removing these destructor and copy constructor declarations would trigger the compiler to define them automatically.   In doing so, the compiler would also define a move constructor and a move assignment operator that it would use when it could, for optimization. 

Mark M asked if you don't declare a copy constructor in your code, will the compiler declare one for you? Yes under certain conditions similar to those mentioned on slide 8 (which talks about conditions for auto-generation of move constructors). When you don't declare any of copy/move constructor/assignment operator nor destructor, then the compiler will declare all of these automatically (ie, Rule of Zero on slide 10).

Mark M also asked if the move operations will work in C++ classes where part of the object memory is allocated underneath in Fortran. Two changes were suggested by Mark O for this case. The first (recommended) is to allocate the entire object in C++ and pass a pointer to Fortran for access. The second is to use a std::unique_ptr in C++, to point to the Fortran piece. The destructor for std::unique_ptr has way to insert your own "deleter" which can then call the Fortran code to do the deallocation. The objective with both of these methods is to get to the Rule of Zero so that the compiler auto-generates the constructors and destructor declarations which then enables move operations.

Next, Mark O presented on the Rule of Five, reinforcing Wojciech's points about move operations and their potential benefits to JEDI, and on Polymorphism. Here are his slides:

Mark O reviewed the Rule of Five, and then pointed out there are two types of Polymorphism in C++. The first is called Dynamic and is implemented through class hierarchy, and the second is called Static and is implemented through templates. Dynamic offers more flexibility than Static, however Static offers advantages with performance. Mark O demonstrated these differences with an abstract Shape class that has two concrete objects: Rectangle and Square. Different examples using Dynamic and Static Polymorphism were measured for runtime performance and memory usage and indeed showed Static having an advantage in both categories.

During the talk, several concepts and utilities were introduced (e.g., std::make_shared<>(), emplace_back(), perfect forwarding) that help trim runtime and memory usage from the code.

Mark O recommended an O'Reily book:

"Effective Modern C++: 42 specific ways to improve your use of C++11 and C++14" by Scott Meyers, 2015.

Sarah asked if the performance results Mark O presented on his slides are a general principle.  Mark responded that each case is a bit different but the general message is robust, namely that templates (static polymorphism) is generally more efficient than (dynamic) polymorphism with class hierarchies and that the use of the C++11 features std::make_unique<>(), std::make_shared<>(), and perfect forwarding are more efficient than explicit calls to new.

Chris H noted that Dynamic Polymorphism allocates on the heap, whereas Static allocates on the stack, and asked if Static was risky for overflowing the stack. Chris added that getting stack allocation working well in Fortran is a nightmare (every library has to be compiled with specific compiler options) and does C++ have the same problem? As long as you avoid putting giant arrays (eg, model field) on the stack, then it should be fine. Execution speed shouldn't be adversely affected, and C++ compilers don't have the same issues with tuning the stack allocation as the Fortran compilers.

With that Mark M closed the meeting. We will be having a general update next week. The following week is the academy in Monterey, so at this time we are not sure what the plans for the weekly meeting will be. However, we will get that figured out and communicate to everyone before then.

  • No labels