Skip to content

Commit

Permalink
kv/tscache: use unsafe transmute for cacheValue encoding/decoding
Browse files Browse the repository at this point in the history
This commit switches the byte encoding of a cacheValue from a custom
encoding to an encoding that directly matches the in-memory representation
of the struct. This allows for encoding and decoding to use an unsafe
re-interpretation of the struct as a bytes slice, eliminating all bounds
checks and byte assignments and replacing them with a single memcpy.

The effect of this is dramatically faster encoding and decoding time in
exchange for a slightly larger encoded size. Encoding time (run once when
adding to the timestamp cache) drops from **9ns** to **.3ns**. Decoding time
(run once per conflicting key when adding and reading to the timestamp
cache) drops from **16ns** to **3ns**. In exchange, the actual encoded byte
size increases from 29 bytes to 32 bytes. This isn't great as it means that
skiplist pages will fill up **10%** faster, but that seems well worth the
gains in encoding/decoding speed.

```
name                                                                 old time/op    new time/op    delta
IntervalSklEncodeValue/logical=false,synthetic=true,txnID=true-16      9.27ns ±47%    0.32ns ±37%  -96.58%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=true,synthetic=true,txnID=true-16       9.66ns ±54%    0.33ns ±37%  -96.57%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=true,synthetic=false,txnID=true-16      9.31ns ±40%    0.32ns ±42%  -96.56%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=true,synthetic=true,txnID=false-16      8.73ns ±31%    0.30ns ±39%  -96.53%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=true,synthetic=false,txnID=false-16     8.93ns ±34%    0.32ns ±28%  -96.44%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=false,synthetic=false,txnID=true-16     9.03ns ±33%    0.32ns ±50%  -96.42%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=false,synthetic=true,txnID=false-16     9.14ns ±32%    0.33ns ±35%  -96.36%  (p=0.008 n=5+5)
IntervalSklEncodeValue/logical=false,synthetic=false,txnID=false-16    8.72ns ±27%    0.33ns ±43%  -96.17%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=true,synthetic=false,txnID=true-16      16.5ns ±30%     2.8ns ±28%  -82.77%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=false,synthetic=true,txnID=true-16      16.0ns ±28%     2.8ns ±25%  -82.73%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=true,synthetic=true,txnID=true-16       16.3ns ±36%     2.8ns ±36%  -82.64%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=false,synthetic=false,txnID=false-16    16.4ns ±25%     2.9ns ±31%  -82.59%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=false,synthetic=true,txnID=false-16     16.2ns ±33%     2.8ns ±29%  -82.52%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=false,synthetic=false,txnID=true-16     16.2ns ±31%     2.8ns ±29%  -82.43%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=true,synthetic=false,txnID=false-16     15.9ns ±25%     2.8ns ±28%  -82.20%  (p=0.008 n=5+5)
IntervalSklDecodeValue/logical=true,synthetic=true,txnID=false-16      16.4ns ±35%     3.1ns ±60%  -81.25%  (p=0.008 n=5+5)

name                                                                 old alloc/op   new alloc/op   delta
IntervalSklDecodeValue/logical=false,synthetic=false,txnID=false-16     0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=false,synthetic=false,txnID=true-16      0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=false,synthetic=true,txnID=false-16      0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=false,synthetic=true,txnID=true-16       0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=false,txnID=false-16      0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=false,txnID=true-16       0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=true,txnID=false-16       0.00B          0.00B          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=true,txnID=true-16        0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=false,txnID=false-16     0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=false,txnID=true-16      0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=true,txnID=false-16      0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=true,txnID=true-16       0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=false,txnID=false-16      0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=false,txnID=true-16       0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=true,txnID=false-16       0.00B          0.00B          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=true,txnID=true-16        0.00B          0.00B          ~     (all equal)

name                                                                 old allocs/op  new allocs/op  delta
IntervalSklDecodeValue/logical=false,synthetic=false,txnID=false-16      0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=false,synthetic=false,txnID=true-16       0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=false,synthetic=true,txnID=false-16       0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=false,synthetic=true,txnID=true-16        0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=false,txnID=false-16       0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=false,txnID=true-16        0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=true,txnID=false-16        0.00           0.00          ~     (all equal)
IntervalSklDecodeValue/logical=true,synthetic=true,txnID=true-16         0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=false,txnID=false-16      0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=false,txnID=true-16       0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=true,txnID=false-16       0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=false,synthetic=true,txnID=true-16        0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=false,txnID=false-16       0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=false,txnID=true-16        0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=true,txnID=false-16        0.00           0.00          ~     (all equal)
IntervalSklEncodeValue/logical=true,synthetic=true,txnID=true-16         0.00           0.00          ~     (all equal)
```

We see these effects in the rest of the timestamp cache benchmarks. In the
benchmarks with the most overlaps (most calls to decodeValue per op) we see
up to a **22%** speedup. Meanwhile, over the lifetime of those benchmarks,
we see up to a **21%** increase in total allocated bytes (amortized across
all ops).

```
name                                old time/op    new time/op    delta
IntervalSklAdd/size_100000000-16      41.4µs ± 2%    32.3µs ± 7%  -22.12%  (p=0.000 n=10+9)
IntervalSklAdd/size_10000000-16       16.2µs ± 1%    12.7µs ± 4%  -21.57%  (p=0.000 n=9+10)
IntervalSklAdd/size_1000000-16        7.33µs ± 1%    6.18µs ± 4%  -15.62%  (p=0.000 n=9+10)
TimestampCacheInsertion-16            1.96µs ± 5%    1.72µs ± 6%  -12.06%  (p=0.000 n=10+10)
IntervalSklAdd/size_100000-16         4.63µs ± 2%    4.18µs ± 1%   -9.92%  (p=0.000 n=10+8)
IntervalSklAdd/size_1000-16           3.32µs ± 3%    3.16µs ± 3%   -4.97%  (p=0.000 n=10+10)
IntervalSklAdd/size_100-16            3.21µs ± 2%    3.06µs ± 4%   -4.84%  (p=0.000 n=10+10)
IntervalSklAdd/size_10-16             3.19µs ± 3%    3.05µs ± 3%   -4.25%  (p=0.000 n=10+10)
IntervalSklAdd/size_10000-16          3.52µs ± 3%    3.43µs ± 3%   -2.47%  (p=0.005 n=10+10)
IntervalSklAdd/size_1-16              1.70µs ± 1%    1.67µs ± 3%   -1.57%  (p=0.016 n=9+10)
IntervalSklAddAndLookup/frac_0-16      592ns ± 6%     509ns ±33%     ~     (p=0.469 n=10+10)
IntervalSklAddAndLookup/frac_1-16      592ns ± 8%     509ns ±32%     ~     (p=0.424 n=10+10)
IntervalSklAddAndLookup/frac_2-16      590ns ± 5%     510ns ±33%     ~     (p=0.481 n=10+10)
IntervalSklAddAndLookup/frac_3-16      585ns ± 5%     514ns ±32%     ~     (p=0.484 n=9+10)
IntervalSklAddAndLookup/frac_4-16      592ns ± 4%     511ns ±34%     ~     (p=0.483 n=9+10)
IntervalSklAddAndLookup/frac_5-16      591ns ± 8%     504ns ±35%     ~     (p=0.165 n=10+10)
IntervalSklAddAndLookup/frac_6-16      586ns ± 6%     508ns ±36%     ~     (p=0.210 n=10+10)
IntervalSklAddAndLookup/frac_7-16      588ns ± 6%     516ns ±46%     ~     (p=0.344 n=9+10)
IntervalSklAddAndLookup/frac_8-16      593ns ± 5%     521ns ±54%     ~     (p=0.240 n=10+10)
IntervalSklAddAndLookup/frac_9-16      594ns ± 5%     546ns ±54%     ~     (p=0.739 n=10+10)
IntervalSklAddAndLookup/frac_10-16     595ns ± 4%     525ns ±64%     ~     (p=0.138 n=10+10)

name                                old alloc/op   new alloc/op   delta
IntervalSklAdd/size_1-16               99.0B ± 0%     99.0B ± 0%     ~     (all equal)
IntervalSklAdd/size_10-16               201B ± 5%      217B ±17%     ~     (p=0.986 n=10+10)
IntervalSklAdd/size_1000-16             243B ±26%      220B ±15%     ~     (p=0.913 n=10+8)
IntervalSklAdd/size_10000-16            325B ±19%      352B ±24%     ~     (p=0.159 n=10+10)
IntervalSklAdd/size_100000-16           715B ±26%      737B ±23%     ~     (p=0.670 n=10+10)
IntervalSklAdd/size_1000000-16        1.92kB ±12%    1.93kB ±20%     ~     (p=0.781 n=10+10)
IntervalSklAddAndLookup/frac_0-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_1-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_2-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_3-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_4-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_5-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_6-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_7-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_8-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_9-16      0.00B          0.00B          ~     (all equal)
IntervalSklAddAndLookup/frac_10-16     0.00B          0.00B          ~     (all equal)
IntervalSklAdd/size_100000000-16      16.3kB ± 4%    17.3kB ± 2%   +5.85%  (p=0.000 n=9+9)
IntervalSklAdd/size_10000000-16       5.47kB ± 8%    5.83kB ±10%   +6.53%  (p=0.010 n=9+10)
TimestampCacheInsertion-16              631B ± 2%      677B ± 3%   +7.21%  (p=0.000 n=10+10)
IntervalSklAdd/size_100-16              194B ± 6%      236B ±28%  +21.71%  (p=0.012 n=10+10)

name                                old allocs/op  new allocs/op  delta
TimestampCacheInsertion-16              8.00 ± 0%      8.00 ± 0%     ~     (all equal)
IntervalSklAdd/size_1-16                0.00           0.00          ~     (all equal)
IntervalSklAdd/size_10-16               0.00           0.00          ~     (all equal)
IntervalSklAdd/size_100-16              0.00           0.00          ~     (all equal)
IntervalSklAdd/size_1000-16             0.00           0.00          ~     (all equal)
IntervalSklAdd/size_10000-16            0.00           0.00          ~     (all equal)
IntervalSklAdd/size_100000-16           0.00           0.00          ~     (all equal)
IntervalSklAdd/size_1000000-16          0.00           0.00          ~     (all equal)
IntervalSklAdd/size_10000000-16         0.00           0.00          ~     (all equal)
IntervalSklAdd/size_100000000-16        0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_0-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_1-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_2-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_3-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_4-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_5-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_6-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_7-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_8-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_9-16       0.00           0.00          ~     (all equal)
IntervalSklAddAndLookup/frac_10-16      0.00           0.00          ~     (all equal)
```
  • Loading branch information
nvanbenschoten committed Jan 5, 2021
1 parent cb1bb98 commit 1db343c
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 25 deletions.
36 changes: 11 additions & 25 deletions pkg/kv/kvserver/tscache/interval_skl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@ import (
"bytes"
"container/list"
"context"
"encoding/binary"
"fmt"
"sync/atomic"
"time"
"unsafe"

"github.com/andy-kimball/arenaskl"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/hlc"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/syncutil"
"github.com/cockroachdb/cockroach/pkg/util/uuid"
"github.com/cockroachdb/errors"
)

Expand Down Expand Up @@ -89,9 +88,7 @@ const (
)

const (
encodedTsSize = 8 + 4 + 1 // walltime + logical + flags
encodedTxnIDSize = uuid.Size
encodedValSize = encodedTsSize + encodedTxnIDSize
encodedValSize = int(unsafe.Sizeof(cacheValue{}))

// initialSklPageSize is the initial size of each page in the sklImpl's
// intervalSkl. The pages start small to limit the memory footprint of
Expand Down Expand Up @@ -1211,30 +1208,19 @@ func encodeValueSet(b []byte, keyVal, gapVal cacheValue) (ret []byte, meta uint1
}

func decodeValue(b []byte) (ret []byte, val cacheValue) {
val.ts.WallTime = int64(binary.BigEndian.Uint64(b))
val.ts.Logical = int32(binary.BigEndian.Uint32(b[8:]))
val.ts.Synthetic = b[12] != 0
var err error
if val.txnID, err = uuid.FromBytes(b[encodedTsSize:encodedValSize]); err != nil {
panic(err)
}
// Copy and interpret the byte slice as a cacheValue.
valPtr := (*[encodedValSize]byte)(unsafe.Pointer(&val))
copy(valPtr[:], b)
ret = b[encodedValSize:]
return
return ret, val
}

func encodeValue(b []byte, val cacheValue) []byte {
l := len(b)
b = b[:l+encodedValSize]
binary.BigEndian.PutUint64(b[l:], uint64(val.ts.WallTime))
binary.BigEndian.PutUint32(b[l+8:], uint32(val.ts.Logical))
syn := byte(0)
if val.ts.Synthetic {
syn = 1
}
b[l+12] = syn
if _, err := val.txnID.MarshalTo(b[l+encodedTsSize:]); err != nil {
panic(err)
}
// Interpret the cacheValue as a byte slice and copy.
prev := len(b)
b = b[:prev+encodedValSize]
valPtr := (*[encodedValSize]byte)(unsafe.Pointer(&val))
copy(b[prev:], valPtr[:])
return b
}

Expand Down
9 changes: 9 additions & 0 deletions pkg/kv/kvserver/tscache/interval_skl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"sync"
"testing"
"time"
"unsafe"

"github.com/andy-kimball/arenaskl"
"github.com/cockroachdb/cockroach/pkg/testutils"
Expand Down Expand Up @@ -1315,6 +1316,14 @@ func TestArenaReuse(t *testing.T) {
require.Equal(t, expArenas, len(arenas))
}

// TestEncodedValSize tests that the encodedValSize does not change unexpectedly
// due to changes in the cacheValue struct size. If the struct size does change,
// if should be done so deliberately.
func TestEncodedValSize(t *testing.T) {
require.Equal(t, encodedValSize, int(unsafe.Sizeof(cacheValue{})), "encodedValSize should equal sizeof(cacheValue{})")
require.Equal(t, 32, encodedValSize, "encodedValSize should not change unexpectedly")
}

func BenchmarkIntervalSklAdd(b *testing.B) {
const max = 500000000 // max size of range
const txnID = "123"
Expand Down

0 comments on commit 1db343c

Please sign in to comment.