Golang中的大小数据加载(将uint16放入uint8切片中)
I'm hacking together a rough ISS of a processor and I wonder if there is a more efficient way to to what I am doing (ideally without resorting to the unsafe library). (Simplified) I'm representing memory by:
type DataMem []uint8
a register by:
type Register uint16
Memory needs to be in byte sized units and the processor works in larger units as above. This means to store the data we do:
func (m *Machine) ExecuteST (ins Instruction) {
// Store to memory
// Data to store specified in one register
// Address to store to specified by another register
target_address := m.RegBank[ins.Dst]
src_data := m.RegBank[ins.Src]
///////// Start of annoying section////////
var target_0 uint8
var target_1 uint8
target_0 = src_data & 0x0f
target_1 = (src_data & 0xf0)>>8
m.data_mem[target_address ] = target_0
m.data_mem[target_address+1] = target_1
/////////// End of annoying section /////////
m.logger.Printf("ST To Address %x, Data %x
",target_address,src_data)
}
And so on for all sorts of different data types and transfers.
The annoying thing for me is I know the processor that the go code will be running on will have a single load instruction that could do all of the above in a single transfer. I'd expect a good c compiler to optimise into that for me, but without breaking out the unsafe library and going straight to the pointers I don't think I can get around this?
I'm open for suggestions including better ways to model the data memory...
Because of the type safety of Go, there's not much you can do differently here without using the unsafe package.
The simplest solution, and probably the most performant, is to do the conversion on the target address:
// convert the target address to *uint16
srcData = uint16(0xeeee)
*(*uint16)(unsafe.Pointer(&dataMem[targetAddress])) = srcData
Similarly, another option is to shadow the []uint8
slice with a []uint16
slice pointing to the same memory region. This looks even more hairy, and you have to check your offsets and alignment yourself.
dataMem16 := (*(*[1<<30 - 1]uint16)(unsafe.Pointer(&dataMem[0])))[:len(dataMem)/2 : len(dataMem)/2]
dataMem16[targetAddress/2] = srcData
One other option to try is using the builtin copy
to move the bytes. Since copy is implemented in assembly by the compiler, it may do what you want (though I haven't checked what the assembly of the conversion actually produces, so it could be a wash)
copy(dataMem[targetAddress:], (*(*[2]uint8)(unsafe.Pointer(&srcData)))[:])
Or as an inline-able function as @OneOfOne shows:
func regToMem(reg uint16) *[2]uint8 {
return (*[2]uint8)(unsafe.Pointer(®))
}
copy(mem[targetAddress:], regToMem(0xffff)[:])
https://play.golang.org/p/0c1UywVuzj
If performance is critical, and you want to use architecture specific instructions, the "proper" way to do this would be to implement what you want directly in assembly, but the code generated from the first solution would probably be hard to beat.
Since what I care about is efficiency of the simulation another option is to declare the data memory differently e.g.
type DataMem []uint32
The provide methods that given an address exctract the byte
func (dm *DataMem) StoreB (address int) uint8 {...}
func (dm *DataMem) Store16 (address int) uint16 {...}
Given this is running on a PC there will be a data cache there so fetching too large a data word will not cost anything on a modern processor.
I need a new cardinal rule, when you have a tricky problem, look at the data structures and your platform ;-)