Skip to content

Instantly share code, notes, and snippets.

@maxpert
Created March 27, 2013 21:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save maxpert/5257982 to your computer and use it in GitHub Desktop.
Save maxpert/5257982 to your computer and use it in GitHub Desktop.
Go code to benchmark memcached binary and ASCII protocol.
=========================
Testing with 100,000 gets
=========================
ASCII test...
ASCII 6.560225s
Binary test...
Binary 5.288102s
==========================
Testing with 1000,000 gets
==========================
ASCII test...
ASCII 1m5.220791s
Binary test...
Binary 53.24275s
// TAKEN FROM https://github.com/bmizerany/mc I hold no copyrights on this code
package mc
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"strings"
"sync"
)
// Errors
var (
ErrNotFound = errors.New("mc: not found")
ErrKeyExists = errors.New("mc: key exists")
ErrValueTooLarge = errors.New("mc: value to large")
ErrInvalidArgs = errors.New("mc: invalid arguments")
ErrValueNotStored = errors.New("mc: value not stored")
ErrNonNumeric = errors.New("mc: incr/decr called on non-numeric value")
ErrAuthRequired = errors.New("mc: authentication required")
ErrUnknownCommand = errors.New("mc: unknown command")
ErrOutOfMemory = errors.New("mc: out of memory")
)
var errMap = map[uint16]error{
0: nil,
1: ErrNotFound,
2: ErrKeyExists,
3: ErrValueTooLarge,
4: ErrInvalidArgs,
5: ErrValueNotStored,
6: ErrNonNumeric,
0x20: ErrAuthRequired,
0x81: ErrUnknownCommand,
0x82: ErrOutOfMemory,
}
// Ops
const (
OpGet = uint8(iota)
OpSet
OpAdd
OpReplace
OpDelete
OpIncrement
OpDecrement
OpQuit
OpFlush
OpGetQ
OpNoop
OpVersion
OpGetK
OpGetKQ
OpAppend
OpPrepend
OpStat
OpSetQ
OpAddQ
OpReplaceQ
OpDeleteQ
OpIncrementQ
OpDecrementQ
OpQuitQ
OpFlushQ
OpAppendQ
OpPrependQ
)
// Auth Ops
const (
OpAuthList = uint8(iota + 0x20)
OpAuthStart
OpAuthStep
)
type header struct {
Magic uint8
Op uint8
KeyLen uint16
ExtraLen uint8
DataType uint8
ResvOrStatus uint16
BodyLen uint32
Opaque uint32
CAS uint64
}
type msg struct {
header
iextras []interface{}
oextras []interface{}
key string
val string
}
type Conn struct {
rwc io.ReadWriteCloser
l sync.Mutex
buf *bytes.Buffer
}
func Dial(nett, addr string) (*Conn, error) {
nc, err := net.Dial(nett, addr)
if err != nil {
return nil, err
}
cn := &Conn{rwc: nc, buf: new(bytes.Buffer)}
return cn, nil
}
func (cn *Conn) Close() error {
return cn.rwc.Close()
}
func (cn *Conn) Get(key string) (val string, cas int, flags uint32, err error) {
m := &msg{
header: header{
Op: OpGet,
},
oextras: []interface{}{&flags},
key: key,
}
err = cn.send(m)
return m.val, int(m.CAS), flags, err
}
func (cn *Conn) Set(key, val string, ocas, flags, exp int) error {
m := &msg{
header: header{
Op: OpSet,
CAS: uint64(ocas),
},
iextras: []interface{}{uint32(flags), uint32(exp)},
key: key,
val: val,
}
return cn.send(m)
}
func (cn *Conn) Del(key string) error {
m := &msg{
header: header{
Op: OpDelete,
},
key: key,
}
return cn.send(m)
}
func (cn *Conn) Incr(key string, delta, init, exp int) (n, cas int, err error) {
return cn.incrdecr(OpIncrement, key, delta, init, exp)
}
func (cn *Conn) Decr(key string, delta, init, exp int) (n, cas int, err error) {
return cn.incrdecr(OpDecrement, key, delta, init, exp)
}
func (cn *Conn) Auth(user, pass string) error {
s, err := cn.authList()
if err != nil {
return err
}
switch {
case strings.Index(s, "PLAIN") != -1:
return cn.authPlain(user, pass)
}
return fmt.Errorf("mc: unknown auth types %q", s)
}
func (cn *Conn) authList() (s string, err error) {
m := &msg{
header: header{
Op: OpAuthList,
},
}
err = cn.send(m)
return m.val, err
}
func (cn *Conn) authPlain(user, pass string) error {
m := &msg{
header: header{
Op: OpAuthStart,
},
key: "PLAIN",
val: fmt.Sprintf("\x00%s\x00%s", user, pass),
}
return cn.send(m)
}
func (cn *Conn) incrdecr(op uint8, key string, delta, init, exp int) (n, cas int, err error) {
m := &msg{
header: header{
Op: op,
},
key: key,
iextras: []interface{}{uint64(delta), uint64(delta), uint32(exp)},
}
err = cn.send(m)
if err != nil {
return
}
return readInt(m.val), int(m.CAS), nil
}
func (cn *Conn) send(m *msg) (err error) {
m.Magic = 0x80
m.ExtraLen = sizeOfExtras(m.iextras)
m.KeyLen = uint16(len(m.key))
m.BodyLen = uint32(m.ExtraLen) + uint32(m.KeyLen) + uint32(len(m.val))
cn.l.Lock()
defer cn.l.Unlock()
// Request
err = binary.Write(cn.buf, binary.BigEndian, m.header)
if err != nil {
return
}
for _, e := range m.iextras {
err = binary.Write(cn.buf, binary.BigEndian, e)
if err != nil {
return
}
}
_, err = io.WriteString(cn.buf, m.key)
if err != nil {
return
}
_, err = io.WriteString(cn.buf, m.val)
if err != nil {
return
}
cn.buf.WriteTo(cn.rwc)
// Response
err = binary.Read(cn.rwc, binary.BigEndian, &m.header)
if err != nil {
return err
}
bd := make([]byte, m.BodyLen)
_, err = io.ReadFull(cn.rwc, bd)
if err != nil {
return err
}
buf := bytes.NewBuffer(bd)
for _, e := range m.oextras {
err = binary.Read(buf, binary.BigEndian, e)
if err != nil {
return
}
}
m.key = string(buf.Next(int(m.KeyLen)))
vlen := int(m.BodyLen) - int(m.ExtraLen) - int(m.KeyLen)
m.val = string(buf.Next(int(vlen)))
return checkError(m)
}
func checkError(m *msg) error {
err, ok := errMap[m.ResvOrStatus]
if !ok {
return errors.New("mc: unknown error from server")
}
return err
}
func sizeOfExtras(extras []interface{}) (l uint8) {
for _, e := range extras {
switch e.(type) {
default:
panic(fmt.Sprintf("mc: unknown extra type (%T)", e))
case uint8:
l += 8 / 8
case uint16:
l += 16 / 8
case uint32:
l += 32 / 8
case uint64:
l += 64 / 8
}
}
return
}
func readInt(b string) int {
switch len(b) {
case 8: // 64 bit
return int(uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56)
}
panic(fmt.Sprintf("mc: don't know how to parse string with %d bytes", len(b)))
}
package main
import (
"github.com/bradfitz/gomemcache/memcache"
"fmt"
"time"
"mc"
)
func bm_mc() {
fmt.Println("Binary test...\n")
con, err := mc.Dial("tcp", "localhost:11211")
if err != nil {
fmt.Println(err)
}
start := time.Now()
for i := 0; i < 1000000; i++ {
// fmt.Printf("%d\r", i)
_, _, _, err = con.Get("foo")
if err != nil {
fmt.Println(err)
}
}
end := time.Now()
fmt.Printf("Binary %v\n", end.Sub(start))
}
func bm_memcache() {
fmt.Println("ASCII test...\n")
a_mc := memcache.New("localhost:11211")
a_mc.Set(&memcache.Item{Key: "foo", Value: []byte("this is a really really really really really really really really really really really really really really long value")})
start := time.Now()
for i := 0; i < 1000000; i++ {
// fmt.Printf("%d\r", i)
_, err := a_mc.Get("foo")
if err != nil {
fmt.Println(err)
}
}
end := time.Now()
fmt.Printf("ASCII %v\n", end.Sub(start))
}
func main() {
bm_memcache()
bm_mc()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment