Skip to content

Commit 500329a

Browse files
committed
(improvement) frame: Optimize buffer read operations for better branch prediction
Convert all framer read* methods from error-first to happy-path-first pattern to improve CPU branch prediction. Modern processors predict forward branches (common case) more efficiently than backward branches (error paths). Since there's no `err != nil` checks that the compiler knows well to handle with branch prediction, this is a reasonable change. Changes: - Reorder all read* functions: check buffer has sufficient data first, process and return on success, panic on error path - Affects: readByte, readInt, readShort, readString, skipString, readLongString, ReadBytesInternal, readBytes, readBytesCopy, readShortBytes, readInetAdressOnly - No functional changes, only reordering of conditionals Benchmark: BenchmarkRowData shows stable performance at 463.0 ns/op (no regression from 464.6 ns/op baseline). This is unlikely to be seen in most cases, but also reasonable for readability. Signed-off-by: Yaniv Kaul <yaniv.kaul@scylladb.com>
1 parent 655a895 commit 500329a

File tree

1 file changed

+63
-84
lines changed

1 file changed

+63
-84
lines changed

frame.go

Lines changed: 63 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,67 +1454,60 @@ func (f *framer) writeRegisterFrame(streamID int, w *writeRegisterFrame) error {
14541454
}
14551455

14561456
func (f *framer) readByte() byte {
1457-
if len(f.buf) < 1 {
1458-
panic(fmt.Errorf("not enough bytes in buffer to read byte require 1 got: %d", len(f.buf)))
1457+
if len(f.buf) >= 1 {
1458+
b := f.buf[0]
1459+
f.buf = f.buf[1:]
1460+
return b
14591461
}
1460-
1461-
b := f.buf[0]
1462-
f.buf = f.buf[1:]
1463-
return b
1462+
panic(fmt.Errorf("not enough bytes in buffer to read byte require 1 got: %d", len(f.buf)))
14641463
}
14651464

14661465
func (f *framer) readInt() (n int) {
1467-
if len(f.buf) < 4 {
1468-
panic(fmt.Errorf("not enough bytes in buffer to read int require 4 got: %d", len(f.buf)))
1466+
if len(f.buf) >= 4 {
1467+
n = int(int32(binary.BigEndian.Uint32(f.buf[:4])))
1468+
f.buf = f.buf[4:]
1469+
return
14691470
}
1470-
1471-
n = int(int32(binary.BigEndian.Uint32(f.buf[:4])))
1472-
f.buf = f.buf[4:]
1473-
return
1471+
panic(fmt.Errorf("not enough bytes in buffer to read int require 4 got: %d", len(f.buf)))
14741472
}
14751473

14761474
func (f *framer) readShort() (n uint16) {
1477-
if len(f.buf) < 2 {
1478-
panic(fmt.Errorf("not enough bytes in buffer to read short require 2 got: %d", len(f.buf)))
1475+
if len(f.buf) >= 2 {
1476+
n = binary.BigEndian.Uint16(f.buf[:2])
1477+
f.buf = f.buf[2:]
1478+
return
14791479
}
1480-
n = binary.BigEndian.Uint16(f.buf[:2])
1481-
f.buf = f.buf[2:]
1482-
return
1480+
panic(fmt.Errorf("not enough bytes in buffer to read short require 2 got: %d", len(f.buf)))
14831481
}
14841482

14851483
func (f *framer) readString() (s string) {
14861484
size := f.readShort()
1487-
1488-
if len(f.buf) < int(size) {
1489-
panic(fmt.Errorf("not enough bytes in buffer to read string require %d got: %d", size, len(f.buf)))
1485+
if len(f.buf) >= int(size) {
1486+
s = string(f.buf[:size])
1487+
f.buf = f.buf[size:]
1488+
return
14901489
}
1491-
1492-
s = string(f.buf[:size])
1493-
f.buf = f.buf[size:]
1494-
return
1490+
panic(fmt.Errorf("not enough bytes in buffer to read string require %d got: %d", size, len(f.buf)))
14951491
}
14961492

14971493
// skipString advances the buffer past a string without allocating
14981494
func (f *framer) skipString() {
14991495
size := f.readShort()
1500-
1501-
if len(f.buf) < int(size) {
1502-
panic(fmt.Errorf("not enough bytes in buffer to skip string require %d got: %d", size, len(f.buf)))
1496+
if len(f.buf) >= int(size) {
1497+
f.buf = f.buf[size:]
1498+
return
15031499
}
1504-
1505-
f.buf = f.buf[size:]
1500+
panic(fmt.Errorf("not enough bytes in buffer to skip string require %d got: %d", size, len(f.buf)))
15061501
}
15071502

15081503
func (f *framer) readLongString() (s string) {
15091504
size := f.readInt()
1510-
1511-
if len(f.buf) < size {
1512-
panic(fmt.Errorf("not enough bytes in buffer to read long string require %d got: %d", size, len(f.buf)))
1505+
if len(f.buf) >= size {
1506+
s = string(f.buf[:size])
1507+
f.buf = f.buf[size:]
1508+
return
15131509
}
1514-
1515-
s = string(f.buf[:size])
1516-
f.buf = f.buf[size:]
1517-
return
1510+
panic(fmt.Errorf("not enough bytes in buffer to read long string require %d got: %d", size, len(f.buf)))
15181511
}
15191512

15201513
func (f *framer) readStringList() []string {
@@ -1533,81 +1526,67 @@ func (f *framer) ReadBytesInternal() ([]byte, error) {
15331526
if size < 0 {
15341527
return nil, nil
15351528
}
1536-
1537-
if len(f.buf) < size {
1538-
return nil, fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.buf))
1529+
if len(f.buf) >= size {
1530+
l := f.buf[:size]
1531+
f.buf = f.buf[size:]
1532+
return l, nil
15391533
}
1540-
1541-
l := f.buf[:size]
1542-
f.buf = f.buf[size:]
1543-
1544-
return l, nil
1534+
return nil, fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.buf))
15451535
}
15461536

15471537
func (f *framer) readBytes() []byte {
15481538
size := f.readInt()
15491539
if size < 0 {
15501540
return nil
15511541
}
1552-
1553-
if len(f.buf) < size {
1554-
panic(fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.buf)))
1542+
if len(f.buf) >= size {
1543+
l := f.buf[:size]
1544+
f.buf = f.buf[size:]
1545+
return l
15551546
}
1556-
1557-
l := f.buf[:size]
1558-
f.buf = f.buf[size:]
1559-
1560-
return l
1547+
panic(fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.buf)))
15611548
}
15621549

15631550
func (f *framer) readBytesCopy() []byte {
15641551
size := f.readInt()
15651552
if size < 0 {
15661553
return nil
15671554
}
1568-
1569-
if len(f.buf) < size {
1570-
panic(fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.buf)))
1555+
if len(f.buf) >= size {
1556+
out := make([]byte, size)
1557+
copy(out, f.buf[:size])
1558+
f.buf = f.buf[size:]
1559+
return out
15711560
}
1572-
1573-
out := make([]byte, size)
1574-
copy(out, f.buf[:size])
1575-
f.buf = f.buf[size:]
1576-
return out
1561+
panic(fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.buf)))
15771562
}
15781563

15791564
func (f *framer) readShortBytes() []byte {
15801565
size := f.readShort()
1581-
if len(f.buf) < int(size) {
1582-
panic(fmt.Errorf("not enough bytes in buffer to read short bytes: require %d got %d", size, len(f.buf)))
1566+
if len(f.buf) >= int(size) {
1567+
l := f.buf[:size]
1568+
f.buf = f.buf[size:]
1569+
return l
15831570
}
1584-
1585-
l := f.buf[:size]
1586-
f.buf = f.buf[size:]
1587-
1588-
return l
1571+
panic(fmt.Errorf("not enough bytes in buffer to read short bytes: require %d got %d", size, len(f.buf)))
15891572
}
15901573

15911574
func (f *framer) readInetAdressOnly() net.IP {
1592-
if len(f.buf) < 1 {
1593-
panic(fmt.Errorf("not enough bytes in buffer to read inet size require %d got: %d", 1, len(f.buf)))
1594-
}
1595-
1596-
size := f.buf[0]
1597-
f.buf = f.buf[1:]
1598-
1599-
if !(size == 4 || size == 16) {
1600-
panic(fmt.Errorf("invalid IP size: %d", size))
1601-
}
1602-
1603-
if len(f.buf) < int(size) {
1575+
if len(f.buf) >= 1 {
1576+
size := f.buf[0]
1577+
f.buf = f.buf[1:]
1578+
if !(size == 4 || size == 16) {
1579+
panic(fmt.Errorf("invalid IP size: %d", size))
1580+
}
1581+
if len(f.buf) >= int(size) {
1582+
ip := make(net.IP, size)
1583+
copy(ip, f.buf[:size])
1584+
f.buf = f.buf[size:]
1585+
return ip
1586+
}
16041587
panic(fmt.Errorf("not enough bytes in buffer to read inet require %d got: %d", size, len(f.buf)))
16051588
}
1606-
1607-
ip := make(net.IP, size)
1608-
copy(ip, f.buf[:size])
1609-
f.buf = f.buf[size:]
1610-
return ip
1589+
panic(fmt.Errorf("not enough bytes in buffer to read inet size require %d got: %d", 1, len(f.buf)))
16111590
}
16121591

16131592
func (f *framer) readInet() (net.IP, int) {

0 commit comments

Comments
 (0)