github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  */