Skip to content

Commit af66cc8

Browse files
committed
Only lock fieldmap once during message parsing
``` goos: darwin goarch: arm64 pkg: github.com/quickfixgo/quickfix │ old │ new-parse-no-lock │ │ sec/op │ sec/op vs base │ ParseMessage-8 695.2n ± 1% 591.0n ± 0% -14.99% (p=0.000 n=20) ``` Signed-off-by: Sylvain Rabot <sylvain@abstraction.fr>
1 parent e3a2994 commit af66cc8

File tree

2 files changed

+73
-10
lines changed

2 files changed

+73
-10
lines changed

field_map.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,20 @@ func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError
115115
return nil
116116
}
117117

118+
// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
119+
func (m FieldMap) getFieldNoLock(tag Tag, parser FieldValueReader) MessageRejectError {
120+
f, ok := m.tagLookup[tag]
121+
if !ok {
122+
return ConditionallyRequiredFieldMissing(tag)
123+
}
124+
125+
if err := parser.Read(f[0].value); err != nil {
126+
return IncorrectDataFormatForValue(tag)
127+
}
128+
129+
return nil
130+
}
131+
118132
// GetBytes is a zero-copy GetField wrapper for []bytes fields.
119133
func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
120134
m.rwLock.RLock()
@@ -128,6 +142,16 @@ func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
128142
return f[0].value, nil
129143
}
130144

145+
// getBytesNoLock is a lock free zero-copy GetField wrapper for []bytes fields.
146+
func (m FieldMap) getBytesNoLock(tag Tag) ([]byte, MessageRejectError) {
147+
f, ok := m.tagLookup[tag]
148+
if !ok {
149+
return nil, ConditionallyRequiredFieldMissing(tag)
150+
}
151+
152+
return f[0].value, nil
153+
}
154+
131155
// GetBool is a GetField wrapper for bool fields.
132156
func (m FieldMap) GetBool(tag Tag) (bool, MessageRejectError) {
133157
var val FIXBoolean
@@ -152,6 +176,21 @@ func (m FieldMap) GetInt(tag Tag) (int, MessageRejectError) {
152176
return int(val), err
153177
}
154178

179+
// GetInt is a lock free GetField wrapper for int fields.
180+
func (m FieldMap) getIntNoLock(tag Tag) (int, MessageRejectError) {
181+
bytes, err := m.getBytesNoLock(tag)
182+
if err != nil {
183+
return 0, err
184+
}
185+
186+
var val FIXInt
187+
if val.Read(bytes) != nil {
188+
err = IncorrectDataFormatForValue(tag)
189+
}
190+
191+
return int(val), err
192+
}
193+
155194
// GetTime is a GetField wrapper for utc timestamp fields.
156195
func (m FieldMap) GetTime(tag Tag) (t time.Time, err MessageRejectError) {
157196
m.rwLock.RLock()
@@ -179,6 +218,15 @@ func (m FieldMap) GetString(tag Tag) (string, MessageRejectError) {
179218
return string(val), nil
180219
}
181220

221+
// GetString is a GetField wrapper for string fields.
222+
func (m FieldMap) getStringNoLock(tag Tag) (string, MessageRejectError) {
223+
var val FIXString
224+
if err := m.getFieldNoLock(tag, &val); err != nil {
225+
return "", err
226+
}
227+
return string(val), nil
228+
}
229+
182230
// GetGroup is a Get function specific to Group Fields.
183231
func (m FieldMap) GetGroup(parser FieldGroupReader) MessageRejectError {
184232
m.rwLock.RLock()
@@ -246,6 +294,13 @@ func (m *FieldMap) Clear() {
246294
}
247295
}
248296

297+
func (m *FieldMap) clearNoLock() {
298+
m.tags = m.tags[0:0]
299+
for k := range m.tagLookup {
300+
delete(m.tagLookup, k)
301+
}
302+
}
303+
249304
// CopyInto overwrites the given FieldMap with this one.
250305
func (m *FieldMap) CopyInto(to *FieldMap) {
251306
m.rwLock.RLock()
@@ -263,9 +318,6 @@ func (m *FieldMap) CopyInto(to *FieldMap) {
263318
}
264319

265320
func (m *FieldMap) add(f field) {
266-
m.rwLock.Lock()
267-
defer m.rwLock.Unlock()
268-
269321
t := fieldTag(f)
270322
if _, ok := m.tagLookup[t]; !ok {
271323
m.tags = append(m.tags, t)

message.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,17 @@ func ParseMessageWithDataDictionary(
181181

182182
// doParsing executes the message parsing process.
183183
func doParsing(mp *msgParser) (err error) {
184+
mp.msg.Header.rwLock.Lock()
185+
defer mp.msg.Header.rwLock.Unlock()
186+
mp.msg.Body.rwLock.Lock()
187+
defer mp.msg.Body.rwLock.Unlock()
188+
mp.msg.Trailer.rwLock.Lock()
189+
defer mp.msg.Trailer.rwLock.Unlock()
190+
184191
// Initialize for parsing.
185-
mp.msg.Header.Clear()
186-
mp.msg.Body.Clear()
187-
mp.msg.Trailer.Clear()
192+
mp.msg.Header.clearNoLock()
193+
mp.msg.Body.clearNoLock()
194+
mp.msg.Trailer.clearNoLock()
188195

189196
// Allocate expected message fields in one chunk.
190197
fieldCount := 0
@@ -267,7 +274,7 @@ func doParsing(mp *msgParser) (err error) {
267274
}
268275

269276
if mp.parsedFieldBytes.tag == tagXMLDataLen {
270-
xmlDataLen, _ = mp.msg.Header.GetInt(tagXMLDataLen)
277+
xmlDataLen, _ = mp.msg.Header.getIntNoLock(tagXMLDataLen)
271278
}
272279
mp.fieldIndex++
273280
}
@@ -292,7 +299,7 @@ func doParsing(mp *msgParser) (err error) {
292299
}
293300
}
294301

295-
bodyLength, err := mp.msg.Header.GetInt(tagBodyLength)
302+
bodyLength, err := mp.msg.Header.getIntNoLock(tagBodyLength)
296303
if err != nil {
297304
err = parseError{OrigError: err.Error()}
298305
} else if length != bodyLength && !xmlDataMsg {
@@ -373,7 +380,7 @@ func parseGroup(mp *msgParser, tags []Tag) {
373380
// tags slice will contain multiple tags if the tag in question is found while processing a group already.
374381
func isNumInGroupField(msg *Message, tags []Tag, appDataDictionary *datadictionary.DataDictionary) bool {
375382
if appDataDictionary != nil {
376-
msgt, err := msg.MsgType()
383+
msgt, err := msg.msgTypeNoLock()
377384
if err != nil {
378385
return false
379386
}
@@ -406,7 +413,7 @@ func isNumInGroupField(msg *Message, tags []Tag, appDataDictionary *datadictiona
406413
// tags slice will contain multiple tags if the tag in question is found while processing a group already.
407414
func getGroupFields(msg *Message, tags []Tag, appDataDictionary *datadictionary.DataDictionary) (fields []*datadictionary.FieldDef) {
408415
if appDataDictionary != nil {
409-
msgt, err := msg.MsgType()
416+
msgt, err := msg.msgTypeNoLock()
410417
if err != nil {
411418
return
412419
}
@@ -476,6 +483,10 @@ func (m *Message) MsgType() (string, MessageRejectError) {
476483
return m.Header.GetString(tagMsgType)
477484
}
478485

486+
func (m *Message) msgTypeNoLock() (string, MessageRejectError) {
487+
return m.Header.getStringNoLock(tagMsgType)
488+
}
489+
479490
// IsMsgTypeOf returns true if the Header contains MsgType (tag 35) field and its value is the specified one.
480491
func (m *Message) IsMsgTypeOf(msgType string) bool {
481492
if v, err := m.MsgType(); err == nil {

0 commit comments

Comments
 (0)