I've gained 5.97% (9584 bytes) of flash and 8.29% (1696 bytes) of RAM, mainly by completely removing usage of virtual functions, but probably also because I've moved to C++23 and used its features and updated GCC to the latest stable version (because C++23 is still a bit under development on the GCC/Clang side).
In this post I will give a cool example of how I've avoided virtual functions and used (a bit pointlessly but it's in compilation time) modern C++23 features.
The thing is that I've structured my application into three layers: BusinessLogic, Device and Driver. All HAL related code is in Driver. There I have for example SdCardDriver, UartDriver and other classes that wrap/hide low level hardware subjects.
Previously I've created for each of such class an abstraction class, so I had ISdCardDriver, IUartDriver etc with all pure virtual methods.
For the build of the firmware, the Driver's classes inherited from it (and implemented its methods using HAL's methods). For the build of PC simulation (the firmware for testing and simulation can be run on PC, no need for hardware, I've presented this in previous entries) I have a different set of Drivers that implemented the same abstraction classes (and they provide mocks too).
Depending on whether I was building binary for ARM or binary for PC I've switched the compiler and the Driver folder that I used so the correct set of drivers would be used.
So far so good, it works and is correct, but now all those methods are virtual and it adds a bit of time of execution and binary size (under the hood compiler adds virtual table and in runtime checks what methods it should call).
I've realized that I could do better :)
Now I don't have those abstraction classes at all, I didn't need them. Build for ARM has its folder with hpp/cpp that uses HAL and build for PC has its own too. The idea of switching folder with drivers depending on build type stays the same. Common logic (like enums etc.) that are used by both AR/PC variants were extracted to common headers so that there is no duplication.
And that could be the end but C++23 has a nice feature... concepts.
An example of concept from project's code:
template <typename T>
concept SdCardDriverConcept =
std::derived_from<T, DriverComponent> &&
requires(T driver,
std::string_view filename,
FileOpenMode mode,
std::span<const std::uint8_t> data) {
// File operations
{ driver.openFile(filename, mode) } noexcept -> std::same_as<SdCardStatus>;
{ driver.write(data) } noexcept -> std::same_as<SdCardStatus>;
{ driver.closeFile() } noexcept -> std::same_as<SdCardStatus>;
};
For each driver I've added concept that checks (during compilation) if the driver implements all needed methods, for example:
class SdCardDriver : public DriverComponent
{
//...
};
static_assert(Driver::Concepts::SdCardDriverConcept<SdCardDriver>,
"SdCardDriverConcept must satisfy the concept requirements");
It's a static assertion. It's not needed here, if one class doesn't implement all that is needed the build would fail anyway, anyway nice to have it.
Before all those optimization:
Memory region Used Size Region Size %age Used
RAM: 6608 B 20 KB 32.27%
FLASH: 35916 B 128 KB 27.40%After:
[22/22] Linking CXX executable HardwareDataLogger_STM32F103RBTx.elf
Memory region Used Size Region Size %age Used
RAM: 4912 B 20 KB 23.98%
FLASH: 28088 B 128 KB 21.43%
Robert Gawron
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.