Skip to content

Commit 699f269

Browse files
authored
A lot of memory allocation optimizations (#466)
A lot of memory allocation optimizations: zero-allocation in hot paths for mysql/client. Changed Resultset.Values public API.
1 parent a8c16ae commit 699f269

File tree

12 files changed

+549
-256
lines changed

12 files changed

+549
-256
lines changed

client/req.go

+11-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package client
22

3+
import (
4+
"github.com/siddontang/go-mysql/utils"
5+
)
6+
37
func (c *Conn) writeCommand(command byte) error {
48
c.ResetSequence()
59

@@ -16,28 +20,20 @@ func (c *Conn) writeCommandBuf(command byte, arg []byte) error {
1620
c.ResetSequence()
1721

1822
length := len(arg) + 1
19-
20-
data := make([]byte, length+4)
21-
23+
data := utils.ByteSliceGet(length + 4)
2224
data[4] = command
2325

2426
copy(data[5:], arg)
2527

26-
return c.WritePacket(data)
27-
}
28-
29-
func (c *Conn) writeCommandStr(command byte, arg string) error {
30-
c.ResetSequence()
31-
32-
length := len(arg) + 1
33-
34-
data := make([]byte, length+4)
28+
err := c.WritePacket(data)
3529

36-
data[4] = command
30+
utils.ByteSlicePut(data)
3731

38-
copy(data[5:], arg)
32+
return err
33+
}
3934

40-
return c.WritePacket(data)
35+
func (c *Conn) writeCommandStr(command byte, arg string) error {
36+
return c.writeCommandBuf(command, utils.StringToByteSlice(arg))
4137
}
4238

4339
func (c *Conn) writeCommandUint32(command byte, arg uint32) error {

client/resp.go

+30-25
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package client
22

33
import (
4-
"encoding/binary"
5-
64
"bytes"
75
"crypto/rsa"
86
"crypto/x509"
7+
"encoding/binary"
98
"encoding/pem"
109

1110
"github.com/pingcap/errors"
1211
. "github.com/siddontang/go-mysql/mysql"
12+
"github.com/siddontang/go-mysql/utils"
1313
"github.com/siddontang/go/hack"
1414
)
1515

@@ -212,40 +212,35 @@ func (c *Conn) readOK() (*Result, error) {
212212
}
213213

214214
func (c *Conn) readResult(binary bool) (*Result, error) {
215-
data, err := c.ReadPacket()
215+
firstPkgBuf, err := c.ReadPacketReuseMem(utils.ByteSliceGet(16)[:0])
216+
defer utils.ByteSlicePut(firstPkgBuf)
217+
216218
if err != nil {
217219
return nil, errors.Trace(err)
218220
}
219221

220-
if data[0] == OK_HEADER {
221-
return c.handleOKPacket(data)
222-
} else if data[0] == ERR_HEADER {
223-
return nil, c.handleErrorPacket(data)
224-
} else if data[0] == LocalInFile_HEADER {
222+
if firstPkgBuf[0] == OK_HEADER {
223+
return c.handleOKPacket(firstPkgBuf)
224+
} else if firstPkgBuf[0] == ERR_HEADER {
225+
return nil, c.handleErrorPacket(append([]byte{}, firstPkgBuf...))
226+
} else if firstPkgBuf[0] == LocalInFile_HEADER {
225227
return nil, ErrMalformPacket
226228
}
227229

228-
return c.readResultset(data, binary)
230+
return c.readResultset(firstPkgBuf, binary)
229231
}
230232

231233
func (c *Conn) readResultset(data []byte, binary bool) (*Result, error) {
232-
result := &Result{
233-
Status: 0,
234-
InsertId: 0,
235-
AffectedRows: 0,
236-
237-
Resultset: &Resultset{},
238-
}
239-
240234
// column count
241235
count, _, n := LengthEncodedInt(data)
242236

243237
if n-len(data) != 0 {
244238
return nil, ErrMalformPacket
245239
}
246240

247-
result.Fields = make([]*Field, count)
248-
result.FieldNames = make(map[string]int, count)
241+
result := &Result{
242+
Resultset: NewResultset(int(count)),
243+
}
249244

250245
if err := c.readResultColumns(result); err != nil {
251246
return nil, errors.Trace(err)
@@ -263,10 +258,12 @@ func (c *Conn) readResultColumns(result *Result) (err error) {
263258
var data []byte
264259

265260
for {
266-
data, err = c.ReadPacket()
261+
rawPkgLen := len(result.RawPkg)
262+
result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg)
267263
if err != nil {
268264
return
269265
}
266+
data = result.RawPkg[rawPkgLen:]
270267

271268
// EOF Packet
272269
if c.isEOFPacket(data) {
@@ -284,7 +281,10 @@ func (c *Conn) readResultColumns(result *Result) (err error) {
284281
return
285282
}
286283

287-
result.Fields[i], err = FieldData(data).Parse()
284+
if result.Fields[i] == nil {
285+
result.Fields[i] = &Field{}
286+
}
287+
err = result.Fields[i].Parse(data)
288288
if err != nil {
289289
return
290290
}
@@ -299,11 +299,12 @@ func (c *Conn) readResultRows(result *Result, isBinary bool) (err error) {
299299
var data []byte
300300

301301
for {
302-
data, err = c.ReadPacket()
303-
302+
rawPkgLen := len(result.RawPkg)
303+
result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg)
304304
if err != nil {
305305
return
306306
}
307+
data = result.RawPkg[rawPkgLen:]
307308

308309
// EOF Packet
309310
if c.isEOFPacket(data) {
@@ -324,10 +325,14 @@ func (c *Conn) readResultRows(result *Result, isBinary bool) (err error) {
324325
result.RowDatas = append(result.RowDatas, data)
325326
}
326327

327-
result.Values = make([][]interface{}, len(result.RowDatas))
328+
if cap(result.Values) < len(result.RowDatas) {
329+
result.Values = make([][]FieldValue, len(result.RowDatas))
330+
} else {
331+
result.Values = result.Values[:len(result.RowDatas)]
332+
}
328333

329334
for i := range result.Values {
330-
result.Values[i], err = result.RowDatas[i].Parse(result.Fields, isBinary)
335+
result.Values[i], err = result.RowDatas[i].Parse(result.Fields, isBinary, result.Values[i])
331336

332337
if err != nil {
333338
return errors.Trace(err)
File renamed without changes.

mysql/field.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package mysql
22

33
import (
44
"encoding/binary"
5+
6+
"github.com/siddontang/go-mysql/utils"
57
)
68

79
type FieldData []byte
@@ -23,9 +25,23 @@ type Field struct {
2325
DefaultValue []byte
2426
}
2527

26-
func (p FieldData) Parse() (f *Field, err error) {
27-
f = new(Field)
28+
type FieldValueType uint8
29+
30+
type FieldValue struct {
31+
Type FieldValueType
32+
value uint64 // Also for int64 and float64
33+
str []byte
34+
}
35+
36+
const (
37+
FieldValueTypeNull = iota
38+
FieldValueTypeUnsigned
39+
FieldValueTypeSigned
40+
FieldValueTypeFloat
41+
FieldValueTypeString
42+
)
2843

44+
func (f *Field) Parse(p FieldData) (err error) {
2945
f.Data = p
3046

3147
var n int
@@ -117,6 +133,14 @@ func (p FieldData) Parse() (f *Field, err error) {
117133
return
118134
}
119135

136+
func (p FieldData) Parse() (f *Field, err error) {
137+
f = new(Field)
138+
if err = f.Parse(p); err != nil {
139+
return nil, err
140+
}
141+
return f, nil
142+
}
143+
120144
func (f *Field) Dump() []byte {
121145
if f == nil {
122146
f = &Field{}
@@ -155,3 +179,34 @@ func (f *Field) Dump() []byte {
155179

156180
return data
157181
}
182+
183+
func (fv *FieldValue) AsUint64() uint64 {
184+
return fv.value
185+
}
186+
187+
func (fv *FieldValue) AsInt64() int64 {
188+
return utils.Uint64ToInt64(fv.value)
189+
}
190+
191+
func (fv *FieldValue) AsFloat64() float64 {
192+
return utils.Uint64ToFloat64(fv.value)
193+
}
194+
195+
func (fv *FieldValue) AsString() []byte {
196+
return fv.str
197+
}
198+
199+
func (fv *FieldValue) Value() interface{} {
200+
switch fv.Type {
201+
case FieldValueTypeUnsigned:
202+
return fv.AsUint64()
203+
case FieldValueTypeSigned:
204+
return fv.AsInt64()
205+
case FieldValueTypeFloat:
206+
return fv.AsFloat64()
207+
case FieldValueTypeString:
208+
return fv.AsString()
209+
default: // FieldValueTypeNull
210+
return nil
211+
}
212+
}

mysql/result.go

+7
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,10 @@ type Result struct {
1212
type Executer interface {
1313
Execute(query string, args ...interface{}) (*Result, error)
1414
}
15+
16+
func (r *Result) Close() {
17+
if r.Resultset != nil {
18+
r.Resultset.returnToPool()
19+
r.Resultset = nil
20+
}
21+
}

0 commit comments

Comments
 (0)