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.

Reply via email to