3 ms·
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
by lifthrasiir 3y 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 https://go.godbolt.org/z/63n6hTGGq (original) vs. https://go.godbolt.org/z/YYPrzjxP5 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.
- masklinn 3y agoThis 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.
- beautron 3y agoI 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.
- lifthrasiir 3y agoC doesn't have any slicing operator like Go's `a[x:y]`, which is the main problem I want to point out. Slice itself is just a natural construction.
- beautron 3y agoYes, I'm saying Go's slice operator (and slice type, and related functions) grew out of experience from the way arrays are used in C. To me, this becomes obvious if you read The Practice of Programming by Kernighan and Pike, the latter of which was a co-designer of Go. If you read the book, which was written well before Go, and pay attention to how it uses C arrays, you can almost feel Go slices emerging. The slice syntax perfectly encapsulates the C array bookkeeping.
- lifthrasiir 3y agoI'm not sure how it can be possible. In my experience the notion of three-part slices does exist in C but only implicitly. For example, size_t trim_end(const char *p, size_t len) { while (len > 0) { if (p[len - 1] != ' ') break; --len; } return len; } Conceptually this function accepts a slice `(p, len, cap)` and returns a slice `(p, len2, cap)` where `len2 <= len` and the capacity never changes. But the actual argument doesn't have `cap`, and the return argument doesn't have `p`. Everything is implicit and it's typical for C programmers to fully document and follow such implicits. Go's slice operator can't come out of such implicit practices in my opinion. In comparison, your claim only makes sense when the following was somehow normal: struct slice { const char *p; size_t len, cap; }; struct slice trim_end(const struct slice *s) { struct slice out = *s; while (out.len > 0) { if (out.p[out.len - 1] != ' ') break; out = subslice(out, 0, out.len - 1); } return out; } Note that a hypothetical `subslice` function call maps perfectly to a Go code `out[0:len(out)-1]`, and my complaint will equally apply: there should be two clearly named variants of `subslice` that may or may not keep the capacity. But I hardly saw such construction in C.
- 3y ago
- brabel 3y agoWow 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).