Skip to content

Commit 4dfdcb5

Browse files
authored
Add zero-copy Unmarshal (#389)
Adds `zerocopy` tag that will allow byte slices to be unmarshaled with zero-copy, referencing the original slice. AFAICT not possible for other types or when decoding.
1 parent 2c27623 commit 4dfdcb5

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

_generated/def.go

+23-7
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,21 @@ type Block [32]byte
2525
// tests edge-cases with
2626
// compiling size compilation.
2727
type X struct {
28-
Values [32]byte // should compile to 32*msgp.ByteSize; encoded as Bin
29-
ValuesPtr *[32]byte // check (*)[:] deref
30-
More Block // should be identical to the above
31-
Others [][32]int32 // should compile to len(x.Others)*32*msgp.Int32Size
32-
Matrix [][]int32 // should not optimize
33-
ManyFixed []Fixed
34-
WeirdTag string `msg:"\x0b"`
28+
Values [32]byte // should compile to 32*msgp.ByteSize; encoded as Bin
29+
ValuesPtr *[32]byte // check (*)[:] deref
30+
More Block // should be identical to the above
31+
Others [][32]int32 // should compile to len(x.Others)*32*msgp.Int32Size
32+
Matrix [][]int32 // should not optimize
33+
ManyFixed []Fixed
34+
WeirdTag string `msg:"\x0b"`
35+
ZCBytes []byte `msg:",zerocopy"`
36+
ZCBytesAN []byte `msg:",zerocopy,allownil"`
37+
ZCBytesOE []byte `msg:",zerocopy,omitempty"`
38+
ZCBytesSlice [][]byte `msg:",zerocopy"`
39+
ZCBytesArr [2][]byte `msg:",zerocopy"`
40+
ZCBytesMap map[string][]byte `msg:",zerocopy"`
41+
ZCBytesMapDeep map[string]map[string][]byte `msg:",zerocopy"`
42+
CustomBytes CustomBytes `msg:",zerocopy"`
3543
}
3644

3745
// test fixed-size struct
@@ -73,6 +81,14 @@ type Object struct {
7381
MapMap map[string]map[string]string
7482
MapStringEmpty map[string]struct{}
7583
MapStringEmpty2 map[string]EmptyStruct
84+
ZCBytes []byte `msg:",zerocopy"`
85+
ZCBytesAN []byte `msg:",zerocopy,allownil"`
86+
ZCBytesOE []byte `msg:",zerocopy,omitempty"`
87+
ZCBytesSlice [][]byte `msg:",zerocopy"`
88+
ZCBytesArr [2][]byte `msg:",zerocopy"`
89+
ZCBytesMap map[string][]byte `msg:",zerocopy"`
90+
ZCBytesMapDeep map[string]map[string][]byte `msg:",zerocopy"`
91+
CustomBytes CustomBytes `msg:",zerocopy"`
7692
}
7793

7894
//msgp:tuple TestBench

gen/elem.go

+1
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ type BaseElem struct {
576576
ShimFromBase string // shim from base type, or empty
577577
Value Primitive // Type of element
578578
Convert bool // should we do an explicit conversion?
579+
zerocopy bool // Allow zerocopy for byte slices in unmarshal.
579580
mustinline bool // must inline; not printable
580581
needsref bool // needs reference for shim
581582
allowNil *bool // Override from parent.

gen/unmarshal.go

+34-1
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,37 @@ func (u *unmarshalGen) tuple(s *Struct) {
8989
u.p.printf("\nif msgp.IsNil(bts) {\nbts = bts[1:]\n%s = nil\n} else {", fieldElem.Varname())
9090
}
9191
SetIsAllowNil(fieldElem, anField)
92+
if s.Fields[i].HasTagPart("zerocopy") {
93+
setRecursiveZC(fieldElem, true)
94+
}
9295
next(u, fieldElem)
96+
if s.Fields[i].HasTagPart("zerocopy") {
97+
setRecursiveZC(fieldElem, false)
98+
}
99+
93100
u.ctx.Pop()
94101
if anField {
95102
u.p.printf("\n}")
96103
}
97104
}
98105
}
99106

107+
// setRecursiveZC will alloc zerocopy for byte fields that are present.
108+
func setRecursiveZC(e Elem, enable bool) {
109+
if base, ok := e.(*BaseElem); ok {
110+
base.zerocopy = enable
111+
}
112+
if el, ok := e.(*Slice); ok {
113+
setRecursiveZC(el.Els, enable)
114+
}
115+
if el, ok := e.(*Array); ok {
116+
setRecursiveZC(el.Els, enable)
117+
}
118+
if el, ok := e.(*Map); ok {
119+
setRecursiveZC(el.Value, enable)
120+
}
121+
}
122+
100123
func (u *unmarshalGen) mapstruct(s *Struct) {
101124
u.needsField()
102125
sz := randIdent()
@@ -136,7 +159,13 @@ func (u *unmarshalGen) mapstruct(s *Struct) {
136159
u.p.printf("\nif msgp.IsNil(bts) {\nbts = bts[1:]\n%s = nil\n} else {", fieldElem.Varname())
137160
}
138161
SetIsAllowNil(fieldElem, anField)
162+
if s.Fields[i].HasTagPart("zerocopy") {
163+
setRecursiveZC(fieldElem, true)
164+
}
139165
next(u, fieldElem)
166+
if s.Fields[i].HasTagPart("zerocopy") {
167+
setRecursiveZC(fieldElem, false)
168+
}
140169
u.ctx.Pop()
141170
if oeCount > 0 && (s.Fields[i].HasTagPart("omitempty") || s.Fields[i].HasTagPart("omitzero")) {
142171
u.p.printf("\n%s", bm.setStmt(len(oeEmittedIdx)))
@@ -188,7 +217,11 @@ func (u *unmarshalGen) gBase(b *BaseElem) {
188217

189218
switch b.Value {
190219
case Bytes:
191-
u.p.printf("\n%s, bts, err = msgp.ReadBytesBytes(bts, %s)", refname, lowered)
220+
if b.zerocopy {
221+
u.p.printf("\n%s, bts, err = msgp.ReadBytesZC(bts)", refname)
222+
} else {
223+
u.p.printf("\n%s, bts, err = msgp.ReadBytesBytes(bts, %s)", refname, lowered)
224+
}
192225
case Ext:
193226
u.p.printf("\nbts, err = msgp.ReadExtensionBytes(bts, %s)", lowered)
194227
case IDENT:

0 commit comments

Comments
 (0)