add end-to-end IO benchmarks and fix pprof-identified hotspots

Add comprehensive benchmark suite (io_bench_test.go):
- BenchmarkEndToEndRead/Write: full SCSI stack (512B to 256KB)
- BenchmarkEndToEndReadParallel/WriteParallel: concurrent IO
- BenchmarkFileBackingStoreRead/Write: isolated backing store

pprof-guided optimizations:
- Guard hot-path log.Debugf with log.GetLevel() check in scsi.go,
  sbc.go, backingstore.go — eliminates 22% CPU overhead from logrus
  Entry allocation even when debug logging is disabled
- Add FileBackingStore.ReadAt for zero-copy reads directly into
  caller's buffer, bypassing Read()'s per-call make([]byte, tl)
- Use ReadAt via interface assertion in bsPerformCommand to read
  directly into InSDBBuffer, eliminating allocation + copy

Results (256KB reads): +42% throughput, allocs reduced from 10 to 5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lei Xue
2026-03-14 19:41:48 +08:00
parent 87c25cf5cd
commit a5628f4ec0
5 changed files with 380 additions and 10 deletions

View File

@@ -109,13 +109,38 @@ func bsPerformCommand(bs api.BackingStore, cmd *api.SCSICommand) (err error, key
// TODO
break
case api.READ_6, api.READ_10, api.READ_12, api.READ_16:
rbuf, err = bs.Read(int64(offset), tl)
if err != nil && err != io.EOF {
key = MEDIUM_ERROR
asc = ASC_READ_ERROR
break
// Read directly into InSDBBuffer to avoid extra allocation + copy
if int64(cmd.InSDBBuffer.Length) >= tl {
if reader, ok := bs.(interface {
ReadAt(buf []byte, offset int64) (int, error)
}); ok {
length, err = reader.ReadAt(cmd.InSDBBuffer.Buffer[:tl], int64(offset))
if err != nil && err != io.EOF {
key = MEDIUM_ERROR
asc = ASC_READ_ERROR
break
}
err = nil
} else {
rbuf, err = bs.Read(int64(offset), tl)
if err != nil && err != io.EOF {
key = MEDIUM_ERROR
asc = ASC_READ_ERROR
break
}
length = len(rbuf)
copy(cmd.InSDBBuffer.Buffer, rbuf)
}
} else {
rbuf, err = bs.Read(int64(offset), tl)
if err != nil && err != io.EOF {
key = MEDIUM_ERROR
asc = ASC_READ_ERROR
break
}
length = len(rbuf)
copy(cmd.InSDBBuffer.Buffer, rbuf)
}
length = len(rbuf)
if (opcode != api.READ_6) && (scb[1]&0x10 != 0) {
bs.DataAdvise(int64(offset), int64(length), util.POSIX_FADV_NOREUSE)
@@ -126,7 +151,6 @@ func bsPerformCommand(bs api.BackingStore, cmd *api.SCSICommand) (err error, key
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
copy(cmd.InSDBBuffer.Buffer, rbuf)
// Zero-fill any remaining bytes if read was short
if length < int(tl) {
for i := length; i < int(tl) && i < len(cmd.InSDBBuffer.Buffer); i++ {
@@ -158,7 +182,9 @@ write:
asc = ASC_WRITE_ERROR
goto sense
}
log.Debugf("write data at 0x%x for length %d", offset, len(wbuf))
if log.GetLevel() >= log.DebugLevel {
log.Debugf("write data at 0x%x for length %d", offset, len(wbuf))
}
var pg *api.ModePage
for _, p := range lu.ModePages {
if p.PageCode == 0x08 && p.SubPageCode == 0 {