December 18, 2020
On 12/17/20 1:10 PM, IGotD- wrote:
> On Thursday, 17 December 2020 at 17:46:59 UTC, Steven Schveighoffer wrote:
>>
>> This isn’t correct. Can you post the code that led you to believe this?
>>
>> -Steve
> 
> Sure.
> 
> 
> import std.algorithm;
> import std.typecons;
> import std.stdio;
> 
> 
> struct Buffer
> {
>      this(size_t size)
>      {
>          m_buffer.reserve = size;
>      }
> 
> 
>      void add(const void[] arr)
>      {
>          m_buffer ~= cast(ubyte[])arr;
>      }
> 
> 
>      string getSome()
>      {
>          if(m_buffer.length > 0)
>          {
>              return cast(string)m_buffer[0..$];
>          }
>          else
>          {
>              return "";
>          }
>      }
> 
>      void remove(size_t size)
>      {
>          m_buffer = m_buffer.remove(tuple(0, size));

Here is where your issue is. It looks like you are removing the first size elements of the array. Which moves all the rest to the front. However, the array runtime still thinks you have the original number of elements in the buffer.

You need to add:

m_buffer.assumeSafeAppend;

This tells the runtime "I'm done with all the elements that are beyond this length."

And then it will work as you expect, no reallocation.

>      }
> 
>      ubyte[] m_buffer;
> }
> 
> void main()
> {
>      Buffer b = Buffer(16);
> 
>      b.add("aa");
> 
>      writeln("b.m_buffer.length ", b.m_buffer.length, ", b.m_buffer.capacity ", b.m_buffer.capacity);
> 
>      string s = b.getSome();
> 
>      assert(s == "aa");
> 
>      b.remove(s.length);
> 
>      writeln("b.m_buffer.length ", b.m_buffer.length, ", b.m_buffer.capacity ", b.m_buffer.capacity);
> }
> 
> This will print
> 
> b.m_buffer.length 2, b.m_buffer.capacity 31
> b.m_buffer.length 0, b.m_buffer.capacity 0
> 
> capacity 0, suggests that the array has been deallocated.

This means it has 0 capacity for appending according to the runtime, NOT that the array was deallocated. This is true of non-GC allocated slices and slices which don't END at the array end.

For example:

auto arr = [1, 2, 3];

auto arr2 = arr[0 .. 2]; // slice off the last element

assert(arr2.capacity == 0);
assert(arr.capacity != 0);

Does this mean the array is deallocated? No, it means that if you append, there is no capacity to add to. A capacity of 0 means "will reallocate if you append".

Why does this happen? Because we don't want to stomp on the existing data that could still be referenced via another slice (in this case arr) that still points to the original data.

You can read a bit about the array runtime here: https://dlang.org/articles/d-array-article.html

-Steve
December 18, 2020
On 12/17/20 5:57 PM, H. S. Teoh wrote:
> On a side note, though, I find this idiosyncratic behaviour annoying
> when all I really want is to use an array as, e.g., a backing for a
> stack.  For those cases, I ignore array capacity and keep a slice over
> the entire allocated storage, including elements that have been erased,
> and keep a separate index that represents the logical end-of-array.
> While .assumeSafeAppend does work well, it does represent a druntime
> function call, which introduces a slight runtime overhead, and it does
> come with a slight performace hit.

Yeah, for quick-and-dirty stuff, runtime appending is decent. But I would much rather use an array + "valid" length for everything else, including stacks or buffers.

assumeSafeAppend is not only a druntime call, but an opaque one. Which means it will never be inlined or optimized out.

-Steve
1 2
Next ›   Last »