go で binary.Write してるところで速度が欲しい場合は binary.ByteOrder interface を直に使う

go で binary package の Write は毎回 allocate しているのでもし速度が必要な場合は binary.ByteOrder interface ( binary.BigEndian, binary. LittleEndian ) を直に使うと改善出来る。 もちろん binary.Write の方が使いやすいので速度とかを気にする場合に binary.ByteOrder を使うのが良さそう。

それぞれ下のように利用する。

binary.Write

binary.Write(&buf, binary.BigEndian, uint16(...))

binary.BigEndian

binary.BigEndian.PutUint16(b, uint16(...))

実際のコード

apns のコードを前に書いた時に binary package を使ったのを思い出し比べてみた。apns の仕様自体はこのドキュメントにある

MsgBinaryWrite は愚直に bytes.Buffer を2つに分けて使ったり、余計に grow したりしてるので参考まで。

result

go test -v -bench . -benchmem
=== RUN TestMsg
--- PASS: TestMsg (0.00s)
PASS
BenchmarkMsgBinaryWrite   200000              9573 ns/op            1633 B/op         58 allocs/op
BenchmarkMsgBinaryBigendianNoReuse      20000000                91.9 ns/op             0 B/op          0 allocs/op
BenchmarkMsgBinaryBigendianReuse        30000000                51.2 ns/op             0 B/op          0 allocs/op

main.go

package main

import (
    "bytes"
    "encoding/binary"
)

const (
    DeviceTokenItemId            = 1
    DeviceTokenLength            = 32
    PayloadItemId                = 2
    NotificationIdentifierItemId = 3
    NotificationIdentifierLength = 4
    ExpirationDateItemId         = 4
    ExpirationDateLength         = 4
    PriorityItemId               = 5
    PriorityLength               = 1
)

func MsgBinaryWrite(token []byte, payload []byte, identifier uint32, expire uint32, priority uint8) []byte {

    var b bytes.Buffer

    // device token
    binary.Write(&b, binary.BigEndian, uint8(DeviceTokenItemId))
    binary.Write(&b, binary.BigEndian, uint16(DeviceTokenLength))
    binary.Write(&b, binary.BigEndian, token)

    // payload
    binary.Write(&b, binary.BigEndian, uint8(PayloadItemId))
    binary.Write(&b, binary.BigEndian, uint16(len(payload)))
    binary.Write(&b, binary.BigEndian, payload)

    // nofication identifier
    binary.Write(&b, binary.BigEndian, uint8(NotificationIdentifierItemId))
    binary.Write(&b, binary.BigEndian, uint16(NotificationIdentifierLength))
    binary.Write(&b, binary.BigEndian, identifier)

    // expiration date
    binary.Write(&b, binary.BigEndian, uint8(ExpirationDateItemId))
    binary.Write(&b, binary.BigEndian, uint16(ExpirationDateLength))
    binary.Write(&b, binary.BigEndian, expire)

    // priority
    binary.Write(&b, binary.BigEndian, uint8(PriorityItemId))
    binary.Write(&b, binary.BigEndian, uint16(PriorityLength))
    binary.Write(&b, binary.BigEndian, priority)

    var f bytes.Buffer

    // frame
    binary.Write(&f, binary.BigEndian, uint8(2))        // command
    binary.Write(&f, binary.BigEndian, uint32(b.Len())) // frame length
    binary.Write(&f, binary.BigEndian, b.Bytes())       // frame

    return f.Bytes()
}

func MsgBinaryBigendian(b []byte, token []byte, payload []byte, identifier uint32, expire uint32, priority uint8) []byte {
    index := 0

    // device token
    b[index] = uint8(DeviceTokenItemId)
    binary.BigEndian.PutUint16(b[index+1:], uint16(DeviceTokenLength))
    copy(b[index+3:], token)

    index += 3 + len(token)

    // payload
    b[index] = uint8(PayloadItemId)
    binary.BigEndian.PutUint16(b[index+1:], uint16(len(payload)))
    copy(b[index+3:], payload)

    index += 3 + len(payload)

    // notification identifier
    b[index] = uint8(NotificationIdentifierItemId)
    binary.BigEndian.PutUint16(b[index+1:], uint16(NotificationIdentifierLength))
    binary.BigEndian.PutUint32(b[index+3:], identifier)

    index += 7

    // expiration date
    b[index] = uint8(ExpirationDateItemId)
    binary.BigEndian.PutUint16(b[index+1:], uint16(ExpirationDateLength))
    binary.BigEndian.PutUint32(b[index+3:], expire)

    index += 7

    // priority
    b[index] = uint8(PriorityItemId)
    binary.BigEndian.PutUint16(b[index+1:], uint16(PriorityLength))
    b[index+3] = priority

    index += 4

    // frame
    copy(b[5:], b[:index])
    b[0] = uint8(2)
    binary.BigEndian.PutUint32(b[1:], uint32(index))

    return b[:index+5]
}

main_test.go

package main

import (
    "bytes"
    "testing"
    "time"
)

var (
    token      = []byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
    payload    = []byte(`{"apns":{"alert":"hi"}}`)
    identifier = uint32(8888)
    expire     = uint32(time.Now().Add(time.Second).Unix())
    priority   = uint8(10)
)

func TestMsg(t *testing.T) {

    b1 := MsgBinaryWrite(token, payload, identifier, expire, priority)
    buffer := make([]byte, 2048)
    b2 := MsgBinaryBigendian(buffer, token, payload, identifier, expire, priority)

    if !bytes.Equal(b1, b2) {
        t.Errorf("b1:%v b2:%v", b1, b2)
    }
}

func BenchmarkMsgBinaryWrite(b *testing.B) {
    for i := 0; i < b.N; i++ {
        MsgBinaryWrite(token, payload, identifier, expire, priority)
    }
}

func BenchmarkMsgBinaryBigendianNoReuse(b *testing.B) {
    for i := 0; i < b.N; i++ {
        buffer := make([]byte, 2048)
        MsgBinaryBigendian(buffer, token, payload, identifier, expire, priority)
    }
}

func BenchmarkMsgBinaryBigendianReuse(b *testing.B) {
    buffer := make([]byte, 2048)
    for i := 0; i < b.N; i++ {
        MsgBinaryBigendian(buffer, token, payload, identifier, expire, priority)
    }
}