Sunday, October 15, 2017

Packet Refinements

The Code

Yesterday, I mentioned I would be refining Chimera's packet system to no longer use reflection. I didn't expect to get to it so soon, but I was thinking about solutions yesterday and today and finished a packet reader and writer. The new reader and writer both help meet decode and encode interfaces, used by the client when receiving and sending data. The interfaces now look like this...

// Decoder defines a function that can be used to decode a byte buffer
// received by the server into a structure using TQ Digital's byte ordering
// rules. Must be met to be received by the server.
type Decoder interface { Decode(*Reader) interface{} }

// Encoder defines a function that can be used to encode a structure into a
// byte buffer to be sent to the client using TQ Digital's byte ordering
// rules. Must be met to be sent to the client.
type Encoder interface { Encode() *Writer }

The writer and reader are implemented using a byte.Buffer for the writer and a static byte array for the reader (slice split from the client's receive buffer). The methods are relatively uninteresting, but most importantly are used like this:

func Decode(read *packet.Reader) *Packet {
    p := new(Packet)
    p.Length     = read.Uint16()
    p.Identifier = read.Uint16()
    p.Account    = read.Seek(4).CString(128)
    p.Server     = read.CString(16)
    return p
}

func (p *Packet) Encode() *packet.Writer {
    write := packet.NewWriter()
    write.Uint16(p.Length)
    write.Uint16(p.Identifier)
    write.Uint32(p.Identity)
    write.Uint32(p.Token)
    write.Uint32(p.Port).Zero(4)
    write.CString(p.IPAddress, 16)
    return write
}

Test Results

As expected, this is much more performant than using reflection. For my decode tests, I tested decoding MsgConnect. When looking at my old reflection based system where structures were encoded and decoded automatically, 1000000 tests averaged about 1298 ns/op. Using the new reader where structures are decoded using explicit instructions, 20000000 tests averaged about 72.2 ns/op. Here are the below results:

> go test warry.io/chimera/lib/packet -bench .
BenchmarkDecodeMsgConnect-8             20000000              72.2 ns/op
BenchmarkDecodeUsingReflection-8         1000000              1298 ns/op
PASS
ok      warry.io/chimera/lib/packet     2.890s

Encoding is slightly slower due to the bytes.Buffer, and thus I might get back to that later. For my encode tests, I tested encoding MsgConnectEx. These were the results:

BenchmarkEncodeMsgConnectEx-8           10000000               191 ns/op
BenchmarkEncodeUsingReflection-8         2000000               722 ns/op

Conclusion

Obviously stated, don't use reflection in a high performance server. Removing reflection improved decode by a multiple of about 17. Encode is currently showing improvement by a multiple of about 4, so I'll work on that next (maybe). If you're interested in testing using Go, check out their testing package. https://golang.org/pkg/testing

No comments:

Post a Comment