Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Empty Data Member Optimization: would it be possible?

In C++, most of the optimizations are derived from the as-if rule. That is, as long as the program behaves as-if no optimization had taken place, then they are valid.

The Empty Base Optimization is one such trick: in some conditions, if the base class is empty (does not have any non-static data member), then the compiler may elide its memory representation.

Apparently it seems that the standard forbids this optimization on data members, that is even if a data member is empty, it must still take at least one byte worth of place: from n3225, [class]

4 - Complete objects and member subobjects of class type shall have nonzero size.

Note: this leads to the use of private inheritance for Policy Design in order to have EBO kick in when appropriate

I was wondering if, using the as-if rule, one could still be able to perform this optimization.


edit: following a number of answers and comments, and to make it clearer what I am wondering about.

First, let me give an example:

struct Empty {};

struct Foo { Empty e; int i; };

My question is, why is sizeof(Foo) != sizeof(int) ? In particular, unless you specify some packing, chances are due to alignment issues that Foo will be twice the size of int, which seems ridiculously inflated.

Note: my question is not why is sizeof(Foo) != 0, this is not actually required by EBO either

According to C++, it is because no sub-object may have a zero size. However a base is authorized to have a zero size (EBO) therefore:

struct Bar: Empty { int i; };

is likely (thanks to EBO) to obey sizeof(Bar) == sizeof(int).

Steve Jessop seems to be of an opinion that it is so that no two sub-objects would have the same address. I thought about it, however it doesn't actually prevent the optimization in most cases:

If you have "unused" memory, then it is trivial:

struct UnusedPadding { Empty e; Empty f; double d; int i; };
// chances are that the layout will leave some memory after int

But in fact, it's even "worse" than that, because Empty space is never written to (you'd better not if EBO kicks in...) and therefore you could actually place it at an occupied place that is not the address of another object:

struct Virtual { virtual ~Virtual() {} Empty e; Empty f; int i; };
// most compilers will reserve some space for a virtual pointer!

Or, even in our original case:

struct Foo { Empty e; int i; }; // deja vu!

One could have (char*)foo.e == (char*)foo.i + 1 if all we wanted were different address.

like image 480
Matthieu M. Avatar asked Jan 07 '11 09:01

Matthieu M.


People also ask

What is empty base optimization?

Empty base optimization (EBO)Allows the size of an empty base subobject to be zero.

What is the use of empty class in C++?

An empty class could be used as a "token" defining something unique; in certain patterns, you want an implementation-agnostic representation of a unique instance, which has no value to the developer other than its uniqueness.

What is empty base?

If your question is why would you have an empty class at all (either as a member, or as a base), it is because you use its member functions. Empty means it has no data member, not that it does not have any members at all.


2 Answers

It is coming to c++20 with the [[no_unique_address]] attribute.

The proposal P0840r2 has been accepted into the draft standard. It has this example:

template<typename Key, typename Value, typename Hash, typename Pred, typename Allocator>
class hash_map {
  [[no_unique_address]] Hash hasher;
  [[no_unique_address]] Pred pred;
  [[no_unique_address]] Allocator alloc;
  Bucket *buckets;
  // ...
public:
  // ...
};
like image 59
Ozirus Avatar answered Sep 17 '22 13:09

Ozirus


Under the as-if rule:

struct A {
    EmptyThing x;
    int y;
};

A a;
assert((void*)&(a.x) != (void*)&(a.y));

The assert must not be triggered. So I don't see any benefit in secretly making x have size 0, when you'd just need to add padding to the structure anyway.

I suppose in theory a compiler could track whether pointers might be taken to the members, and make the optimization only if they definitely aren't. This would have limited use, since there'd be two different versions of the struct with different layouts: one for the optimized case and one for general code.

But for example if you create an instance of A on the stack, and do something with it that is entirely inlined (or otherwise visible to the optimizer), yes, parts of the struct could be completely omitted. This isn't specific to empty objects, though - an empty object is just a special case of an object whose storage isn't accessed, and therefore could in some situations never be allocated at all.

like image 25
Steve Jessop Avatar answered Sep 20 '22 13:09

Steve Jessop