Nice! Do you have any benchmarks on how much faster the "scalar" version is than the non-scalar?
On Tuesday, December 22, 2020 at 12:58:19 AM UTC+13 arn...@gmail.com wrote: > Just an update (in case anyone is interested!). I went for the approach > described below of having a Value type holding a scalar for quick access to > values that fit in 64 bits (ints, floats, bools) and an interface fo for > the rest. > > type Value struct { > scalar uint64 > iface interface{} > } > > That significantly decreased memory management pressure on the program for > many workloads, without having to manage a pool of say integer values. It > also had the consequence of speeding up many arithmetic operations. Thanks > all for your explanations and suggestions! > > -- > Arnaud > > On Wednesday, 16 December 2020 at 11:15:32 UTC Arnaud Delobelle wrote: > >> Ah interesting, I guess that could mean I would need to switch to using >> reflect.Value as the "value" type in the Lua runtime. I am unclear about >> the performance consequences, but I guess I could try to measure that. >> >> Also, looking at the implementation of reflect, its seems like the Value >> type I suggested in my reply to Ben [1] is a "special purpose" version of >> reflect.Value - if you squint at it from the right angle! >> >> -- >> Arnaud >> >> [1] >> type Value struct { >> scalar uint64 >> iface interface{} >> } >> On Wednesday, 16 December 2020 at 00:56:52 UTC Keith Randall wrote: >> >>> Unfortunately for you, interfaces are immutable. We can't provide a >>> means to create an interface from a pointer, because then the user can >>> modify the interface using the pointer they constructed it with (as you >>> were planning to do). >>> >>> You could use a modifiable reflect.Value for this. >>> >>> var i int64 = 77 >>> v := reflect.ValueOf(&i).Elem() >>> >>> At this point, v now has .Type() of int64, and is settable. >>> >>> Note that to get the value you can't do v.Interface().(int64), as that >>> allocates. You need to use v.Int(). >>> Of course, reflection has its own performance gotchas. It will solve >>> this problem but may surface others. >>> On Tuesday, December 15, 2020 at 12:04:54 PM UTC-8 ben...@gmail.com >>> wrote: >>> >>>> Nice project! >>>> >>>> It's a pity Go doesn't have C-like unions for cases like this (though I >>>> understand why). In my implementation of AWK in Go, I modelled the value >>>> type as a pseudo-union struct, passed by value: >>>> >>>> type value struct { >>>> typ valueType // Type of value (Null, Str, Num, NumStr) >>>> s string // String value (for typeStr) >>>> n float64 // Numeric value (for typeNum and typeNumStr) >>>> } >>>> >>>> Code here: >>>> https://github.com/benhoyt/goawk/blob/22bd82c92461cedfd02aa7b8fe1fbebd697d59b5/interp/value.go#L22-L27 >>>> >>>> Initially I actually used "type Value interface{}" as well, but I >>>> switched to the above primarily to model the funky AWK "numeric string" >>>> concept. However, I seem to recall that it had a significant performance >>>> benefit too, as passing everything by value avoided a number of >>>> allocations. >>>> >>>> Lua has more types to deal with, but you could try something similar. >>>> Or maybe include int64 (for bool as well) and string fields, and >>>> everything >>>> else falls back to interface{}? It'd be a fairly large struct, so not sure >>>> it would help ... you'd have to benchmark it. But I'm thinking something >>>> like this: >>>> >>>> type Value struct { >>>> typ valueType >>>> i int64 // for typ = bool, integer >>>> s string // for typ = string >>>> v interface{} // for typ = float, other >>>> } >>>> >>>> -Ben >>>> >>>> On Wednesday, December 16, 2020 at 6:50:05 AM UTC+13 arn...@gmail.com >>>> wrote: >>>> >>>>> Hi >>>>> >>>>> The context for this question is that I am working on a pure Go >>>>> implementation of Lua [1] (as a personal project). Now that it is more >>>>> or >>>>> less functionally complete, I am using pprof to see what the main CPU >>>>> bottlenecks are, and it turns out that they are around memory management. >>>>> >>>>> The first one was to do with allocating and collecting Lua "stack frame" >>>>> data, which I improved by having add-hoc pools for such objects. >>>>> >>>>> The second one is the one that is giving me some trouble. Lua is a >>>>> so-called "dynamically typed" language, i.e. values are typed but >>>>> variables >>>>> are not. So for easy interoperability with Go I implemented Lua values >>>>> with the type >>>>> >>>>> // Go code >>>>> type Value interface{} >>>>> >>>>> The scalar Lua types are simply implemented as int64, float64, bool, >>>>> string with their type "erased" by putting them in a Value interface. >>>>> The >>>>> problem is that the Lua runtime creates a great number of short lived >>>>> Value >>>>> instances. E.g. >>>>> >>>>> -- Lua code >>>>> for i = 0, 1000000000 do >>>>> n = n + i >>>>> end >>>>> >>>>> When executing this code, the Lua runtime will put the values 0 to 1 >>>>> billion into the register associated with the variable "i" (say, r_i). >>>>> But >>>>> because r_i contains a Value, each integer is converted to an interface >>>>> which triggers a memory allocation. The critical functions in the Go >>>>> runtime seem to be convT64 and mallocgc. >>>>> >>>>> I am not sure how to deal with this issue. I cannot easily create a >>>>> pool of available values because Go presents say Value(int64(1000)) as an >>>>> immutable object to me, so I cannot keep it around for later use to hold >>>>> the integer 1001. To be more explicit >>>>> >>>>> // Go code >>>>> i := int64(1000) >>>>> v := Value(i) // This triggers an allocation (because the >>>>> interface needs a pointer) >>>>> // Here the Lua runtime can work with v (containing 1000) >>>>> j := i + 1 >>>>> // Even though v contains a pointer to a heap location, I cannot >>>>> modify it >>>>> v := Value(j) // This triggers another allocation >>>>> // Here the Lua runtime can work with v (containing 1001) >>>>> >>>>> >>>>> I could perhaps use a pointer to an integer to make a Value out of. >>>>> This would allow reuse of the heap location. >>>>> >>>>> // Go code >>>>> p :=new(int64) // Explicit allocation >>>>> vp := Value(p) >>>>> i :=int64(1000) >>>>> *p = i // No allocation >>>>> // Here the Lua runtime can work with vp (contaning 1000) >>>>> j := i + 1 >>>>> *p = j // No allocation >>>>> // Here the Lua runtime can work with vp (containing 1001) >>>>> >>>>> But the issue with this is that Go interoperability is not so good, as >>>>> Go int64 now map to (interfaces holding) *int64 in the Lua runtime. >>>>> >>>>> However, as I understand it, in reality interfaces holding an int64 >>>>> and an *int64 both contain the same thing (with a different type >>>>> annotation): a pointer to an int64. >>>>> >>>>> Imagine that if somehow I had a function that can turn an *int64 to a >>>>> Value holding an int64 (and vice-versa): >>>>> >>>>> func Int64PointerToInt64Iface(p *int16) interface{} { >>>>> // returns an interface that has concrete type int64, and >>>>> points at p >>>>> } >>>>> >>>>> func int64IfaceToInt64Pointer(v interface{}) *int64 { >>>>> // returns the pointer that v holds >>>>> } >>>>> >>>>> then I would be able to "pool" the allocations as follows: >>>>> >>>>> func NewIntValue(n int64) Value { >>>>> v = getFromPool() >>>>> if p == nil { >>>>> return Value(n) >>>>> } >>>>> *p = n >>>>> return Int64PointerToint64Iface(p) >>>>> } >>>>> >>>>> func ReleaseIntValue(v Value) { >>>>> addToPool(Int64IPointerFromInt64Iface(v)) >>>>> } >>>>> >>>>> func getFromPool() *int64 { >>>>> // returns nil if there is no available pointer in the pool >>>>> } >>>>> >>>>> func addToPool(p *int64) { >>>>> // May add p to the pool if there is spare capacity. >>>>> } >>>>> >>>>> I am sure that this must leak an abstraction and that there are good >>>>> reasons why this may be dangerous or impossible, but I don't know what >>>>> the >>>>> specific issues are. Could someone enlighten me? >>>>> >>>>> Or even better, would there be a different way of modelling Lua values >>>>> that would allow good Go interoperability and allow controlling heap >>>>> allocations? >>>>> >>>>> If you got to this point, thank you for reading! >>>>> >>>>> Arnaud Delobelle >>>>> >>>>> [1] https://github.com/arnodel/golua >>>>> >>>> -- 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/b73da83e-18f7-4348-8753-7e985896edacn%40googlegroups.com.