top | item 39110594

(no title)

Thaliana | 2 years ago

> why is it significant that we slice like a[i:i+4:i+4] rather than just a[i:i+4]?

Well I had never seen that "full slice" expression syntax before. It turns out that it's important because it controls the capacity of the new slice. The capacity of the new slice is now i+4 - i.

So by using the full slice expression you get a slice of length 4 and capacity 4. Without doing this the capacity would be equal to the capacity of the original slice.

I suppose that by controlling the capacity that you eliminate the bounds check.

discuss

order

lifthrasiir|2 years ago

In my testing [1] that doesn't eliminate bound checks. Instead, it avoids a computation of otherwise unused `cap(a[i:i+4]) = len(a) - i` value if my reading is correct.

[1] https://go.godbolt.org/z/63n6hTGGq (original) vs. https://go.godbolt.org/z/YYPrzjxP5 (capacity not limited)

> Well I had never seen that "full slice" expression syntax before.

Go's notion of capacity is somewhat pragmatic but at the same time confusing as well. I learned the hard way that the excess capacity is always available for the sake of optimization:

    a := []int{1, 2, 3, 4, 5}
    lo, hi := a[:2], a[2:]
    lo = append(lo, 6, 7, 8)      // Oops, it tries to reuse `lo[2:5]`!
    fmt.Printf("%v %v\n", lo, hi) // Prints `[1 2 6 7 8] [6 7 8]`
While I do understand the rationale, it is too unintuitive because there is no indication of the excess capacity in this code. I would prefer `a[x:y]` to be a shorthand for `a[x:y:y]` instead. The `a[x:y:len(a)]` case is of course useful though, so maybe a different shorthand like `a[x:y:$]` can be added.

beautron|2 years ago

I think slices are wonderfully intuitive... if you've worked with C.

Slices encapsulate an ubiquitous C pattern, where you pass to a function: a pointer (to an initial array element), and a length or capacity (sometimes both). This pattern is directly encapsulated by Go's slices, which can be thought of as something like:

  type Slice struct {
      Ptr *Elem
      Len int
      Cap int
  }
I love Go's slice syntax. It's the right piece of syntactic sugar. It removes tedium and room-for-mistakes from this prevalent C pattern. It lets me work as precisely with memory as I do in C, yet everything is simpler, lighter, and breezier.

For example, I'm making a game in Go. I don't want to be allocating memory throughout the game (which can cause frame drops), so instead I allocate giant arrays of memory on launch. Then I use slices to partition this memory as needed.

Some of these arrays get their elements re-accumulated at some interval (every level, or every frame, etc.). And so it works nicely to first set them to zero length:

  myMem = myMem[:0]
Notice that the myMem slice now has zero length, but still points to its same underlying array. Then I perform the accumulation:

  for ... {
      myMem = append(myMem, elem)
  }
Again, I don't want to be allocating in general, so I care very much that append continues to use myMem's preexisting capacity.

All this is to say, I don't see slices as being the way they are for the "sake of optimization." Rather I see them as an ideal tool for working with, referring to, and partitioning memory.

brabel|2 years ago

Wow that seems pretty unsafe...

In D, for example, this works as most people would expect:

    import std.stdio;
    void main() {
      int[] a = [1, 2, 3, 4, 5];
      auto lo = a[0 .. 2], hi = a[2 .. $];
      lo ~= [6,7,8]; // new allocation
      writefln("%s %s", lo, hi);  // prints [1, 2, 6, 7, 8] [3, 4, 5]
    }
You simply can't overwrite a backing array from a slice (unless you do unsafe stuff very explicitly).

masklinn|2 years ago

This like most issues with slices stem directly from them pulling double duty as vectors.

Capacity being conserved by slicing is important to the “slice tricks” of removing items, I assume.

ok_dad|2 years ago

You’re also never increasing the size of those slices right? So it’s better memory wise by a bit and maybe faster? I last used Go a while ago, but I recall the capacity was the length of the underlying array? Internally it may even reuse the same arrays for slices maybe, since they’re not changing size each loop iteration.

Edit: weird, this was supposed to be an update to a previous comment I made, but this is a different comment now

ok_dad|2 years ago

You’re also never increasing the size of those slices right? So it’s better memory wise by a bit and maybe faster? I last used Go a while ago, but I recall the capacity was the length of the underlying array?

Edit2: (I’m throttled and I can’t post a new comment I guess? Makes me feel like my voice was stolen! I guess I’m not wanted around HN.)

Thanks for the correction, I would delete my comment but I found a bug in HN while updating it so I’ll preserve my stupidity here in duplicate for now.

costco|2 years ago

There is no copying of memory going on, the a[i: i+4] or a[i: i+4: i+4] slice references the same underlying data as in a.