August 03, 2021

On 8/3/21 2:21 PM, jfondren wrote:

>

On Tuesday, 3 August 2021 at 18:18:48 UTC, Steven Schveighoffer wrote:

>

It's no different for length setting:

buffer = new int[100];
auto buffer2 = buffer;
buffer.length = 50;
buffer.length = 51; // must reallocate
buffer[$-1] = 10;

All of the buffer-stomping "must reallocate" cases are preserved in the rewrite that only assigns buffer.length when the new value is larger. The cases that were omitted, that were a performance drain, were same-length or less-length assignments.

There is not a reallocation on shrinking of length. Or am I misunderstanding your statement?

-Steve

August 03, 2021

On Tuesday, 3 August 2021 at 18:34:55 UTC, Steven Schveighoffer wrote:

>

On 8/3/21 2:21 PM, jfondren wrote:

>

On Tuesday, 3 August 2021 at 18:18:48 UTC, Steven Schveighoffer wrote:

>

It's no different for length setting:

buffer = new int[100];
auto buffer2 = buffer;
buffer.length = 50;
buffer.length = 51; // must reallocate
buffer[$-1] = 10;

All of the buffer-stomping "must reallocate" cases are preserved in the rewrite that only assigns buffer.length when the new value is larger. The cases that were omitted, that were a performance drain, were same-length or less-length assignments.

There is not a reallocation on shrinking of length. Or am I misunderstanding your statement?

I think you're understanding it but not believing it. Again:

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  buffer.length = length; // this is a serious bottleneck
}

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  if (buffer.length < length) {
    buffer.length = length; // this is fine actually
  }
}

The story is that the situation was improved by excluding only those cases that should not have incurred any reallocations. Every single invocation of the bottlenecking ensureHasRoom that should have reallocated would still do so with the fixed ensureHasRoom. So something else was going on.

August 03, 2021

On Tuesday, 3 August 2021 at 19:04:06 UTC, jfondren wrote:

>
void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  buffer.length = length; // this is a serious bottleneck
}

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  if (buffer.length < length) {
    buffer.length = length; // this is fine actually
  }
}

The story is that the situation was improved by excluding only those cases that should not have incurred any reallocations. Every single invocation of the bottlenecking ensureHasRoom that should have reallocated would still do so with the fixed ensureHasRoom. So something else was going on.

ah. No, I get it: this was probably shrinking and then regrowing buffer.length across separate calls, which would incur reallocations on the regrowth.

August 03, 2021

On 8/3/21 3:04 PM, jfondren wrote:

>

On Tuesday, 3 August 2021 at 18:34:55 UTC, Steven Schveighoffer wrote:

>

On 8/3/21 2:21 PM, jfondren wrote:

>

On Tuesday, 3 August 2021 at 18:18:48 UTC, Steven Schveighoffer wrote:

>

It's no different for length setting:

buffer = new int[100];
auto buffer2 = buffer;
buffer.length = 50;
buffer.length = 51; // must reallocate
buffer[$-1] = 10;

All of the buffer-stomping "must reallocate" cases are preserved in the rewrite that only assigns buffer.length when the new value is larger. The cases that were omitted, that were a performance drain, were same-length or less-length assignments.

There is not a reallocation on shrinking of length. Or am I misunderstanding your statement?

I think you're understanding it but not believing it. Again:

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
   buffer.length = length; // this is a serious bottleneck
}

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
   if (buffer.length < length) {
     buffer.length = length; // this is fine actually
   }
}

The story is that the situation was improved by excluding only those cases that should not have incurred any reallocations. Every single invocation of the bottlenecking ensureHasRoom that should have reallocated would still do so with the fixed ensureHasRoom. So something else was going on.

The allocation doesn't happen on the shrinking, it happens on the regrowing.

So e.g. you have:

int[] buf;
ensureHasRoom(buf, 10); // allocates
buf[0 .. 10] = 42;
ensureHasRoom(buf, 5); // does not reallocate in both versions
buf[0 .. 5] = 43;
ensureHasRoom(buf, 10); // Maybe reallocates (see below)
buf[0 .. 10] = 44;

So before Ali made the change, the line to regrow to 10 elements DID reallocate, because now it might stomp on data held elsewhere (the runtime has no way of knowing how many references to the array buffer there are, it just knows how much has been used).

After the change, the shrinking to 5 did not actually set the length to 5, the function left it at 10, so the "regrowing" sets the length from 10 to 10. This means no reallocation on the subsequent length change.

The serious bottleneck is on the growing (unnecessarily), not in the shrinking. Clearly, the code in question can handle a buffer that isn't the exact length, but has at least enough length. Here's how I would write the function (if the buffer had to be the exact length needed):

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  bool shouldAssume = buffer.length > length;
  buffer.length = length;
  if(shouldAssume) buffer.assumeSafeAppend;
}

which would have the same effect (though be slightly less efficient since it would still have to be an opaque call into the runtime, and possibly have to fetch block metadata).

-Steve

August 03, 2021

On 8/3/21 3:16 PM, jfondren wrote:

>

On Tuesday, 3 August 2021 at 19:04:06 UTC, jfondren wrote:

>
void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  buffer.length = length; // this is a serious bottleneck
}

void ensureHasRoom(ref ubyte[] buffer, size_t length) {
  if (buffer.length < length) {
    buffer.length = length; // this is fine actually
  }
}

The story is that the situation was improved by excluding only those cases that should not have incurred any reallocations. Every single invocation of the bottlenecking ensureHasRoom that should have reallocated would still do so with the fixed ensureHasRoom. So something else was going on.

ah. No, I get it: this was probably shrinking and then regrowing buffer.length across separate calls, which would incur reallocations on the regrowth.

Yep, correct! Sorry I didn't see this before I did the other reply.

-Steve

August 03, 2021

On Tuesday, 3 August 2021 at 19:19:46 UTC, Steven Schveighoffer wrote:

>

The allocation doesn't happen on the shrinking, it happens on the regrowing.

Aye, I had some kind of sleep deprivation mental block on this function getting called repeatedly for the same buffer with different lengths. Imagine, instead, 10k separate buffers and a single call of this function on each with a random length. In this case the reallocating calls would be the same with/without the fix.

August 03, 2021
There's some good replies here.

When I write high performance code, I use a mix of manual and GC allocation. Stuff that's critical for performance or recycling memory, I use manual. For not critical stuff, like setup and configuration code, I use the GC because it's so much more convenient.
August 03, 2021
BTW, regardless of what language you use, or use GC or not, there's no way to write a high performance program without being cognizant of its memory consumption patterns.
August 04, 2021
On Tuesday, 3 August 2021 at 19:33:27 UTC, Walter Bright wrote:
> BTW, regardless of what language you use, or use GC or not, there's no way to write a high performance program without being cognizant of its memory consumption patterns.

Hear, hear.

There have been others, but this one hit me like lightning.

I'm now talking to you, and about you to everyone else, Walter.  :)

I'm still fairly new here.  Does anyone have a Walter's Wisdoms file?  Add this one.  An ageless wisdom, well spoken.
August 04, 2021
On Wednesday, 4 August 2021 at 21:58:58 UTC, Brian Tiffin wrote:
> I'm still fairly new here.  Does anyone have a Walter's Wisdoms file?  Add this one.  An ageless wisdom, well spoken.


On bugs vs input errors:

"I believe this is a misunderstanding of what exceptions are for. "File not found" exceptions, and other errors detected in inputs, are routine and routinely recoverable.

This discussion has come up repeatedly in the last 30 years. It's root is always the same - conflating handling of input errors, and handling of bugs in the logic of the program.

The two are COMPLETELY different and dealing with them follow completely different philosophies, goals, and strategies.

Input errors are not bugs, and vice versa. There is no overlap." - Walter Bright, 2014


Programming Languages on Crack:
https://forum.dlang.org/post/saeagb$10t6$1@digitalmars.com

AST macros:
https://forum.dlang.org/post/qkpgea$1am3$1@digitalmars.com
https://forum.dlang.org/post/l6co6u$vo$1@digitalmars.com

Andrei also have "Great work is the cure for Good work"
https://forum.dlang.org/post/q7u6g1$94p$1@digitalmars.com