github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/encoding/ostream.go (about) 1 // Copyright (c) 2016 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 encoding 22 23 import ( 24 "github.com/m3db/m3/src/x/checked" 25 "github.com/m3db/m3/src/x/pool" 26 ) 27 28 const ( 29 initAllocSize = 1024 30 ) 31 32 // ostream encapsulates a writable stream. 33 type ostream struct { 34 // We want to use a checked.Bytes when transferring ownership of the buffer 35 // of the ostream. Unfortunately, the accounting overhead of going through 36 // the checked.Bytes for every write is massive. As a result, we store both 37 // the rawBuffer that backs the checked.Bytes AND the checked.Bytes themselves 38 // in this struct. 39 // 40 // That way, whenever we're writing to the buffer we can avoid the cost accounting 41 // overhead entirely, but when the data needs to be transffered to another owner 42 // we use the checked.Bytes, which is when the accounting really matters anyways. 43 // 44 // The rawBuffer and checked.Bytes may get out of sync as the rawBuffer is written to, 45 // but thats fine because we perform a "repair" by resetting the checked.Bytes underlying 46 // byte slice to be the rawBuffer whenever we expose a checked.Bytes to an external caller. 47 rawBuffer []byte 48 checked checked.Bytes 49 50 pos int // how many bits have been used in the last byte 51 bytesPool pool.CheckedBytesPool 52 } 53 54 // NewOStream creates a new Ostream 55 func NewOStream( 56 bytes checked.Bytes, 57 initAllocIfEmpty bool, 58 bytesPool pool.CheckedBytesPool, 59 ) OStream { 60 if bytes == nil && initAllocIfEmpty { 61 bytes = checked.NewBytes(make([]byte, 0, initAllocSize), nil) 62 } 63 64 stream := &ostream{bytesPool: bytesPool} 65 stream.Reset(bytes) 66 return stream 67 } 68 69 func (os *ostream) Len() int { 70 return len(os.rawBuffer) 71 } 72 73 func (os *ostream) Empty() bool { 74 return os.Len() == 0 && os.pos == 0 75 } 76 77 func (os *ostream) lastIndex() int { 78 return os.Len() - 1 79 } 80 81 func (os *ostream) hasUnusedBits() bool { 82 return os.pos > 0 && os.pos < 8 83 } 84 85 // grow appends the last byte of v to rawBuffer and sets pos to np. 86 func (os *ostream) grow(v byte, np int) { 87 os.ensureCapacityFor(1) 88 os.rawBuffer = append(os.rawBuffer, v) 89 90 os.pos = np 91 } 92 93 // ensureCapacity ensures that there is at least capacity for n more bytes. 94 func (os *ostream) ensureCapacityFor(n int) { 95 var ( 96 currCap = cap(os.rawBuffer) 97 currLen = len(os.rawBuffer) 98 availableCap = currCap - currLen 99 missingCap = n - availableCap 100 ) 101 if missingCap <= 0 { 102 // Already have enough capacity. 103 return 104 } 105 106 newCap := max(cap(os.rawBuffer)*2, currCap+missingCap) 107 if p := os.bytesPool; p != nil { 108 newChecked := p.Get(newCap) 109 newChecked.IncRef() 110 newChecked.AppendAll(os.rawBuffer) 111 112 if os.checked != nil { 113 os.checked.DecRef() 114 os.checked.Finalize() 115 } 116 117 os.checked = newChecked 118 os.rawBuffer = os.checked.Bytes() 119 } else { 120 newRawBuffer := make([]byte, 0, newCap) 121 newRawBuffer = append(newRawBuffer, os.rawBuffer...) 122 os.rawBuffer = newRawBuffer 123 124 os.checked = checked.NewBytes(os.rawBuffer, nil) 125 os.checked.IncRef() 126 } 127 } 128 129 func (os *ostream) fillUnused(v byte) { 130 os.rawBuffer[os.lastIndex()] |= v >> uint(os.pos) 131 } 132 133 func (os *ostream) WriteBit(v Bit) { 134 v <<= 7 135 if !os.hasUnusedBits() { 136 os.grow(byte(v), 1) 137 return 138 } 139 os.fillUnused(byte(v)) 140 os.pos++ 141 } 142 143 func (os *ostream) WriteByte(v byte) { 144 if !os.hasUnusedBits() { 145 os.grow(v, 8) 146 return 147 } 148 os.fillUnused(v) 149 os.grow(v<<uint(8-os.pos), os.pos) 150 } 151 152 func (os *ostream) WriteBytes(bytes []byte) { 153 // Call ensureCapacityFor ahead of time to ensure that the bytes pool is used to 154 // grow the rawBuffer (as opposed to append possibly triggering an allocation if 155 // it wasn't) and that its only grown a maximum of one time regardless of the size 156 // of the []byte being written. 157 os.ensureCapacityFor(len(bytes)) 158 159 if !os.hasUnusedBits() { 160 // If the stream is aligned on a byte boundary then all of the WriteByte() 161 // function calls and bit-twiddling can be skipped in favor of a single 162 // copy operation. 163 os.rawBuffer = append(os.rawBuffer, bytes...) 164 // Position 8 indicates that the last byte of the buffer has been completely 165 // filled. 166 os.pos = 8 167 return 168 } 169 170 for i := 0; i < len(bytes); i++ { 171 os.WriteByte(bytes[i]) 172 } 173 } 174 175 func (os *ostream) Write(bytes []byte) (int, error) { 176 os.WriteBytes(bytes) 177 return len(bytes), nil 178 } 179 180 func (os *ostream) WriteBits(v uint64, numBits int) { 181 if numBits == 0 { 182 return 183 } 184 185 // we should never write more than 64 bits for a uint64 186 if numBits > 64 { 187 numBits = 64 188 } 189 190 v <<= uint(64 - numBits) 191 192 for numBits >= 32 { 193 os.WriteByte(byte(v >> 56)) 194 os.WriteByte(byte(v >> 48)) 195 os.WriteByte(byte(v >> 40)) 196 os.WriteByte(byte(v >> 32)) 197 198 v <<= 32 199 numBits -= 32 200 } 201 202 for numBits >= 8 { 203 os.WriteByte(byte(v >> 56)) 204 v <<= 8 205 numBits -= 8 206 } 207 208 remainder := byte(v >> 56) 209 for numBits > 0 { 210 val := remainder & 0x80 211 // inlined WriteBit 212 if os.hasUnusedBits() { 213 os.fillUnused(val) 214 os.pos++ 215 } else { 216 os.grow(val, 1) 217 } 218 remainder <<= 1 219 numBits-- 220 } 221 } 222 223 func (os *ostream) Discard() checked.Bytes { 224 os.repairCheckedBytes() 225 226 buffer := os.checked 227 buffer.DecRef() 228 229 os.rawBuffer = nil 230 os.pos = 0 231 os.checked = nil 232 233 return buffer 234 } 235 236 func (os *ostream) Reset(buffer checked.Bytes) { 237 if os.checked != nil { 238 // Release ref of the current raw buffer 239 os.checked.DecRef() 240 os.checked.Finalize() 241 242 os.rawBuffer = nil 243 os.checked = nil 244 } 245 246 if buffer != nil { 247 // Track ref to the new raw buffer 248 buffer.IncRef() 249 250 os.checked = buffer 251 os.rawBuffer = os.checked.Bytes() 252 } 253 254 os.pos = 0 255 if os.Len() > 0 { 256 // If the byte array passed in is not empty, we set 257 // pos to 8 indicating the last byte is fully used. 258 os.pos = 8 259 } 260 } 261 262 func (os *ostream) RawBytes() ([]byte, int) { 263 return os.rawBuffer, os.pos 264 } 265 266 func (os *ostream) CheckedBytes() (checked.Bytes, int) { 267 return os.checked, os.pos 268 } 269 270 // repairCheckedBytes makes sure that the checked.Bytes wraps the rawBuffer as 271 // they may have fallen out of sync during the writing process. 272 func (os *ostream) repairCheckedBytes() { 273 if os.checked != nil { 274 os.checked.Reset(os.rawBuffer) 275 } 276 } 277 278 func max(x, y int) int { 279 if x > y { 280 return x 281 } 282 return y 283 }