lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xbufio/seqbuf_ioat.go (about) 1 // Copyright (C) 2017 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 package xbufio 21 // buffering for io.ReaderAt optimized for sequential access 22 23 import ( 24 "io" 25 //"log" 26 ) 27 28 // SeqReaderAt implements buffering for a io.ReaderAt optimized for sequential access. 29 // 30 // Both forward, backward and interleaved forward/backward access patterns are supported 31 // 32 // NOTE SeqReaderAt is not safe to use from multiple goroutines concurrently. 33 // 34 // Strictly speaking this goes against io.ReaderAt interface but sequential 35 // workloads usually mean sequential processing. It would be a pity to 36 // add mutex for nothing. 37 type SeqReaderAt struct { 38 // buffer for data at pos. cap(buf) - whole buffer capacity 39 buf []byte 40 pos int64 41 42 posLastAccess int64 // position of last access request 43 posLastFwdAfter int64 // position of last forward access request 44 posLastBackward int64 // position of last backward access request 45 46 r io.ReaderAt 47 48 // debug: for ioReadAt tracing 49 //posLastIO int64 50 } 51 52 const defaultSeqBufSize = 8192 53 54 // NewSeqReaderAt wraps r with SeqReaderAt with buffer of default size. 55 func NewSeqReaderAt(r io.ReaderAt) *SeqReaderAt { 56 return NewSeqReaderAtSize(r, defaultSeqBufSize) 57 } 58 59 // NewSeqReaderAtSize wraps r with SeqReaderAt with buffer of specified size. 60 func NewSeqReaderAtSize(r io.ReaderAt, size int) *SeqReaderAt { 61 sb := &SeqReaderAt{r: r, buf: make([]byte, 0, size)} // all positions are zero initially 62 return sb 63 } 64 65 // // XXX temp 66 // func init() { 67 // log.SetFlags(0) 68 // } 69 70 // debug helper for sb.r.ReadAt 71 func (sb *SeqReaderAt) ioReadAt(p []byte, pos int64) (int, error) { 72 /* 73 verb := "read" 74 if len(p) > cap(sb.buf) { 75 verb = "READ" 76 } 77 log.Printf("%s\t[%v, %v)\t#%v\tIO%+d", verb, pos, pos + len64(p), len(p), pos - sb.posLastIO) 78 sb.posLastIO = pos 79 */ 80 return sb.r.ReadAt(p, pos) 81 } 82 83 func (sb *SeqReaderAt) ReadAt(p []byte, pos int64) (int, error) { 84 //log.Printf("access\t[%v, %v)\t#%v\t@%+d", pos, pos + len64(p), len(p), pos - sb.posLastAccess) 85 86 // read-in last access positions and update them in *sb with current ones for next read 87 posLastAccess := sb.posLastAccess 88 posLastFwdAfter := sb.posLastFwdAfter 89 posLastBackward := sb.posLastBackward 90 sb.posLastAccess = pos 91 if pos >= posLastAccess { 92 sb.posLastFwdAfter = pos + len64(p) 93 } else { 94 sb.posLastBackward = pos 95 } 96 97 // if request size > buffer - read data directly 98 if len(p) > cap(sb.buf) { 99 // no copying from sb.buf here at all as if e.g. we could copy from sb.buf, the 100 // kernel can copy the same data from pagecache as well, and it will take the same time 101 // because for data in sb.buf corresponding page in pagecache has high p. to be hot. 102 return sb.ioReadAt(p, pos) 103 } 104 105 var nhead int // #data read from buffer for p head 106 var ntail int // #data read from buffer for p tail 107 108 // try to satisfy read request via (partly) reading from buffer 109 110 // use buffered data: head(p) 111 if sb.pos <= pos && pos < sb.pos + len64(sb.buf) { 112 nhead = copy(p, sb.buf[pos - sb.pos:]) // NOTE len(p) can be < len(sb[copyPos:]) 113 114 // if all was read from buffer - we are done 115 if nhead == len(p) { 116 return nhead, nil 117 } 118 119 p = p[nhead:] 120 pos += int64(nhead) 121 122 // empty request (possibly not hitting buffer - do not let it go to real IO path) 123 // `len(p) != 0` is also needed for backward reading from buffer, so this condition goes before 124 } else if len(p) == 0 { 125 return 0, nil 126 127 // use buffered data: tail(p) 128 } else if posAfter := pos + len64(p); 129 sb.pos < posAfter && posAfter <= sb.pos + len64(sb.buf) { 130 // here we know pos < sb.pos 131 // 132 // proof: consider if pos >= sb.pos. 133 // Then from `pos <= sb.pos + len(sb.buf) - len(p)` above it follow that: 134 // `pos < sb.pos + len(sb.buf)` (NOTE strictly < because len(p) > 0) 135 // and we come to condition which is used in `start + forward` if 136 ntail = copy(p[sb.pos - pos:], sb.buf) // NOTE ntail == len(p[sb.pos - pos:]) 137 138 // NOTE no return here: full p read is impossible for backward 139 // p filling: it would mean `pos = sb.pos` which in turn means 140 // the condition for forward buf reading must have been triggered. 141 142 p = p[:sb.pos - pos] 143 // pos stays the same 144 } 145 146 147 // here we need to refill the buffer. determine range to read by current IO direction. 148 // NOTE len(p) <= cap(sb.buf) 149 var xpos int64 // position for new IO request 150 151 if pos >= posLastAccess { 152 // forward 153 xpos = pos 154 155 // if forward trend continues and buffering can be made adjacent to 156 // previous forward access - shift reading down right to after it. 157 xLastFwdAfter := posLastFwdAfter + int64(nhead) // adjusted for already read from buffer 158 if xLastFwdAfter <= xpos && xpos + len64(p) <= xLastFwdAfter + cap64(sb.buf) { 159 xpos = xLastFwdAfter 160 } 161 162 // NOTE no symmetry handling for "alternatively" in backward case 163 // because symmetry would be: 164 // 165 // ← → ← → 166 // 3 4 2 1 167 // 168 // but buffer for 4 already does not overlap 3 as for 169 // non-trendy forward reads buffer always grows forward. 170 171 } else { 172 // backward 173 xpos = pos 174 175 // if backward trend continues and buffering would overlap with 176 // previous backward access - shift reading up right to it. 177 xLastBackward := posLastBackward - int64(ntail) // adjusted for already read from buffer 178 if xpos < xLastBackward && xLastBackward < xpos + cap64(sb.buf) { 179 xpos = max64(xLastBackward, xpos + len64(p)) - cap64(sb.buf) 180 181 // alternatively even if backward trend does not continue anymore 182 // but if this will overlap with last access range, probably 183 // it is better (we are optimizing for sequential access) to 184 // shift loading region down not to overlap. example: 185 // 186 // ← → ← → 187 // 2 1 4 3 188 // 189 // here we do not want 4'th buffer to overlap with 3 190 } else if xpos + cap64(sb.buf) > posLastAccess { 191 xpos = max64(posLastAccess, xpos + len64(p)) - cap64(sb.buf) 192 } 193 194 // don't let reading go beyond start of the file 195 xpos = max64(xpos, 0) 196 } 197 198 nn, err := sb.ioReadAt(sb.buf[:cap(sb.buf)], xpos) 199 200 // even if there was an error, or data partly read, we cannot retain 201 // the old buf content as io.ReaderAt can use whole buf as scratch space 202 sb.pos = xpos 203 sb.buf = sb.buf[:nn] 204 205 // here we know: 206 // - some data was read 207 // - in case of successful read pos/p lies completely inside sb.pos/sb.buf 208 209 // copy loaded data from buffer to p 210 pBufOffset := pos - xpos // offset corresponding to p in sb.buf 211 if pBufOffset >= len64(sb.buf) { 212 // this can be only due to some IO error 213 214 // if original request was narrower than buffer try to satisfy 215 // it once again directly 216 if pos != xpos { 217 nn, err = sb.ioReadAt(p, pos) 218 if nn < len(p) { 219 return nhead + nn, err 220 } 221 return nhead + nn + ntail, nil // request fully satisfied - we can ignore error 222 } 223 224 // Just return the error 225 return nhead, err 226 } 227 nn = copy(p, sb.buf[pBufOffset:]) 228 if nn < len(p) { 229 // some error - do not account tail - we did not get to it 230 return nhead + nn, err 231 } 232 233 // all ok 234 // NOTE if there was an error - we can skip it if original read request was completely satisfied 235 // NOTE not preserving EOF at ends - not required per ReaderAt interface 236 return nhead + nn + ntail, nil 237 } 238 239 240 // utilities: 241 242 // len and cap as int64 (we frequently need them and int64 is covering int so 243 // the conversion is not lossy) 244 func len64(b []byte) int64 { return int64(len(b)) } 245 func cap64(b []byte) int64 { return int64(cap(b)) } 246 247 // min/max 248 func min64(a, b int64) int64 { if a < b { return a } else { return b} } 249 func max64(a, b int64) int64 { if a > b { return a } else { return b} }