Commit bbf27ce0 authored by Kirill Smelkov's avatar Kirill Smelkov

X zodbdump: remove allocations from hot path

on non testdata/1.fs

name        old time/op    new time/op    delta
ZodbDump-4     313ms ± 1%      98ms ± 0%  -68.81%  (p=0.008 n=5+5)

name        old alloc/op   new alloc/op   delta
ZodbDump-4    75.0MB ± 0%     0.1MB ± 0%  -99.89%  (p=0.008 n=5+5)

name        old allocs/op  new allocs/op  delta
ZodbDump-4     2.16M ± 0%     0.00M ± 0%  -99.91%  (p=0.008 n=5+5)
parent 2121ce8a
// TODO copyright/license
// Package xfmt provide addons to std fmt and strconv package with focus on
// formatting text without allocations.
package xfmt package xfmt
import ( import (
...@@ -6,7 +10,29 @@ import ( ...@@ -6,7 +10,29 @@ import (
"../xslice" "../xslice"
) )
// appendHex appends hex representation of x to b const (
hexdigits = "0123456789abcdef"
)
// Stringer is interface for natively formatting a value representation via xfmt
type Stringer interface {
// XFmtString method is used to append formatted value to destination buffer
// The grown buffer have to be returned
XFmtString(b []byte) []byte
}
// Append appends to b formatted x
//
// NOTE sadly since x is interface it makes real value substituted to it
// escape to heap (not so a problem since usually they already are) but then also
// if x has non-pointer receiver convT2I _allocates_ memory for the value copy.
//
// -> always pass to append &object, even if object has non-pointer XFmtString receiver.
func Append(b []byte, x Stringer) []byte {
return x.XFmtString(b)
}
// AppendHex appends to b hex representation of x
func AppendHex(b []byte, x []byte) []byte { func AppendHex(b []byte, x []byte) []byte {
lx := hex.EncodedLen(len(x)) lx := hex.EncodedLen(len(x))
lb := len(b) lb := len(b)
...@@ -14,3 +40,16 @@ func AppendHex(b []byte, x []byte) []byte { ...@@ -14,3 +40,16 @@ func AppendHex(b []byte, x []byte) []byte {
hex.Encode(b[lb:], x) hex.Encode(b[lb:], x)
return b return b
} }
// AppendHex64 appends to b x formateed 16-character hex string
func AppendHex64(b []byte, x uint64) []byte {
// like sprintf("%016x") but faster and less allocations
l := len(b)
b = xslice.Grow(b, 16)
bb := b[l:]
for i := 15; i >= 0; i-- {
bb[i] = hexdigits[x & 0xf]
x >>= 4
}
return b
}
...@@ -38,11 +38,11 @@ import ( ...@@ -38,11 +38,11 @@ import (
"io" "io"
"log" "log"
"os" "os"
"strconv"
"../../../zodb" "../../../zodb"
"../../../storage/fs1" "../../../storage/fs1"
"../../../xcommon/xfmt"
"lab.nexedi.com/kirr/go123/mem"
) )
...@@ -52,29 +52,50 @@ type dumper struct { ...@@ -52,29 +52,50 @@ type dumper struct {
HashOnly bool // whether to dump only hashes of data without content HashOnly bool // whether to dump only hashes of data without content
afterFirst bool // true after first transaction has been dumped afterFirst bool // true after first transaction has been dumped
workMem []byte // reusable data buffer for formatting
} }
var _LF = []byte{'\n'}
// DumpData dumps one data record // DumpData dumps one data record
func (d *dumper) DumpData(datai *zodb.StorageRecordInformation) error { func (d *dumper) DumpData(datai *zodb.StorageRecordInformation) error {
entry := "obj " + datai.Oid.String() + " " buf := d.workMem[:0]
//entry := "obj " + datai.Oid.String() + " "
buf = append(buf, "obj "...)
buf = xfmt.Append(buf, &datai.Oid)
buf = append(buf, ' ')
writeData := false writeData := false
switch { switch {
case datai.Data == nil: case datai.Data == nil:
entry += "delete" //entry += "delete"
buf = append(buf, "delete"...)
case datai.Tid != datai.DataTid: case datai.Tid != datai.DataTid:
entry += "from " + datai.DataTid.String() //entry += "from " + datai.DataTid.String()
buf = append(buf, "from "...)
buf = xfmt.Append(buf, &datai.DataTid)
default: default:
entry += fmt.Sprintf("%d sha1:%x", len(datai.Data), sha1.Sum(datai.Data)) //entry += fmt.Sprintf("%d sha1:%x", len(datai.Data), sha1.Sum(datai.Data))
buf = strconv.AppendInt(buf, int64(len(datai.Data)), 10)
buf = append(buf, " sha1:"...)
dataSha1 := sha1.Sum(datai.Data)
buf = xfmt.AppendHex(buf, dataSha1[:])
writeData = true writeData = true
} }
entry += "\n" //entry += "\n"
buf = append(buf, '\n')
// TODO use writev(data, "\n") via net.Buffers (it is already available) // TODO use writev(data, "\n") via net.Buffers (it is already available)
_, err := d.W.Write(mem.Bytes(entry)) //_, err := d.W.Write(mem.Bytes(entry))
_, err := d.W.Write(buf)
if err != nil { if err != nil {
goto out goto out
} }
...@@ -85,13 +106,15 @@ func (d *dumper) DumpData(datai *zodb.StorageRecordInformation) error { ...@@ -85,13 +106,15 @@ func (d *dumper) DumpData(datai *zodb.StorageRecordInformation) error {
goto out goto out
} }
_, err = d.W.Write([]byte("\n")) _, err = d.W.Write(_LF)
if err != nil { if err != nil {
goto out goto out
} }
} }
out: out:
d.workMem = buf
// XXX do we need this context ? // XXX do we need this context ?
// see for rationale in similar place in DumpTxn // see for rationale in similar place in DumpTxn
if err != nil { if err != nil {
......
...@@ -10,16 +10,28 @@ import ( ...@@ -10,16 +10,28 @@ import (
"encoding/binary" "encoding/binary"
"lab.nexedi.com/kirr/go123/xstrings" "lab.nexedi.com/kirr/go123/xstrings"
"../xcommon/xfmt"
) )
func (tid Tid) String() string { func (tid Tid) String() string {
// XXX also print "tid:" prefix ? // XXX also print "tid:" prefix ?
return fmt.Sprintf("%016x", uint64(tid)) //return fmt.Sprintf("%016x", uint64(tid))
return string(tid.XFmtString(nil))
} }
func (oid Oid) String() string { func (oid Oid) String() string {
// XXX also print "oid:" prefix ? // XXX also print "oid:" prefix ?
return fmt.Sprintf("%016x", uint64(oid)) //return fmt.Sprintf("%016x", uint64(oid))
return string(oid.XFmtString(nil))
}
func (tid Tid) XFmtString(b []byte) []byte {
return xfmt.AppendHex64(b, uint64(tid))
}
func (oid Oid) XFmtString(b []byte) []byte {
return xfmt.AppendHex64(b, uint64(oid))
} }
// XXX move me out of here // XXX move me out of here
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment