Luckily, I have the "no scalar" version with a build tag.  Here is a simple 
benchmark:

func BenchmarkValue(b *testing.B) {
    for n := 0; n < b.N; n++ {
                sv := IntValue(0)
                for i := 0; i < 1000; i++ {
                    iv := IntValue(int64(i))
                sv, _ = add(nil, sv, iv) // add is the "real" lua runtime 
function that adds two numeric values.
        }
    }
}

Results with the "scalar" version

$ go test -benchmem -run=^$ -bench '^(BenchmarkValue)$' ./runtime
goos: darwin
goarch: amd64
pkg: github.com/arnodel/golua/runtime
cpu: Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
BenchmarkValue-8          122995              9494 ns/op               0 
B/op          0 allocs/op
PASS
ok      github.com/arnodel/golua/runtime        1.415s

Results without the "scalar" version (noscalar build tag)

$ go test -benchmem -run=^$ -tags noscalar  -bench '^(BenchmarkValue)$' 
./runtime
goos: darwin
goarch: amd64
pkg: github.com/arnodel/golua/runtime
cpu: Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
BenchmarkValue-8           37407             32357 ns/op           13768 
B/op       1721 allocs/op
PASS
ok      github.com/arnodel/golua/runtime        1.629s

That looks like a pretty big improvement :)

The improvement is also significant in real workloads but not.so dramatic 
(given they don't spend all their time manipulating scalar values!)

On Monday, 21 December 2020 at 21:02:26 UTC ben...@gmail.com wrote:

> 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/ebcdbdbb-d1f0-47b7-a9f5-696d9887c6d6n%40googlegroups.com.

Reply via email to