Saving RAM with PONUA

If you follow what’s going on github, you may have seen a lot of PONUA being added all over the style templates, and wondered what that’s all about. If you didn’t you should probably stop reading now.

I was working on some optimizations, when I noticed that styles take up more RAM (which is different from flash memory) than I expected. After poking around a bit, I realized that empty classes (classes which have no variables) still take up one byte for some reason. One byte might not seem like much, but in our styles, we may have hundreds of instances of empty classes, each taking up one byte, so it adds up.

Here is an example of an “empty class”

template<int N>
class Int {
  void run(BladeBase* blade) {}
  int getInteger(int led) { return N; }
// NO VARIABLES HERE
}

So whenever we use an Int<> in a style, we are using an empty class. Now, you might think that you don’t really use Int<> that much, but it turns out that almost all the commonly used style elements expand to something that uses Int<> one or more times.

For instance, your style might use Percent<SOMETHING, 30>. This expands to Mult<SOMETHING, Int<32768 * 30 / 100>>. If we take a look at the Mult class, it looks like this:

template<class F, class V>
class Mult {
public:
  void run(BladeBase* blade) {
    f_.run(blade);
    v_.run(blade);
  }
  int getInteger(int led) {
    return (f_.getInteger(led) * v_.getInteger(led)) >> 15;
  }
private:
  F f_;
  V v_;
};

So in this case V is an Int<>, which is an empty class. Because of the way C++ works, the high lords of C++ has decided that no two objects can have the same address. Unfortunately, if an object has zero size, the next object would be in the same spot, so therefore, they decided that no object can have a size of zero. (Apparently, this debate has raged for a long time, so I’m not going to dig deeper into why it’s like this…) So what happens here is that V gets a size of 1 instead of 0, and uses up one byte of RAM. Due to padding, this might become 4 bytes, and the style code does this sort of thing a lot…

Fortunately, C++20 has an attribute that can allow an object to be zero bytes long. Basically, by putting this attribute on there, you’re saying “I don’t care if it has the same address as some other object”, which is why this attribute is called “no_unique_address”. In C++ code, the attribute is written as [[no_unique_address]]. Not only is this sort of long and incomprehensible, but compilers that don’t understand C++20 will issue lots of warnings when trying to compile code that has this attribute in it.

Luckily, the preprocessor knows if the compiler supports this attribute or not, so it’s possible to add this code somewhere:

#if __has_cpp_attribute(no_unique_address)
#define PONUA [[no_unique_address]]
#else
#define PONUA
#endif

This creates a define that expands to [[no_unique_address]] if that works, and nothing if it doesn’t. Technically it should be called PROFFIEOS_NO_UNIQUE_ADDRESS, but it was so long and unwieldy to type that I decided to abbreviate it to PONUA. So now we can apply this to the Mult class:

template<class F, class V>
class Mult {
public:
  void run(BladeBase* blade) {
    f_.run(blade);
    v_.run(blade);
  }
  int getInteger(int led) {
    return (f_.getInteger(led) * v_.getInteger(led)) >> 15;
  }
private:
  PONUA F f_;
  PONUA V v_;
};

It turns out that the arduino-proffieos plugin uses a compiler that is new enough to understand the no_unique_address attribute, so by doing this V will no longer take up any RAM space, and by applying this all over the code, we can save quite a few bytes.

PONUA only makes sense where the variable is/can be an empty class. It doesn’t really hurt putting it anywhere else, but it also doesn’t do anything.

4 Likes

does this explain why my configuration on 5.9 is 96% but on yesterdays pull of the proffieos is 75%? I’m gonna have some issues trying to fill up the configuration to 99%

Nope. That is flash memory, which is different than RAM.
I don’t think I’ve done anything recently that reduces flash memory usage.
You sure you didn’t switch optimization from “fastest” to “smallest” or something?

2 Likes

just checked, yes it is on smallest rather then fastest but that’s true of the other 5.9 sketch. I added the #define DISABLE_BASIC_PARSER_STYLES and that saved a fair bit of memory. oh and I am using the new arduino plugin as well.

regardless this is great work, this should improve on performance of the swings and other effects right?

Nope, it just raises the bar for how complex a style can be before it makes proffieos run out of memory and stop working.

The explanation is stellar and clear, Yes, I was totally wondering what PONUA was, especially when Google turned up nothing :slight_smile:
Thanks.

2 Likes