internal/impl: inline coderInfoFields for better cache locality

Microbenchmarks are inconclusive/noisy, but shows a small but noticeable
improvement on internal benchmarks.

Change-Id: Ic46c6aac8a42c4dc749c4f3583d8c8c95e9548b7
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/229277
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/internal/impl/codec_message.go b/internal/impl/codec_message.go
index 370ec65..03b096e 100644
--- a/internal/impl/codec_message.go
+++ b/internal/impl/codec_message.go
@@ -31,6 +31,11 @@
 	needsInitCheck     bool
 	isMessageSet       bool
 	numRequiredFields  uint8
+
+	// Include space for a number of coderFieldInfos to improve cache locality.
+	// The number of entries is chosen through a combination of guesswork and
+	// empirical testing.
+	coderFieldBuf [32]coderFieldInfo
 }
 
 type coderFieldInfo struct {
@@ -53,6 +58,7 @@
 
 	mi.coderFields = make(map[protowire.Number]*coderFieldInfo)
 	fields := mi.Desc.Fields()
+	preallocFields := mi.coderFieldBuf[:]
 	for i := 0; i < fields.Len(); i++ {
 		fd := fields.Get(i)
 
@@ -80,7 +86,14 @@
 			fieldOffset = offsetOf(fs, mi.Exporter)
 			childMessage, funcs = fieldCoder(fd, ft)
 		}
-		cf := &coderFieldInfo{
+		var cf *coderFieldInfo
+		if len(preallocFields) > 0 {
+			cf = &preallocFields[0]
+			preallocFields = preallocFields[1:]
+		} else {
+			cf = new(coderFieldInfo)
+		}
+		*cf = coderFieldInfo{
 			num:        fd.Number(),
 			offset:     fieldOffset,
 			wiretag:    wiretag,