0

I have implemented a function that resets the content of the structure to which a pointer points:

template <typename Struct>
void initialize(Struct* s)
{
    *s = Struct{};
}

I have performance issues when Struct becomes big (above 10K) because Struct is created in the stack and then assigned to *s. I was wondering if I could improve it:

  1. Is it possible to initialize directly *s without the temporary Struct{} object?
  2. Should I instead evaluate the size of Struct and build it in the heap if it is big?

Thank you in advance

JaMiT
  • 14,422
  • 4
  • 15
  • 31
Victor
  • 460
  • 3
  • 11
  • 1
    Have a static one and assign that? (it would be created once and then you'd just do the copy). – Borgleader Mar 04 '21 at 01:55
  • That would spare the creation of the object but not the storage in memory, which will also be allocated in stack. – Victor Mar 04 '21 at 01:57
  • What is possible depends on `Struct`. What are we allowed to assume about `Struct`? If `Struct` has something akin to a `clear()` method, then yes there is a better way to re-initialize the object. (Before you object to placing restrictions on `Struct`, remind yourself that your current code already places a restriction, specifically that `Struct` is default constructible.) – JaMiT Mar 04 '21 at 02:01
  • Since you don't check for `s` being null, you might as well have the function take a reference to give the calling code more freedom at no cost. – JaMiT Mar 04 '21 at 02:03
  • 1
    The "content of a pointer" is the address of an object. In the title you should be saying "Reset directly a struct being pointed to by a pointer" or similar – M.M Mar 04 '21 at 02:22
  • I need to clarify to everyone that I have no option to add a `clear()` method because these structs come from external interfaces and pre-compiled headers. – Victor Mar 04 '21 at 02:28
  • 1
    @Victor "*That would spare the creation of the object but not the storage in memory, which will also be allocated in stack*" - `static` variables are not generally stored in stack memory. – Remy Lebeau Mar 04 '21 at 02:34
  • @RemyLebeau and @Borgleader That is true. However, would be worth the cost of storing in memory more than 10K of 0s in the `data` section of the program? – Victor Mar 04 '21 at 02:57

1 Answers1

1

Firstly, you should probably use a reference; not a pointer. The point of this is to avoid null indirection bugs.

If the class is trivial and value initialise to zero (which is typically the case for most trivial types), then the optimiser should compile your function into a call to memset, without any need for initialisation of a temporary object. So there should be no reason to worry in that case.

You could call memset explicitly, although that is technically not portable to exotic systems in case the class contains certain types (for example, null pointer does not necessarily have the representation of zero).

Is it possible to initialize directly *s without the temporary Struct{} object?.

Yes, if you're willing to change the requirements of the function. Currently it works for classes that are default constructible and move assignable.

You can avoid creation of a temporary object if you modify the pointed object directly. In following example, there are no temporaries of type Struct created:

constexpr void
initialize(Struct& s)
{
    s.member1 = T1{};
    s.member2 = T2{};

To make this generic, the operation could be performed in a member function. Thus, you could specify a requirement that the pointed class has a member function with particular name and no parameters:

s.clear();

You can combine both approaches for types which they apply to:

template<class Struct>
constexpr void
initialize(Struct& s)
{
    if constexpr (std::is_trivially_copyable_v<Struct>) {
        // alternative 1, if you trust your optimiser
        s = Struct{};
        // alternative 2, if you doubt the quality of the optimiser
        //    technically may have different meaning on exotic systems
        std::memset(&s, 0, sizeof s);
    } else {
        s.clear();
    }
}

If you need this to work with some classes that conforms to neither requirement, then you'll need to specialise the template.

Should I instead evaluate the size of Struct and build it in the heap if it is big [10K]?

You generally should avoid having public classes that large entirely. If you need such large storage, you could wrap it in a type that allocates it dynamically. Something like this:

class Struct
{
private:
    struct VeryLarge{/.../};
    std::unique_ptr<VeryLarge> storage;
public:
    // public interface
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • To be tricky, you could define `initialize(x)` using SFINAE to call `x.clear()` if such function exists, otherwise execute `x = X{}` – M.M Mar 04 '21 at 02:25
  • @M.M I prefer my new suggestion of conditioning on triviality of the class. This way the function never constructs a non-trivial temporary which is a good property to have. Sometimes failure to compile can be more useful than generality. – eerorika Mar 04 '21 at 02:28
  • Are you sure that `(Struct* s) { *s = {} }` and `(Struct& s) { s = {} }` are optimized differently? The `*` operation is only a memory deferencing at instruction level. – Victor Mar 04 '21 at 02:30
  • @Victor No. The reason to use a reference has little to do with optimisation. It's to avoid null indirection bugs. Note that I had a copy-paste error in my examples, which may have been misleading. It's fixed now. – eerorika Mar 04 '21 at 02:31
  • `s = {}` should be `s = Struct{}` as the former will match `operator=(int)` ahead of built-in move assignment – M.M Mar 04 '21 at 03:04
  • there's also the issue of `memset` to `0` not being the same as value-initialization for some trivially-copyable types – M.M Mar 04 '21 at 03:05
  • @M.M Thanks. Good catches. – eerorika Mar 04 '21 at 03:09
  • I have tried with `memset` and compiler warns me: `clearing an object of non-trivial type ‘struct MyStruct’; use assignment or value-initialization instead [-Werror=class-memaccess]`. I also do not want to do a low-level reset, because a struct might have some default values. Optimization tools also warn me that assignment `s = Struct{};` consumes stack memory. Is there any other option? – Victor Mar 09 '21 at 05:49
  • @Victor Your best option is to define a member function to clear the class. – eerorika Mar 09 '21 at 05:55