On Mon, Jun 22, 2020 at 9:45 AM roger peppe <rogpe...@gmail.com> wrote:
> > > On Mon, 22 Jun 2020 at 14:26, Andrew Werner <awerne...@gmail.com> wrote: > >> Hey Rog, >> >> I think I sent you a private reply earlier. My bad. I now see what you're >> proposing in the first proposal think it makes a lot of sense. >> >> The thing that I think I had missed was this piece of magic: >> // New returns a new Dequeue instance using B as the block storage >> backing. >> func New(type T interface{}, *B Block(T))() *Dequeue(T) { >> >> One concern I have here is that the `Slice` method is on the array itself >> so it's possible that invoking it will copy the entire array to the stack. >> Does that concern you? >> > > It won't copy the array because it's a pointer method (that's what the > "*B" means). > In fact it *must not* copy the array because then it couldn't return a > reference to it. > Oh! It’s saying that *B implements the constraint, nifty. Is this idea in the current proposal? I agree that it’ll do the trick in the same way as your second proposal and my proposal and is even a bit cleaner. That being said, it still seems worse than having a mechanism to indicate that a type really is an array and can be syntactically used as such, no? I suppose if the Slice method gets reasonably inclined the compiler could do good things. I think I’m in favor of this proposal in addition to my proposal that allows a type to be declared as an array and to thus be used syntactically as an array. > The second proposal feels almost identical to what I proposed. Can you >> help me understand how it differs? >> > > It is indeed a similar approach. The main difference (beyond the somewhat > simpler dequeue implementation itself) is that my New method returns a > concrete type, not an interface. This means that it's easier to jump to the > definition of its method implementations, potentially more performant > because it can use direct rather than indirect calls (*), and arguably more > idiomatic Go. It also means it's possible to add more methods without > breaking backward compatibility, which is a significant benefit in my view. > > cheers, > rog. > > (*) you'll still get *some* indirect calls, amortised over the buffer > size. > Ack, sure, thanks for clarifying. No quibbles from me on this front. FWIW the Alloc* methods came from some experience with these data structures where I want to decode a stream of structs off the network or something so I want pointers to zero values to then pass to Decode or Unmarshal. I’ll admit it feels weird without the context. > >> >> On Monday, June 22, 2020 at 7:58:43 AM UTC-4, rog wrote: >>> >>> Thanks for the interesting use case, Andrew! >>> >>> I've experimented with a slightly different approach: >>> >>> https://go2goplay.golang.org/p/AkqzbWmpj6t >>> >>> It expects the caller to implement a method on the array to get a >>> reference to the >>> underlying storage, but that's fairly trivial to implement. >>> >>> I've built it on top of the "list" package from the stdlib (available in >>> the dev.go2go branch under src/cmd/go2go/testdata/go2path). >>> >>> I think it has the potential to be a bit more performant as the >>> type-hiding abstraction (Block in my example) is inside the static type, so >>> most calls can be direct rather than indirect. >>> >>> It's *almost* possible to make this implementation use a slice-based >>> implementation of Block too (thus allowing dynamically specified block >>> sizes at the cost of an extra indirection through the linked list node). >>> That's not possible because the pointer method is invoked on the zero-value >>> of the type, so without using a global variable, there's no way for the >>> method to find out the required block size. A way to work around that is to >>> use another level of indirection instead of using pointer methods directly: >>> >>> https://go2goplay.golang.org/p/nMreEMpqXd1 >>> >>> This makes me wonder whether the pointer methods restriction in the >>> generics draft proposal might not be unnecessary and perhaps not >>> necessarily a good idea. ISTM that the only reason for restricting methods >>> to pointer-only is so that they can be called on zero values, and that >>> necessarily restricts the available parameterisation to static-only (or >>> using global variables). I suspect that might turn out to be an >>> anti-pattern. Instead, using function or interface value that operates on >>> the pointer is strictly more general, I think, and doesn't suffer from that >>> problem. >>> >>> cheers, >>> rog. >>> >>> >>> On Sun, 21 Jun 2020 at 17:43, Andrew Werner <awer...@gmail.com> wrote: >>> >>>> Thanks for the reply! If I read it correctly, it is already possible to >>>> specify a type constraint as an Array of a given size and so the first part >>>> of the "What I might have expected" actually does work. I noticed that it >>>> works in that it works as a type constraint but I found that types >>>> constrained that way do not support indexing or slicing. Perhaps that's >>>> just a limitation of the prototype and not the proposal. >>>> >>>> The other thing that I think I'm proposing (thanks for the correction) >>>> is a way to represent all arrays of a given type: >>>> >>>> type Array(type T) interface { >>>> type [...]T >>>> } >>>> >>>> Thanks for dealing with my very long-winded way of saying that. >>>> >>>> On Sunday, June 21, 2020 at 12:35:53 PM UTC-4, David Finkel wrote: >>>>> >>>>> >>>>> >>>>> On Sat, Jun 20, 2020 at 1:22 AM Andrew Werner <awer...@gmail.com> >>>>> wrote: >>>>> >>>>>> [The body of this email is a duplication of the README in >>>>>> https://github.com/ajwerner/go2dequeue/ >>>>>> which also contains the sample implementation] >>>>>> >>>>>> Exercise building a dequeue with the go2 Type Parameter Draft >>>>>> >>>>>> This project is an exploration with the go2go / Type Parameters - >>>>>> Design Draft >>>>>> <https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md> >>>>>> with an eye towards block-based data structures which promote access >>>>>> locality. >>>>>> >>>>>> Such data structures traditionally utilize blocks of objects laid out >>>>>> contiguously in memory. In go, today, one generally specializes such data >>>>>> structures which are performance critical. This is especially important >>>>>> in >>>>>> the case of reducing allocations which occur on critical paths. Using a >>>>>> sync.Pool can help with allocations in some cases but can be clunky to >>>>>> use >>>>>> and, well, do create more pointers for GC to worry about and don't have >>>>>> the >>>>>> access locality. >>>>>> >>>>>> Whether it's really important to have data structures which include >>>>>> arrays of other data structures laid out contiguously in RAM is really >>>>>> important is sort of orthogonal. Let's just assume that it is for now and >>>>>> look at how we'd do it. We only need to consider rejecting the importance >>>>>> of this case if we can't find a good solution or idiom to support it. The >>>>>> google/btree <https://github.com/google/btree> library README links >>>>>> this Google Open Source Blog Post on block-based container >>>>>> <https://opensource.googleblog.com/2013/01/c-containers-that-save-memory-and-time.html> >>>>>> performance indicating it does matter. The interesting thing about that >>>>>> library is it hardly gets the access locality of the C++ library in the >>>>>> blog post it references. >>>>>> >>>>>> The challenge this library explores is to layout blocks in structures >>>>>> contiguously in RAM while allowing the user to have some control over >>>>>> that >>>>>> block size. >>>>>> This example >>>>>> >>>>>> The approach this experiment takes is to allow the users to specify >>>>>> the block size by providing a function to map an array type to a slice. >>>>>> The >>>>>> allows the blocks to contain a slice which references the array. The >>>>>> overhead to maintain the slice header is 3 words but the cost is probably >>>>>> less in memory and more in the optimizations the compiler will be less >>>>>> able >>>>>> to perform. In particular, it probably will need to do bounds checking on >>>>>> that slice and there are probably some opportunities to avoid the pointer >>>>>> interactions. The interface ends up being pretty reasonable though a >>>>>> little >>>>>> bit ... opaque? >>>>>> >>>>>> What this looked like in this demo is* >>>>>> >>>>>> // Say we want to create a dequeue of things.type Thing struct { Foo >>>>>> int64, Bar int64, ID [16]byte }// The below incantation will create one >>>>>> with blocks of 37 elements.d := NewWithArray(func(a *[37]Thing) []Thing >>>>>> { return (*a)[:] })// The nodes in the linked list of this structure are >>>>>> 1240 bytes, not that// that's a great number or anything like that, just >>>>>> saying it's pretty big.// Maybe there'd be something good to get out of >>>>>> aligning things are cache line// sizes. See TestNodeSize. >>>>>> >>>>>> Other things you could do Use a slice for the buffer >>>>>> >>>>>> A different approach would be to just have the array in the node, >>>>>> take a buffer size and then keep a sync.Pool or freelist of slices. >>>>>> Today's >>>>>> github.com/google/btree does this but way worse in that it just >>>>>> keeps a slice of btree.Item. So fine, it's be two objects, the nodes and >>>>>> their item buffers. Maybe that is the answer but it's pretty lame. My >>>>>> feeling is once somebody is optimizing a data structure for access >>>>>> locality >>>>>> they are trying to optimize as far as they can go. >>>>>> Ignore or augment the language generics with manual or automatic >>>>>> specialization >>>>>> >>>>>> Today, in performance critical cases, people use a variety of >>>>>> automatic or manual specialization. There are tools like >>>>>> https://github.com/cheekybits/genny or >>>>>> https://github.com/mmatczuk/go_generics. Note that the latter has >>>>>> been used to good effect in CockroachDB for building specialized >>>>>> copy-on-write, interval B-trees. >>>>>> >>>>>> One answer to this problem might just be that go's generics solution >>>>>> doesn't solve for the problem of specializing block-based data >>>>>> structures. >>>>>> That's not a particularly satisfying answer. >>>>>> What I might have expected / guess I'm proposing >>>>>> >>>>>> Type lists are a thing for utilizing language features which only >>>>>> apply to certain kinds of types. There should be a way to create a type >>>>>> list for arrays of types. >>>>>> >>>>> As a first pass I would have anticipated that there would be a way to >>>>>> inform this package of a buffer size maybe drawn from a fixed number of >>>>>> sizes. For example: >>>>>> >>>>>> type BlockSize int >>>>>> const ( >>>>>> _ BlockSize = 1 << (4*iota) >>>>>> Block16 >>>>>> Block256 >>>>>> ) >>>>>> // Array here allowstype Array(type T) interface { >>>>>> type [Block16]T, [Block256]T >>>>>> } >>>>>> func New(type T)(blockSize BlockSize) Dequeue(T) { >>>>>> >>>>>> Hmm, this function signature would require being able to return >>>>> different types based on a non-type-parameter, which would require Dequeue >>>>> to be an interface (I don't see a definition, so I'm not sure) >>>>> >>>>> From what I can tell, though, if you make the array-type a >>>>> type-parameter directly, though, this becomes workable in the current >>>>> proposal (although rather awkward for users). >>>>> e.g. >>>>> https://go2goplay.golang.org/p/UTnElflTH27 >>>>> >>>>> type ArrayT(type T) interface { >>>>> type [3]T, [4]T >>>>> } >>>>> >>>>> func NewArray(type T ArrayT(V), V interface{})() T { >>>>> var v T >>>>> return v >>>>> } >>>>> >>>> >>>> Hmm, maybe I was just holding it wrong and the proposal in "What I >>>> might have expected" already is the proposal. >>>> >>>> >>>>> >>>>> switch blockSize { >>>>>> case Block16: >>>>>> return &(dequeue(T, [Block16]T){ ... }) >>>>>> case Block256: >>>>>> return &(dequeue(T, [Block16]T){}) >>>>>> >>>>>> nit: I assume the second type-parameter for this case statement >>>>> should be Block256. >>>>> >>>>>> } >>>>>> } >>>>>> type dequeue(type T interface{}, ArrayT Array(T)) struct { >>>>>> len >>>>>> } >>>>>> type node(type T interface{}, ArrayT Array(T)) struct { >>>>>> left, right *node(T, ArrayT) >>>>>> rb ringBuf(T, ArrayT) >>>>>> } >>>>>> type ringBuf(type T interface{}, ArrayT Array(T)) struct { >>>>>> head, len int >>>>>> // buf is known to be an array type with a size of either 16 or 256 >>>>>> so >>>>>> // I'd expect to be able to interact with this like an array. I >>>>>> don't think >>>>>> // that that works today. >>>>>> buf ArrayT >>>>>> } >>>>>> >>>>>> The above also seems needlessly clunky. I'd prefer if I could just >>>>>> represent all array types like this: >>>>>> >>>>>> type Array(type T) interface { >>>>>> [...]T >>>>>> } >>>>>> >>>>>> I like that approach to distinguishing between slices and arrays in >>>>> type-lists. >>>>> Nit: I think it would still need the type keyword, so it would look >>>>> like: >>>>> >>>>> type Array(type T) interface { >>>>> type [...]T >>>>> } >>>>> >>>> Yes, definitely, my bad. >>>> >>>>> >>>>> I feel like there must be an interaction with something else that I'm >>>>> missing, though. Since there is some discussion about limitations with >>>>> respect to the lack of non-type parameters in the omissions section >>>>> <https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#omissions>. >>>>> (although not about arrays in type-list constraints, specifically) >>>>> >>>>>> Then I could do the following and everything else should just fall >>>>>> out. >>>>>> >>>>>> func New(type T interface{}, ArrayT Array(T))() Dequeue(T) { >>>>>> return &(dequeue(T, ArrayT){}) >>>>>> } >>>>>> >>>>>> The above feels to me to be compatible with the goals and idioms of >>>>>> the proposal but I could be missing out on some major complexity. Hope >>>>>> y'all find this helpful. >>>>>> Aside >>>>>> >>>>>> It would be cool to be able to embed a generic type in another >>>>>> generics type. I can see why it's annoying because the name of the field >>>>>> will get totally mangled in the specialization but maybe that's not so >>>>>> bad? >>>>>> Maybe you'd actually call the embedded type by its unspecialized type >>>>>> name >>>>>> even in the specialized version. >>>>>> >>>>>> I would have liked to embed the ringBuf in the node. Given it's not >>>>>> embedded it's almost not worth abstracting. >>>>>> >>>>> I think this is already addressed in the current design >>>>> <https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#embedding-an-instantiated-interface-type>. >>>>> You are allowed to directly embed type-parameters, and they have the name >>>>> of the template argument (rather than its instantiated value). >>>>> >>>> Cool, I'll try it out. I found it not compiling with `go tool go2go` in >>>> my first pass. Maybe I was holding it wrong. >>>> >>>>> -- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "golang-nuts" group. >>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>> send an email to golan...@googlegroups.com. >>>>>> To view this discussion on the web visit >>>>>> https://groups.google.com/d/msgid/golang-nuts/CA%2BvRuzOYXz1t6CsWLd6gxS4AErRNCwA_ctdSYiu3Mq-YH_TEDA%40mail.gmail.com >>>>>> <https://groups.google.com/d/msgid/golang-nuts/CA%2BvRuzOYXz1t6CsWLd6gxS4AErRNCwA_ctdSYiu3Mq-YH_TEDA%40mail.gmail.com?utm_medium=email&utm_source=footer> >>>>>> . >>>>>> >>>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "golang-nuts" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to golan...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/golang-nuts/2c712c9d-2a5b-44a9-b783-15585a6654b1o%40googlegroups.com >>>> <https://groups.google.com/d/msgid/golang-nuts/2c712c9d-2a5b-44a9-b783-15585a6654b1o%40googlegroups.com?utm_medium=email&utm_source=footer> >>>> . >>>> >>> -- >> You received this message because you are subscribed to the Google Groups >> "golang-nuts" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to golang-nuts+unsubscr...@googlegroups.com. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/24115910-1de9-4ae4-bfc9-c9c8ecdbf539o%40googlegroups.com >> <https://groups.google.com/d/msgid/golang-nuts/24115910-1de9-4ae4-bfc9-c9c8ecdbf539o%40googlegroups.com?utm_medium=email&utm_source=footer> >> . >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CA%2BvRuzNApZExE8TLTY-Ldk9BfGH_kqGP3S-mZVaNfaQVTDrZ5Q%40mail.gmail.com.