String::preallocateBytes is great!


#1

I was about to complain about the missing String::reserve function when I stumbled on String::preallocateBytes. As usual Jules you are one step ahead of the game.

For my use case I need to be able to produce formatted strings for diagnostics at a time when either the program is in an inconsistent state, or when I’m being called from an i/o service handler with restrictions on what functions it can call (especially the global allocator).

This would probably also be a very good thing for typical JUCE users, especially in the AudioDeviceIOCallback.


#2

:slight_smile:


#3

What would be the feasibility of modifying the String class to have an option where the string is not allowed to grow in size or re-allocate its internal buffer? It would just silently do nothing for those cases.

I can add it myself but I wonder if it is possible…I would add bool allowReallocation to PreallocationBytes and pass the bool into the pimpl. Would I run into trouble doing this, and is it something you want for JUCE?

I’m thinking:

void String::preallocateBytes (size_t numBytesNeeded, bool allowReallocation = true);

#4

Can’t really see the point of that - surely if you’re calling that, it’s because you know exactly how many bytes you need, so you wouldn’t want it to do nothing? Perhaps a better approach would be to have a method that returns the number of allocated bytes, so you can decide whether you need to preallocate?


#5

I keep a static array of strings that have been preallocated and later on I make them available to various handlers like signal handlers, fatal error reporters, and asynchronous i/o callback functions that perform logging. So I am calling preallocateBytes from a different place than where I am actually using the strings.


#6

Also I don’t see a way to determine the preallocated size, like std::string::capacity. And how do you reset the String to be empty again, while keeping the preallocated buffer?


#7

That’s exactly what I meant in my last post.

I guess you could do this:


#8

Why doesn’t String::length guarantee a constant-time execution? It seems that since you have the pimpl (StringHolder I think), this should be easy.

Also, String::length doesn’t adhere to the definition of std::string::length, which is defined as std::distance (begin(), end()). Specifically, std::string may contain embedded nulls. While a juce::String will consider the null terminator to be the end of the string.

Or did I misunderstand the code?


#9

[quote=“TheVinn”]Why doesn’t String::length guarantee a constant-time execution? It seems that since you have the pimpl (StringHolder I think), this should be easy.

Also, String::length doesn’t adhere to the definition of std::string::length, which is defined as std::distance (begin(), end()). Specifically, std::string may contain embedded nulls. While a juce::String will consider the null terminator to be the end of the string.

Or did I misunderstand the code?[/quote]

Yes, I’ve often considered thought about trying that, but without building elaborate benchmark tests, it’s impossible to know whether it’d actually improve performance or not in the general case. And it’d take a significant amount of work to rewrite it, so I’d need some pretty good evidence that it’d be worthwhile.

I’ve also considered just using a std::string inside the juce::String as the storage layer, in the assumption that the people who write the std library will have made it as fast as possible. But that’d also just be an experiment that could easily fail - I once tried replacing the juce array in the Path class with a std::vector and performance dropped by an inexplicable 10x.


#10

regarding std::vector, were you trying in debug ?

VC has been know for having a very slow debug STL


#11

[quote=“otristan”]regarding std::vector, were you trying in debug ?

VC has been know for having a very slow debug STL[/quote]

No, I don’t think it was anything simple like that. Can’t remember the details but it was very odd. I think it must have just been an unlucky situation where it really wasn’t the right class for the job.


#12

Brother, this is a terrible idea for me! One of the reasons I love JUCE is that the code is predictable and the same for all platforms. Especially for building critical systems. If you use std::string as the storage layer, then the resulting application will have a runtime behavior that is different between platforms, or even different on the same platform depending on which version of the standard library they link with.

Having no external dependencies is a major selling point.

Now if you want to parameterize String on a template argument that decides what to use for storage, expose your StringHolder object and make that the default, and also provided a std::string wrapper whose interface is compatible with StringHolder, I would not complain!!!


#13

Yes, that’d be my main argument against it too. I’m not planning on doing it, just saying that it’s an interesting possibility!


#14

To me, String’s main advantage over stl::string is its API, which seems much better suited to quickly achieving real-world tasks than stl::string’s more ‘academic’ feel. On this basis, I shouldn’t really care how it achieves that goal, but I’d also be against using stl::string as the underlying type for String due to the opacity it would introduce, which String is blessedly free of. It’s very easy to bridge between std::string and String anyway, if std::string is a better fit in some applications.

My vote would be to add a ‘fixed’ string (derived from String and templatised on initial size) which would guarantee no memory allocations until necessary. This would be a useful programmer-guided optimisation (particularly in rendering code) which is still safe if the string needs to grow further in size, eg:

// Declare a string.  This can be declared as a stack variable - it won't need any heap allocations at all until its size needs to grow beyond the declared size
FixedString<8> Str;

// Assign a short string.  No heap allocations occur.
Str = "1235";

// Assign a longer string.  The string will need to allocate some heap memory this time.
Str = "123456789";

This sounds like it might be a specialised application of Vinn’s more generic string allocator idea. I don’t know how easy this is to achieve in the String implementation - the main concept is that the String doesn’t own its first ‘allocation’. Also, the implementation would need to be careful to avoid code bloat for many fixed string sizes.