internal/impl: precompute required bit in validator

Required field validation populates a bitmask of observed required
fields. Store a uint64 containing the bit to set in the validationInfo
rather than the index of the bit. Provides a noticeable speed increase
in validation.

name                             old time/op  new time/op  delta
EmptyMessage/Wire/Unmarshal      40.2ns ± 1%  40.2ns ± 2%    ~     (p=0.860 n=35+37)
EmptyMessage/Wire/Unmarshal-12   7.13ns ± 5%  7.12ns ± 1%    ~     (p=0.112 n=37+37)
RepeatedInt32/Wire/Unmarshal     6.57µs ± 1%  6.46µs ± 1%  -1.56%  (p=0.000 n=39+35)
RepeatedInt32/Wire/Unmarshal-12  1.05µs ± 2%  1.05µs ± 2%    ~     (p=0.659 n=37+33)
Required/Wire/Unmarshal           258ns ± 1%   251ns ± 1%  -2.87%  (p=0.000 n=32+38)
Required/Wire/Unmarshal-12       44.3ns ± 2%  42.4ns ± 1%  -4.36%  (p=0.000 n=36+37)

Change-Id: Ib1cb74d3e348355a6a2f66aecf8fdc4b58cd84d4
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/216420
Reviewed-by: Joe Tsai <joetsai@google.com>
This commit is contained in:
Damien Neil 2020-01-24 16:42:42 -08:00
parent ce8f7f6353
commit 170b2bfca6
2 changed files with 13 additions and 10 deletions

View File

@ -143,9 +143,10 @@ func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag wire.Numbe
var o unmarshalOutput var o unmarshalOutput
o, err = f.funcs.unmarshal(b, p.Apply(f.offset), wtyp, opts) o, err = f.funcs.unmarshal(b, p.Apply(f.offset), wtyp, opts)
n = o.n n = o.n
if reqi := f.validation.requiredIndex; reqi > 0 && err == nil { if err != nil {
requiredMask |= 1 << (reqi - 1) break
} }
requiredMask |= f.validation.requiredBit
if f.funcs.isInit != nil && !o.initialized { if f.funcs.isInit != nil && !o.initialized {
initialized = false initialized = false
} }

View File

@ -77,11 +77,13 @@ type validationInfo struct {
typ validationType typ validationType
keyType, valType validationType keyType, valType validationType
// For non-required fields, requiredIndex is 0. // For non-required fields, requiredBit is 0.
// //
// For required fields, requiredIndex is unique index in the range // For required fields, requiredBit's nth bit is set, where n is a
// (0, MessageInfo.numRequiredFields]. // unique index in the range [0, MessageInfo.numRequiredFields).
requiredIndex uint8 //
// If there are more than 64 required fields, requiredBit is 0.
requiredBit uint64
} }
type validationType uint8 type validationType uint8
@ -131,7 +133,7 @@ func newFieldValidationInfo(mi *MessageInfo, si structInfo, fd pref.FieldDescrip
// of the required fields past 64. // of the required fields past 64.
if mi.numRequiredFields < math.MaxUint8 { if mi.numRequiredFields < math.MaxUint8 {
mi.numRequiredFields++ mi.numRequiredFields++
vi.requiredIndex = mi.numRequiredFields vi.requiredBit = 1 << (mi.numRequiredFields - 1)
} }
} }
return vi return vi
@ -272,7 +274,7 @@ State:
case 2: case 2:
vi.typ = st.valType vi.typ = st.valType
vi.mi = st.mi vi.mi = st.mi
vi.requiredIndex = 1 vi.requiredBit = 1
} }
default: default:
var f *coderFieldInfo var f *coderFieldInfo
@ -321,7 +323,7 @@ State:
vi = getExtensionFieldInfo(xt).validation vi = getExtensionFieldInfo(xt).validation
} }
} }
if vi.requiredIndex > 0 { if vi.requiredBit != 0 {
// Check that the field has a compatible wire type. // Check that the field has a compatible wire type.
// We only need to consider non-repeated field types, // We only need to consider non-repeated field types,
// since repeated fields (and maps) can never be required. // since repeated fields (and maps) can never be required.
@ -337,7 +339,7 @@ State:
ok = wtyp == wire.BytesType ok = wtyp == wire.BytesType
} }
if ok { if ok {
st.requiredMask |= 1 << (vi.requiredIndex - 1) st.requiredMask |= vi.requiredBit
} }
} }
switch vi.typ { switch vi.typ {