How often to call Path::preallocateSpace()?

I’m using paths to draw a frequency spectrum in a plugin. Since the path is being constantly repainted every frame and a lot of points are being added to the paths each time I call preallocateSpace() to improve performance.

However I’m not sure how often I should call preallocateSpace(). At the moment I have something like this:

void updatePath()
{
    path.clear();
    path.preallocateSpace(numPoints * 3); // numPoints is the number of times lineTo() will be called
    
    // ...
    // add lines to the path
    // ...
    
    repaint();
}

In short, the question I’m asking is do I need to call preallocateSpace() after every time I call clear() or do I only need to call it when the value of numPoints changes?

Clear() does not de-allocate. Do it only once, for instance in a resize() callback.

2 Likes

That’s perfect, thankyou! :slight_smile:

1 Like

In most IDE’s Ctrl/Cmd click on a Juce method can show you the implementation! :wink:

But don’t trust it blindly, XCode meant, the values in the path would be a StringArray… :smiley:
It sent me to StringArray::clear()

1 Like

That’s Doh… :wink: clear() calls clearQuick(), with the comment:

    /** Removes all elements from the array without freeing the array's allocated storage.
    @see clear
    */

Thanks, sure, I went to the type of values and scrolled to clear() manually.
But you answered meanwhile already :wink:

You’re right I shouldn’t be lazy!

I did a little digging as to what goes on when Path::clear() is called and Path::preallocateSpace() and yes, it looks like clear() never does any resizing anywhere…

Path::clear() eventually calls Array::deleteAllElements():

inline void deleteAllElements() noexcept
{
    for (int i = 0; i < numUsed; ++i)
        data.elements[i].~ElementType();
}

Which calls the destructor on all the elements. I’d never seen this behavior before - calling a destructor to delete an element - so I made a quick dummy program to test it but couldn’t get it to actually delete any values?

template <typename T>
struct Test
{
    void clear()
    {
        data.~T();
    }

    T data;
};

int main()
{
    Test<float> t;
    t.data = 10.f;
    std::cout << t.data << std::endl;
    t.clear();
    std::cout << t.data << std::endl;
    return 0;
}

The output from main is:

10
10

Have I misunderstood something or does this just magically work in JUCE but not for me? Going off topic from my original question but can someone shed some light as to how this works?

The Juce array is of course a multi-purpose class.
My guess is that Path elements simply do not have destructors and thus no de-allocation takes place in the Path context.

ElementType in this case is a float and since floats don’t have destrctors… does that mean deleteAllElements() in this case has no effect?

I think, this is the trickery to keep the space allocated, but destroy the elements properly.
By setting the numUsed, the result should be always the expected one, since you are not accessing the elements after numUsed.

But @t0m is expert on the Array class, he can certainly explain more/better.

As far as I know, directly calling the destructor should only be done after having used the placement new operator to create an object. I only know this since I came across a very special use case for placement new some time ago.

I just had a quick look at the Array source code, it seems that it indeed uses placement new to place new elements into the preallocated memory block used by the array (which is managed by the ArrayAllocationBase data member, which uses a simple HeapBlock for raw memory allocation).

For those who haven’t heard of placement new (like me some month ago):

The usual way of using new SomeObject would lead to some heap allocation first and then would create the object in the allocated memory location. Calling delete later on that object (or better let your smart pointer go out of scope :wink:) will lead to first calling the destructor and then freeing the heap memory.

With placement new, you supply a self-managed memory location yourself and just create a new instance of the object there without any previous allocation. This could be stack or heap memory. Now as this memory is managed by the user, calling delete would be no good idea. Explicitly calling the destructor in this case just leads to a clean destruction routine of the class and then gives you the possibility and responsibility to re-use or free the memory for whatever you want to do with it after that.

So this should also make clear why the code snippet above doesn’t actually reset the float to any value, as @Im_Jimmi said, float as a POD has no destructor.

TBH, the Array class seems to be one of the most senseful pieces of example code for placement new I came across so far :wink: Hope that helps clearing up some peoples questions here and doesn’t sound even more confusing :stuck_out_tongue:

3 Likes