github.com/m3db/m3@v1.5.0/src/dbnode/persist/fs/msgpack/encoder_fast.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE 20 21 package msgpack 22 23 import ( 24 "math" 25 26 "github.com/m3db/m3/src/dbnode/persist/schema" 27 28 "gopkg.in/vmihailenco/msgpack.v2/codes" 29 ) 30 31 // EncodeLogEntryFast encodes a commit log entry with no buffering and using optimized helper 32 // functions that bypass the msgpack encoding library by manually inlining the equivalent code. 33 // 34 // The reason we had to bypass the msgpack encoding library is that during perf testing we found that 35 // this function was spending most of its time setting up stack frames for function calls. While 36 // the overhead of a function call in Golang is small, when every helper function does nothing more 37 // than write a few bytes to an in-memory array the function call overhead begins to dominate, 38 // especially when each call to this function results in dozens of such helper function calls. 39 // 40 // Manually inlining the msgpack encoding results in a lot of code duplication for this one path, but 41 // we pay the price because this is the most frequently called function in M3DB and it indirectly 42 // applies back-pressure on every other part of the system via the commitlog queue. As a result, almost 43 // any performance gains that can be had in this function are worth it. 44 // 45 // Before modifying this function, please run the BenchmarkLogEntryEncoderFast benchmark as a small 46 // degradation in this functions performance can have a substantial impact on M3DB. 47 // 48 // 49 // Also note that there are extensive prop tests for this function in the encoder_decoder_prop_test.go 50 // file which verify its correctness. 51 func EncodeLogEntryFast(b []byte, entry schema.LogEntry) ([]byte, error) { 52 if logEntryHeaderErr != nil { 53 return nil, logEntryHeaderErr 54 } 55 56 // TODO(rartoul): Can optimize this further by storing the version as part of the 57 // info for the commit log file itself, instead of in every entry. 58 // https://github.com/m3db/m3/issues/1161 59 b = append(b, logEntryHeader...) 60 b = encodeVarUint64(b, entry.Index) 61 b = encodeVarInt64(b, entry.Create) 62 b = encodeBytes(b, entry.Metadata) 63 b = encodeVarInt64(b, entry.Timestamp) 64 b = encodeFloat64(b, entry.Value) 65 b = encodeVarUint64(b, uint64(entry.Unit)) 66 b = encodeBytes(b, entry.Annotation) 67 68 return b, nil 69 } 70 71 // EncodeLogMetadataFast is the same as EncodeLogEntryFast except for the metadata 72 // entries instead of the data entries. 73 func EncodeLogMetadataFast(b []byte, entry schema.LogMetadata) ([]byte, error) { 74 if logMetadataHeaderErr != nil { 75 return nil, logMetadataHeaderErr 76 } 77 78 // TODO(rartoul): Can optimize this further by storing the version as part of the 79 // info for the commit log file itself, instead of in every entry. 80 // https://github.com/m3db/m3/issues/1161 81 b = append(b, logMetadataHeader...) 82 b = encodeBytes(b, entry.ID) 83 b = encodeBytes(b, entry.Namespace) 84 b = encodeVarUint64(b, uint64(entry.Shard)) 85 b = encodeBytes(b, entry.EncodedTags) 86 87 return b, nil 88 } 89 90 func growAndReturn(b []byte, n int) ([]byte, []byte) { 91 if cap(b)-len(b) < n { 92 newCapacity := 2 * (len(b) + n) 93 newBuff := make([]byte, len(b), newCapacity) 94 copy(newBuff, b) 95 b = newBuff 96 } 97 ret := b[:len(b)+n] 98 return ret, ret[len(b):] 99 } 100 101 // encodeVarUint64 is used in EncodeLogEntryFast which is a very hot path. 102 // As a result, many of the function calls in this function have been 103 // manually inlined to reduce function call overhead. 104 func encodeVarUint64(b []byte, v uint64) []byte { 105 if v <= math.MaxInt8 { 106 b, buf := growAndReturn(b, 1) 107 buf[0] = byte(v) 108 return b 109 } 110 111 if v <= math.MaxUint8 { 112 // Equivalent to: return write1(b, codes.Uint8, v) 113 b, buf := growAndReturn(b, 2) 114 buf[0] = codes.Uint8 115 buf[1] = byte(v) 116 return b 117 } 118 119 if v <= math.MaxUint16 { 120 // Equivalent to: return write2(b, codes.Uint16, v) 121 b, buf := growAndReturn(b, 3) 122 buf[0] = codes.Uint16 123 buf[1] = byte(v >> 8) 124 buf[2] = byte(v) 125 return b 126 } 127 128 if v <= math.MaxUint32 { 129 // Equivalent to: return write4(b, codes.Uint32, v) 130 b, buf := growAndReturn(b, 5) 131 buf[0] = codes.Uint32 132 buf[1] = byte(v >> 24) 133 buf[2] = byte(v >> 16) 134 buf[3] = byte(v >> 8) 135 buf[4] = byte(v) 136 return b 137 } 138 139 // Equivalent to: return write8(b, codes.Uint64, v) 140 b, buf := growAndReturn(b, 9) 141 buf[0] = codes.Uint64 142 buf[1] = byte(v >> 56) 143 buf[2] = byte(v >> 48) 144 buf[3] = byte(v >> 40) 145 buf[4] = byte(v >> 32) 146 buf[5] = byte(v >> 24) 147 buf[6] = byte(v >> 16) 148 buf[7] = byte(v >> 8) 149 buf[8] = byte(v) 150 151 return b 152 } 153 154 // encodeVarInt64 is used in EncodeLogEntryFast which is a very hot path. 155 // As a result, many of the function calls in this function have been 156 // manually inlined to reduce function call overhead. 157 func encodeVarInt64(b []byte, v int64) []byte { 158 if v >= 0 { 159 // Equivalent to: return encodeVarUint64(b, uint64(v)) 160 if v <= math.MaxInt8 { 161 b, buf := growAndReturn(b, 1) 162 buf[0] = byte(v) 163 return b 164 } 165 166 if v <= math.MaxUint8 { 167 // Equivalent to: return write1(b, codes.Uint8, v) 168 b, buf := growAndReturn(b, 2) 169 buf[0] = codes.Uint8 170 buf[1] = byte(v) 171 return b 172 } 173 174 if v <= math.MaxUint16 { 175 // Equivalent to: return write2(b, codes.Uint16, v) 176 b, buf := growAndReturn(b, 3) 177 buf[0] = codes.Uint16 178 buf[1] = byte(v >> 8) 179 buf[2] = byte(v) 180 return b 181 } 182 183 if v <= math.MaxUint32 { 184 // Equivalent to: return write4(b, codes.Uint32, v) 185 b, buf := growAndReturn(b, 5) 186 buf[0] = codes.Uint32 187 buf[1] = byte(v >> 24) 188 buf[2] = byte(v >> 16) 189 buf[3] = byte(v >> 8) 190 buf[4] = byte(v) 191 return b 192 } 193 194 // Equivalent to: return write8(b, codes.Uint64, v) 195 b, buf := growAndReturn(b, 9) 196 buf[0] = codes.Uint64 197 buf[1] = byte(v >> 56) 198 buf[2] = byte(v >> 48) 199 buf[3] = byte(v >> 40) 200 buf[4] = byte(v >> 32) 201 buf[5] = byte(v >> 24) 202 buf[6] = byte(v >> 16) 203 buf[7] = byte(v >> 8) 204 buf[8] = byte(v) 205 return b 206 } 207 208 if v >= int64(int8(codes.NegFixedNumLow)) { 209 b, buff := growAndReturn(b, 1) 210 buff[0] = byte(v) 211 return b 212 } 213 214 if v >= math.MinInt8 { 215 // Equivalent to: return write1(b, codes.Int8, uint64(v)) 216 b, buf := growAndReturn(b, 2) 217 buf[0] = codes.Int8 218 buf[1] = byte(uint64(v)) 219 return b 220 } 221 222 if v >= math.MinInt16 { 223 // Equivalent to: return write2(b, codes.Int16, uint64(v)) 224 b, buf := growAndReturn(b, 3) 225 n := uint64(v) 226 buf[0] = codes.Int16 227 buf[1] = byte(n >> 8) 228 buf[2] = byte(n) 229 return b 230 } 231 232 if v >= math.MinInt32 { 233 // Equivalent to: return write4(b, codes.Int32, uint64(v)) 234 b, buf := growAndReturn(b, 5) 235 n := uint64(v) 236 buf[0] = codes.Int32 237 buf[1] = byte(n >> 24) 238 buf[2] = byte(n >> 16) 239 buf[3] = byte(n >> 8) 240 buf[4] = byte(n) 241 return b 242 } 243 244 // Equivalent to: return write8(b, codes.Int64, uint64(v)) 245 b, buf := growAndReturn(b, 9) 246 n := uint64(v) 247 buf[0] = codes.Int64 248 buf[1] = byte(n >> 56) 249 buf[2] = byte(n >> 48) 250 buf[3] = byte(n >> 40) 251 buf[4] = byte(n >> 32) 252 buf[5] = byte(n >> 24) 253 buf[6] = byte(n >> 16) 254 buf[7] = byte(n >> 8) 255 buf[8] = byte(n) 256 return b 257 } 258 259 // encodeFloat64 is used in EncodeLogEntryFast which is a very hot path. 260 // As a result, many of the function calls in this function have been 261 // manually inlined to reduce function call overhead. 262 func encodeFloat64(b []byte, v float64) []byte { 263 // Equivalent to: return write8(b, codes.Double, math.Float64bits(n)) 264 b, buf := growAndReturn(b, 9) 265 buf[0] = codes.Double 266 n := math.Float64bits(v) 267 buf[1] = byte(n >> 56) 268 buf[2] = byte(n >> 48) 269 buf[3] = byte(n >> 40) 270 buf[4] = byte(n >> 32) 271 buf[5] = byte(n >> 24) 272 buf[6] = byte(n >> 16) 273 buf[7] = byte(n >> 8) 274 buf[8] = byte(n) 275 return b 276 } 277 278 // encodeBytes is used in EncodeLogEntryFast which is a very hot path. 279 // As a result, many of the function calls in this function have been 280 // manually inlined to reduce function call overhead. 281 func encodeBytes(b []byte, data []byte) []byte { 282 if data == nil { 283 b, buf := growAndReturn(b, 1) 284 buf[0] = codes.Nil 285 return b 286 } 287 288 // Equivalent to: encodeBytesLen(b, len(data)) 289 var ( 290 l = len(data) 291 v = uint64(l) 292 buf []byte 293 ) 294 if l < 256 { 295 b, buf = growAndReturn(b, 2) 296 buf[0] = codes.Bin8 297 buf[1] = byte(v) 298 } else if l < 65536 { 299 b, buf = growAndReturn(b, 3) 300 buf[0] = codes.Bin16 301 buf[1] = byte(v >> 8) 302 buf[2] = byte(v) 303 } else { 304 b, buf = growAndReturn(b, 5) 305 buf[0] = codes.Bin32 306 buf[1] = byte(v >> 24) 307 buf[2] = byte(v >> 16) 308 buf[3] = byte(v >> 8) 309 buf[4] = byte(v) 310 } 311 312 b = append(b, data...) 313 return b 314 } 315 316 /* 317 These functions are not used, but are left here to demonstrate what the manually 318 // inlined versions of these functions should look like. 319 func encodeBytesLen(b []byte, l int) []byte { 320 if l < 256 { 321 return write1(b, codes.Bin8, uint64(l)) 322 } 323 if l < 65536 { 324 return write2(b, codes.Bin16, uint64(l)) 325 } 326 return write4(b, codes.Bin32, uint64(l)) 327 } 328 329 func write1(b []byte, code byte, n uint64) []byte { 330 b, buf := growAndReturn(b, 2) 331 buf[0] = code 332 buf[1] = byte(n) 333 return b 334 } 335 336 func write2(b []byte, code byte, n uint64) []byte { 337 b, buf := growAndReturn(b, 3) 338 buf[0] = code 339 buf[1] = byte(n >> 8) 340 buf[2] = byte(n) 341 return b 342 } 343 344 func write4(b []byte, code byte, n uint64) []byte { 345 b, buf := growAndReturn(b, 5) 346 buf[0] = code 347 buf[1] = byte(n >> 24) 348 buf[2] = byte(n >> 16) 349 buf[3] = byte(n >> 8) 350 buf[4] = byte(n) 351 return b 352 } 353 354 func write8(b []byte, code byte, n uint64) []byte { 355 b, buf := growAndReturn(b, 9) 356 buf[0] = code 357 buf[1] = byte(n >> 56) 358 buf[2] = byte(n >> 48) 359 buf[3] = byte(n >> 40) 360 buf[4] = byte(n >> 32) 361 buf[5] = byte(n >> 24) 362 buf[6] = byte(n >> 16) 363 buf[7] = byte(n >> 8) 364 buf[8] = byte(n) 365 return b 366 } 367 */