FWIW, not that you *should* do it, but you *could* enact your original plan using unsafe: https://play.golang.org/p/W9Ntzxif_ol I don't think it's advisable though - among other things, the compiler might well conclude that `i` doesn't change in this code and eliminate the repeated loads at some point in the future, for example.
On Wed, Dec 16, 2020 at 11:22 AM Arnaud Delobelle <arno...@gmail.com> wrote: > (sorry about the code formatting gone wrong, I replied in gmail it it > seems to have removed all indentation!) > > On Wednesday, 16 December 2020 at 10:15:07 UTC Arnaud Delobelle wrote: > >> Hi Ben, that's an interesting idea. I considered it at the start but >> didn't go for it in the end (I can't remember why exactly, probably >> because that would make it quite a big struct for Lua). There is a >> possibility that I could adapt it a bit and have something like >> >> type Value struct { >> scalar uint64 >> iface interface{} >> } >> >> The type could be always obtained from the iface field (it would be >> its concrete type), but the value could be encoded in the scalar field >> for a few types such as int64, float64, bool. There would be no >> storage overhead for int64 and floa64, as the extra 8 bytes used for >> the scalar field are saved by having a "constant" iface field. The >> overhead for other non-scalar values would be only 8 bytes. >> >> I would need some reusable "dummy" interface values for the types >> encoded in the scalar: >> >> var ( >> dummyInt64 interface{} = int64(0) >> dummyFloat64 interface{} = float64(0) >> dummyBool interface{} = false >> ) >> >> Then I could create Value instances like this: >> >> func IntValue(n int64) Value { >> return Value{uint64(n), dummyInt64} >> } >> >> func FloatValue(f float64) Value { >> return Value{*(*uint64)(unsafe.Pointer(&f)), dummyFloat64} >> } >> >> func BoolValue(b bool) Value { >> var s uint64 >> if b { >> s = 1 >> } >> return Value{s, dummyBool} >> } >> >> func StringValue(s string) Value { >> return Value{iface: s} >> } >> >> func TableValue(t Table) Value { >> return Value{iface: t} >> } >> >> We could obtain the type of Values like this: >> >> type ValueType uint8 >> >> const ( >> IntType ValueType = iota >> FloatType >> BoolType >> StringType >> TableType >> ) >> >> func (v Value) Type() ValueType { >> switch v.iface.(type) { >> case int64: >> return IntType >> case float64: >> return FloatType >> case bool: >> return BoolType >> case string: >> return StringType >> case Table: >> return TableType >> default: >> panic("invalid type") >> } >> } >> >> Methods like this could extract the concrete value out a Value instance: >> >> func (v Value) AsInt() int64 { >> return int64(v.scalar) >> } >> >> func (v Value) AsFloat() float64 { >> return *(*float64)(unsafe.Pointer(&v.scalar)) >> } >> >> func (v Value) AsBool() bool { >> return v.scalar != 0 >> } >> >> func (v Value) AsString() string { >> return v.iface.(string) >> } >> >> func (v Value) AsTable() Table { >> return v.iface.(Table) >> } >> >> Interoperability with Go code is not as good but still OK. There is >> no need to maintain a pool of reusable values, which is a bonus. I'll >> have to see how much modification to the codebase it requires, but >> that sounds interesting. >> >> -- >> Arnaud >> >> On Tue, 15 Dec 2020 at 20:06, ben...@gmail.com <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 a topic in the >> Google Groups "golang-nuts" group. >> > To unsubscribe from this topic, visit >> https://groups.google.com/d/topic/golang-nuts/163s0WdXYIU/unsubscribe. >> > To unsubscribe from this group and all its topics, send an email to >> golang-nuts...@googlegroups.com. >> > To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/dcd07f38-1ead-4359-90f3-f6b514c7d541n%40googlegroups.com. >> >> > -- > 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/841a70b9-ee31-40e9-9841-adc70c008962n%40googlegroups.com > <https://groups.google.com/d/msgid/golang-nuts/841a70b9-ee31-40e9-9841-adc70c008962n%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/CAEkBMfFjgz2Fb1NFBWV0Md9VteF5gsHqkQAiXQ%3DCZY_J6hHZWQ%40mail.gmail.com.