Keith, thank you very much for your feedback, it is highly appreciated!

With this in mind, it's time for lies, more lies, and statistics, 
benchmarking the three different implementations below:

func (r *Reader) Uint32() uint32 {
if r.err != nil {
return 0
}
var s struct {
_ [0]uint32
b [4]byte
}
_, r.err = r.buff.Read(s.b[:])
if r.err != nil {
return 0
}
return *(*uint32)(unsafe.Pointer(&s.b[0]))
}

func (r *Reader) Uint32X() uint32 {
if r.err != nil {
return 0
}
var v uint32
_, r.err = r.buff.Read((*[4]byte)(unsafe.Pointer(&v))[:])
if r.err != nil {
return 0
}
return v
}

func (r *Reader) Uint32N() uint32 {
if r.err != nil {
return 0
}
b := make([]byte, 4)
_, r.err = r.buff.Read(b)
if r.err != nil {
return 0
}
return hostnative.Uint32(b)
}

The benchmarking results using "go test -bench=. -benchtime=60s -benchmem .
":

cpu: Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
BenchmarkReadUint32
BenchmarkReadUint32-8           1000000000               5.974 ns/op       
    0 B/op          0 allocs/op
BenchmarkReadUint32X
BenchmarkReadUint32X-8          1000000000               5.977 ns/op       
    0 B/op          0 allocs/op
BenchmarkReadUint32N
BenchmarkReadUint32N-8          1000000000              20.81 ns/op         
   4 B/op          1 allocs/op

The two "unsafe" contenders are absolutely neck-to-neck, so in terms of 
better readability and maintainability your proposed variant wins for me. 
And as I was somehow suspecting, encoding/binary takes almost 4 times as 
much as the first two implementations, and throwing a needless heap 
allocation into the bargain. 

On Saturday, March 4, 2023 at 1:20:01 AM UTC+1 Keith Randall wrote:

> If you're using unsafe anyway, I'd go the other direction, casting from 
> the larger alignment to the smaller one. That avoids any alignment concerns.
>
> var x uint32
> b := (*[4]byte)(unsafe.Pointer(&x))[:]
> r.buff.Read(b)
> return x
>
> I would encourage you to use encoding/binary though. It all works out just 
> as well without unsafe, with a bit of trickiness around making sure that 
> calls can be resolved and inlined.
>
> b := make([]byte, 4)
> buf.Read(b)
> if little { // some global variable (or constant) you set
>    return binary.LittleEndian.Uint32(b)
> }
> return binary.BigEndian.Uint32(b)
> On Friday, March 3, 2023 at 12:30:37 PM UTC-8 TheDiveO wrote:
>
>> In dealing with Linux netlink messages I need to decode and encode 
>> uint16, uint32, and uint64 numbers that are in an arbitrary aligned byte 
>> buffer in an arbitrary position. In any case, these numbers are in native 
>> endianess, so I would like to avoid having to go through encoding/binary.
>>
>> buff := bytes.NewBuffer(/* some data */)
>>
>> // ...
>>
>> func foo() uint32 {
>>     var s struct {
>>         _ [0]uint32
>>         b [4]byte
>>     }
>>     r.buff.Read(s.b[:])
>>     return *(*uint32)(unsafe.Pointer(&s.b[0]))
>> }
>>
>> Will the go compiler (1.19+) allocate on the stack with the correct 
>> alignment for its element b, so that the unsafe.Pointer operation correctly 
>> works on different CPU architectures?
>>
>> Or is this inefficient anyway in a subtle way that my attempt to avoid 
>> non-stack allocations is moot anyway?
>>
>

-- 
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/c87b6661-2ba6-4aa5-863b-4391cbb6f6c8n%40googlegroups.com.

Reply via email to