/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package simdjson
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"runtime"
"sync"
"unsafe"
"github.com/klauspost/compress/s2"
"github.com/klauspost/compress/zstd"
)
const (
stringBits = 14
stringSize = 1 << stringBits
stringmask = stringSize - 1
serializedVersion = 3
)
// Serializer allows to serialize parsed json and read it back.
// A Serializer can be reused, but not used concurrently.
type Serializer struct {
// Compressed strings
sMsg []byte
// Uncompressed tags
tagsBuf []byte
// Values
valuesBuf []byte
valuesCompBuf []byte
tagsCompBuf []byte
compValues, compTags uint8
compStrings uint8
fasterComp bool
// Deduplicated strings
stringWr io.Writer
stringsTable [stringSize]uint32
stringBuf []byte
maxBlockSize uint64
}
// NewSerializer will create and initialize a Serializer.
func NewSerializer() *Serializer {
initSerializerOnce.Do(initSerializer)
var s Serializer
s.CompressMode(CompressDefault)
s.maxBlockSize = 1 << 31
return &s
}
type CompressMode uint8
const (
// CompressNone no compression whatsoever.
CompressNone CompressMode = iota
// CompressFast will apply light compression,
// but will not deduplicate strings which may affect deserialization speed.
CompressFast
// CompressDefault applies light compression and deduplicates strings.
CompressDefault
// CompressBest
CompressBest
)
func (s *Serializer) CompressMode(c CompressMode) {
switch c {
case CompressNone:
s.compValues = blockTypeUncompressed
s.compTags = blockTypeUncompressed
s.compStrings = blockTypeUncompressed
case CompressFast:
s.compValues = blockTypeS2
s.compTags = blockTypeS2
s.compStrings = blockTypeS2
s.fasterComp = true
case CompressDefault:
s.compValues = blockTypeS2
s.compTags = blockTypeZstd
s.compStrings = blockTypeS2
case CompressBest:
s.compValues = blockTypeZstd
s.compTags = blockTypeZstd
s.compStrings = blockTypeZstd
default:
panic("unknown compression mode")
}
}
func serializeNDStream(dst io.Writer, in <-chan Stream, reuse chan<- *ParsedJson, concurrency int, comp CompressMode) error {
if concurrency <= 0 {
concurrency = (runtime.GOMAXPROCS(0) + 1) / 2
}
var wg sync.WaitGroup
wg.Add(concurrency)
type workload struct {
pj *ParsedJson
dst chan []byte
}
var readCh = make(chan workload, concurrency)
var writeCh = make(chan chan []byte, concurrency)
dstPool := sync.Pool{
New: func() interface{} {
return make([]byte, 0, 64<<10)
},
}
for i := 0; i < concurrency; i++ {
go func() {
s := NewSerializer()
s.CompressMode(comp)
defer wg.Done()
for input := range readCh {
res := s.Serialize(dstPool.Get().([]byte)[:0], *input.pj)
input.dst <- res
select {
case reuse <- input.pj:
default:
}
}
}()
}
var writeErr error
var wwg sync.WaitGroup
wwg.Add(1)
go func() {
defer wwg.Done()
for block := range writeCh {
b := <-block
var n int
n, writeErr = dst.Write(b)
if n != len(b) {
writeErr = io.ErrShortWrite
}
}
}()
var readErr error
var rwg sync.WaitGroup
rwg.Add(1)
go func() {
defer rwg.Done()
defer close(readCh)
for block := range in {
if block.Error != nil {
readErr = block.Error
}
readCh <- workload{
pj: block.Value,
dst: make(chan []byte, 0),
}
}
}()
rwg.Wait()
if readErr != nil {
wg.Wait()
close(writeCh)
wwg.Wait()
return readErr
}
// Read done, wait for workers...
wg.Wait()
close(writeCh)
// Wait for writer...
wwg.Wait()
return writeErr
}
const (
tagFloatWithFlag = Tag('e')
)
// Serialize the data in pj and return the data.
// An optional destination can be provided.
func (s *Serializer) Serialize(dst []byte, pj ParsedJson) []byte {
// Blocks:
// - Compressed size of entire block following. Can be 0 if empty. (varuint)
// - Block type, byte:
// 0: uncompressed, rest is data.
// 1: S2 compressed stream.
// 2: Zstd block.
// - Compressed data.
//
// Serialized format:
// - Header: Version (byte)
// - Compressed size of remaining data (varuint). Excludes previous and size of this.
// - Tape size, uncompressed (varuint)
// - Strings size, uncompressed (varuint)
// - Strings Block: Compressed block. See above.
// - Message size, uncompressed (varuint)
// - Message Block: Compressed block. See above.
// - Uncompressed size of tags (varuint)
// - Tags Block: Compressed block. See above.
// - Uncompressed values size (varuint)
// - Values Block: Compressed block. See above.
//
// Reconstruction:
//
// Read next tag. Depending on the tag, read a number of values:
// Values:
// - Null, BoolTrue/BoolFalse: No value.
// - Nop, Skip distances must be reconstructed.
// - TagObjectStart, TagArrayStart, TagRoot: (Offset - Current offset). Write end tag for object and array.
// - TagObjectEnd, TagArrayEnd: No value stored, derived from start.
// - TagInteger, TagUint, TagFloat: 64 bits
// - TagString: offset, length stored.
// - tagFloatWithFlag (v2): Contains float parsing flag.
//
// If there are any values left as tag or value, it is considered invalid.
var wg sync.WaitGroup
// Reset lookup table.
// Offsets are offset by 1, so 0 indicates an unfilled entry.
for i := range s.stringsTable[:] {
s.stringsTable[i] = 0
}
if len(s.stringBuf) > 0 {
s.stringBuf = s.stringBuf[:0]
}
if len(s.sMsg) > 0 {
s.sMsg = s.sMsg[:0]
}
msgWr, msgDone := encBlock(s.compStrings, s.sMsg, s.fasterComp)
s.stringWr = msgWr
const tagBufSize = 64 << 10
const valBufSize = 64 << 10
valWr, valDone := encBlock(s.compValues, s.valuesCompBuf, s.fasterComp)
tagWr, tagDone := encBlock(s.compTags, s.tagsCompBuf, s.fasterComp)
// Pessimistically allocate for maximum possible size.
if cap(s.tagsBuf) <= tagBufSize {
s.tagsBuf = make([]byte, tagBufSize)
}
s.tagsBuf = s.tagsBuf[:tagBufSize]
// At most one value per 2 tape entries
if cap(s.valuesBuf) < valBufSize+4 {
s.valuesBuf = make([]byte, valBufSize+4)
}
s.valuesBuf = s.valuesBuf[:0]
off := 0
tagsOff := 0
var tmp [8]byte
rawValues := 0
rawTags := 0
for off < len(pj.Tape) {
if tagsOff >= tagBufSize {
rawTags += tagsOff
tagWr.Write(s.tagsBuf[:tagsOff])
tagsOff = 0
}
if len(s.valuesBuf) >= valBufSize {
rawValues += len(s.valuesBuf)
valWr.Write(s.valuesBuf)
s.valuesBuf = s.valuesBuf[:0]
}
entry := pj.Tape[off]
ntype := Tag(entry >> 56)
payload := entry & JSONVALUEMASK
switch ntype {
case TagNop:
// We recreate the skip count when we unmarshal
case TagString:
sb, err := pj.stringByteAt(payload, pj.Tape[off+1])
if err != nil {
panic(err)
}
offset := s.indexString(sb)
binary.LittleEndian.PutUint64(tmp[:], offset)
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
binary.LittleEndian.PutUint64(tmp[:], uint64(len(sb)))
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
off++
case TagUint:
binary.LittleEndian.PutUint64(tmp[:], pj.Tape[off+1])
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
off++
case TagInteger:
binary.LittleEndian.PutUint64(tmp[:], pj.Tape[off+1])
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
off++
case TagFloat:
if payload == 0 {
binary.LittleEndian.PutUint64(tmp[:], pj.Tape[off+1])
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
off++
} else {
ntype = tagFloatWithFlag
binary.LittleEndian.PutUint64(tmp[:], entry)
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
binary.LittleEndian.PutUint64(tmp[:], pj.Tape[off+1])
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
off++
}
case TagNull, TagBoolTrue, TagBoolFalse:
// No value.
case TagObjectStart, TagArrayStart, TagRoot:
// TagObjectStart TagArrayStart always points forward.
// TagRoot can point either direction so we rely on under/overflow.
binary.LittleEndian.PutUint64(tmp[:], payload-uint64(off))
s.valuesBuf = append(s.valuesBuf, tmp[:]...)
case TagObjectEnd, TagArrayEnd, TagEnd:
// Value can be deducted from start tag or no value.
default:
wg.Wait()
panic(fmt.Errorf("unknown tag: %d", int(ntype)))
}
s.tagsBuf[tagsOff] = uint8(ntype)
tagsOff++
off++
}
if tagsOff > 0 {
rawTags += tagsOff
tagWr.Write(s.tagsBuf[:tagsOff])
}
if len(s.valuesBuf) > 0 {
rawValues += len(s.valuesBuf)
valWr.Write(s.valuesBuf)
}
wg.Add(3)
go func() {
var err error
s.tagsCompBuf, err = tagDone()
if err != nil {
panic(err)
}
wg.Done()
}()
go func() {
var err error
s.valuesCompBuf, err = valDone()
if err != nil {
panic(err)
}
wg.Done()
}()
go func() {
var err error
s.sMsg, err = msgDone()
if err != nil {
panic(err)
}
wg.Done()
}()
// Wait for compressors
wg.Wait()
// Version
dst = append(dst, serializedVersion)
// Size of varints...
varInts := binary.PutUvarint(tmp[:], uint64(0)) +
binary.PutUvarint(tmp[:], uint64(len(s.sMsg))) +
binary.PutUvarint(tmp[:], uint64(rawTags)) +
binary.PutUvarint(tmp[:], uint64(len(s.tagsCompBuf))) +
binary.PutUvarint(tmp[:], uint64(rawValues)) +
binary.PutUvarint(tmp[:], uint64(len(s.valuesCompBuf))) +
binary.PutUvarint(tmp[:], uint64(len(s.stringBuf))) +
binary.PutUvarint(tmp[:], uint64(len(pj.Tape)))
n := binary.PutUvarint(tmp[:], uint64(1+len(s.sMsg)+len(s.tagsCompBuf)+len(s.valuesCompBuf)+varInts))
dst = append(dst, tmp[:n]...)
// Tape elements, uncompressed.
n = binary.PutUvarint(tmp[:], uint64(len(pj.Tape)))
dst = append(dst, tmp[:n]...)
// Strings uncompressed size
dst = append(dst, 0)
// Strings
dst = append(dst, 0)
// Messages uncompressed size
n = binary.PutUvarint(tmp[:], uint64(len(s.stringBuf)))
dst = append(dst, tmp[:n]...)
// Message
n = binary.PutUvarint(tmp[:], uint64(len(s.sMsg)))
dst = append(dst, tmp[:n]...)
dst = append(dst, s.sMsg...)
// Tags
n = binary.PutUvarint(tmp[:], uint64(rawTags))
dst = append(dst, tmp[:n]...)
n = binary.PutUvarint(tmp[:], uint64(len(s.tagsCompBuf)))
dst = append(dst, tmp[:n]...)
dst = append(dst, s.tagsCompBuf...)
// Values
n = binary.PutUvarint(tmp[:], uint64(rawValues))
dst = append(dst, tmp[:n]...)
n = binary.PutUvarint(tmp[:], uint64(len(s.valuesCompBuf)))
dst = append(dst, tmp[:n]...)
dst = append(dst, s.valuesCompBuf...)
if false {
fmt.Println("strings:", len(pj.Strings.B)+len(pj.Message), "->", len(s.sMsg), "tags:", rawTags, "->", len(s.tagsCompBuf), "values:", rawValues, "->", len(s.valuesCompBuf), "Total:", len(pj.Message)+len(pj.Strings.B)+len(pj.Tape)*8, "->", len(dst))
}
return dst
}
func (s *Serializer) splitBlocks(r io.Reader, out chan []byte) error {
br := bufio.NewReader(r)
defer close(out)
for {
if v, err := br.ReadByte(); err != nil {
return err
} else if v != 1 {
return errors.New("unknown version")
}
// Comp size
c, err := binary.ReadUvarint(br)
if err != nil {
return err
}
if c > s.maxBlockSize {
return errors.New("compressed block too big")
}
block := make([]byte, c)
n, err := io.ReadFull(br, block)
if err != nil {
return err
}
if n > 0 {
out <- block
}
}
}
// Deserialize the content in src.
// Only basic sanity checks will be performed.
// Slight corruption will likely go through unnoticed.
// And optional destination can be provided.
func (s *Serializer) Deserialize(src []byte, dst *ParsedJson) (*ParsedJson, error) {
br := bytes.NewBuffer(src)
if v, err := br.ReadByte(); err != nil {
return dst, err
} else if v > serializedVersion {
// v3 reads v2.
// v2 reads v1.
return dst, errors.New("unknown version")
}
if dst == nil {
dst = &ParsedJson{}
}
// Comp size
if c, err := binary.ReadUvarint(br); err != nil {
return dst, err
} else {
if int(c) > br.Len() {
return dst, fmt.Errorf("stream too short, want %d, only have %d left", c, br.Len())
}
if int(c) > br.Len() {
fmt.Println("extra length:", int(c), br.Len())
}
}
// Tape size
if ts, err := binary.ReadUvarint(br); err != nil {
return dst, err
} else {
if uint64(cap(dst.Tape)) < ts {
dst.Tape = make([]uint64, ts)
}
dst.Tape = dst.Tape[:ts]
}
// String size
if ss, err := binary.ReadUvarint(br); err != nil {
return dst, err
} else {
if dst.Strings == nil || uint64(cap(dst.Strings.B)) < ss {
dst.Strings = &TStrings{B: make([]byte, ss)}
}
dst.Strings.B = dst.Strings.B[:ss]
}
// Decompress strings
var sWG sync.WaitGroup
var stringsErr, msgErr error
err := s.decBlock(br, dst.Strings.B, &sWG, &stringsErr)
if err != nil {
return dst, err
}
// Message size
if ss, err := binary.ReadUvarint(br); err != nil {
return dst, err
} else {
if uint64(cap(dst.Message)) < ss || dst.Message == nil {
dst.Message = make([]byte, ss)
}
dst.Message = dst.Message[:ss]
}
// Messages
err = s.decBlock(br, dst.Message, &sWG, &msgErr)
if err != nil {
return dst, err
}
defer sWG.Wait()
// Decompress tags
if tags, err := binary.ReadUvarint(br); err != nil {
return dst, err
} else {
if uint64(cap(s.tagsBuf)) < tags {
s.tagsBuf = make([]byte, tags)
}
s.tagsBuf = s.tagsBuf[:tags]
}
var wg sync.WaitGroup
var tagsErr error
err = s.decBlock(br, s.tagsBuf, &wg, &tagsErr)
if err != nil {
return dst, fmt.Errorf("decompressing tags: %w", err)
}
defer wg.Wait()
// Decompress values
if vals, err := binary.ReadUvarint(br); err != nil {
return dst, err
} else {
if uint64(cap(s.valuesBuf)) < vals {
s.valuesBuf = make([]byte, vals)
}
s.valuesBuf = s.valuesBuf[:vals]
}
var valsErr error
err = s.decBlock(br, s.valuesBuf, &wg, &valsErr)
if err != nil {
return dst, fmt.Errorf("decompressing values: %w", err)
}
// Wait until we have what we need for the tape.
wg.Wait()
switch {
case tagsErr != nil:
return dst, fmt.Errorf("decompressing tags: %w", tagsErr)
case valsErr != nil:
return dst, fmt.Errorf("decompressing values: %w", valsErr)
}
// Reconstruct tape:
var off int
values := s.valuesBuf
nSkips := 0
for _, t := range s.tagsBuf {
if off == len(dst.Tape) {
return dst, errors.New("tags extended beyond tape")
}
tag := Tag(t)
tagDst := uint64(t) << 56
if nSkips > 0 && tag != TagNop {
// We owe skips. Add with jumps
for i := 0; i < nSkips; i++ {
dst.Tape[off] = (uint64(TagNop) << JSONTAGOFFSET) | uint64(nSkips-i)
off++
}
nSkips = 0
}
switch tag {
case TagNop:
nSkips++
case TagString:
if len(values) < 16 {
return dst, fmt.Errorf("reading %v: no values left", tag)
}
sOffset := binary.LittleEndian.Uint64(values[:8])
sLen := binary.LittleEndian.Uint64(values[8:16])
values = values[16:]
dst.Tape[off] = tagDst | sOffset
dst.Tape[off+1] = sLen
off += 2
case TagFloat, TagInteger, TagUint:
if len(values) < 8 {
return dst, fmt.Errorf("reading %v: no values left", tag)
}
dst.Tape[off] = tagDst
dst.Tape[off+1] = binary.LittleEndian.Uint64(values[:8])
values = values[8:]
off += 2
case tagFloatWithFlag:
// Tape contains full value
if len(values) < 16 {
return dst, fmt.Errorf("reading %v: no values left", tag)
}
dst.Tape[off] = binary.LittleEndian.Uint64(values[:8])
dst.Tape[off+1] = binary.LittleEndian.Uint64(values[8:16])
values = values[16:]
off += 2
case TagNull, TagBoolTrue, TagBoolFalse, TagEnd:
dst.Tape[off] = tagDst
off++
case TagObjectStart, TagArrayStart:
if len(values) < 8 {
return dst, fmt.Errorf("reading %v: no values left", tag)
}
// Always forward
val := binary.LittleEndian.Uint64(values[:8])
values = values[8:]
val += uint64(off)
if val > uint64(len(dst.Tape)) {
return dst, fmt.Errorf("%v extends beyond tape (%d). offset:%d", tag, len(dst.Tape), val)
}
dst.Tape[off] = tagDst | val
// Write closing...
dst.Tape[val-1] = uint64(tagOpenToClose[tag])<<56 | uint64(off)
off++
case TagRoot:
if len(values) < 8 {
return dst, fmt.Errorf("reading %v: no values left", tag)
}
// Always forward
val := binary.LittleEndian.Uint64(values[:8])
values = values[8:]
val += uint64(off)
if val > uint64(len(dst.Tape)) {
return dst, fmt.Errorf("%v extends beyond tape (%d). offset:%d", tag, len(dst.Tape), val)
}
dst.Tape[off] = tagDst | val
off++
case TagObjectEnd, TagArrayEnd:
// This should already have been written.
if dst.Tape[off]&JSONTAGMASK != tagDst {
return dst, fmt.Errorf("reading %v, offset:%d, start tag did not match %x != %x", tag, off, dst.Tape[off]>>56, uint8(tagDst))
}
off++
default:
return nil, fmt.Errorf("unknown tag: %v", tag)
}
}
if nSkips > 0 {
// We owe skips. Add with jumps
for i := 0; i < nSkips; i++ {
dst.Tape[off] = (uint64(TagNop) << JSONTAGOFFSET) | uint64(nSkips-i)
off++
}
nSkips = 0
}
sWG.Wait()
if off != len(dst.Tape) {
return dst, fmt.Errorf("tags did not fill tape, want %d, got %d", len(dst.Tape), off)
}
if len(values) > 0 {
return dst, fmt.Errorf("values did not fill tape, want %d, got %d", len(dst.Tape), off)
}
if stringsErr != nil {
return dst, fmt.Errorf("reading strings: %w", stringsErr)
}
return dst, nil
}
func (s *Serializer) decBlock(br *bytes.Buffer, dst []byte, wg *sync.WaitGroup, dstErr *error) error {
size, err := binary.ReadUvarint(br)
if err != nil {
return err
}
if size > uint64(br.Len()) {
return fmt.Errorf("block size (%d) extends beyond input %d", size, br.Len())
}
if size == 0 && len(dst) == 0 {
// Nothing, no compress type
return nil
}
if size < 1 {
return fmt.Errorf("block size (%d) too small %d", size, br.Len())
}
typ, err := br.ReadByte()
if err != nil {
return err
}
size--
compressed := br.Next(int(size))
if len(compressed) != int(size) {
return errors.New("short block section")
}
switch typ {
case blockTypeUncompressed:
// uncompressed
if len(compressed) != len(dst) {
return fmt.Errorf("short uncompressed block: in (%d) != out (%d)", len(compressed), len(dst))
}
copy(dst, compressed)
case blockTypeS2:
wg.Add(1)
go func() {
defer wg.Done()
buf := bytes.NewBuffer(compressed)
dec := s2Readers.Get().(*s2.Reader)
dec.Reset(buf)
_, err := io.ReadFull(dec, dst)
dec.Reset(nil)
s2Readers.Put(dec)
*dstErr = err
}()
case blockTypeZstd:
wg.Add(1)
go func() {
defer wg.Done()
want := len(dst)
dst, err = zDec.DecodeAll(compressed, dst[:0])
if err == nil && want != len(dst) {
err = errors.New("zstd decompressed size mismatch")
}
*dstErr = err
}()
default:
return fmt.Errorf("unknown compression type: %d", typ)
}
return nil
}
const (
blockTypeUncompressed byte = 0
blockTypeS2 byte = 1
blockTypeZstd byte = 2
)
var zDec *zstd.Decoder
var zEncFast = sync.Pool{New: func() interface{} {
e, _ := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedDefault), zstd.WithEncoderCRC(false))
return e
}}
var s2FastWriters = sync.Pool{New: func() interface{} {
return s2.NewWriter(nil)
}}
var s2Writers = sync.Pool{New: func() interface{} {
return s2.NewWriter(nil, s2.WriterBetterCompression())
}}
var s2Readers = sync.Pool{New: func() interface{} {
return s2.NewReader(nil)
}}
var initSerializerOnce sync.Once
func initSerializer() {
zDec, _ = zstd.NewReader(nil)
}
type encodedResult func() ([]byte, error)
// encBlock will encode a block of data.
func encBlock(mode byte, buf []byte, fast bool) (io.Writer, encodedResult) {
dst := bytes.NewBuffer(buf[:0])
dst.WriteByte(mode)
switch mode {
case blockTypeUncompressed:
return dst, func() ([]byte, error) {
return dst.Bytes(), nil
}
case blockTypeS2:
var enc *s2.Writer
var put *sync.Pool
if fast {
enc = s2FastWriters.Get().(*s2.Writer)
put = &s2FastWriters
} else {
enc = s2Writers.Get().(*s2.Writer)
put = &s2Writers
}
enc.Reset(dst)
return enc, func() (i []byte, err error) {
err = enc.Close()
if err != nil {
return nil, err
}
enc.Reset(nil)
put.Put(enc)
return dst.Bytes(), nil
}
case blockTypeZstd:
enc := zEncFast.Get().(*zstd.Encoder)
enc.Reset(dst)
return enc, func() (i []byte, err error) {
err = enc.Close()
if err != nil {
return nil, err
}
enc.Reset(nil)
zEncFast.Put(enc)
return dst.Bytes(), nil
}
}
panic("unknown compression mode")
}
// indexString will deduplicate strings and populate
func (s *Serializer) indexString(sb []byte) (offset uint64) {
// Only possible on 64 bit platforms, so it will never trigger on 32 bit platforms.
if uint32(len(sb)) >= math.MaxUint32 {
panic("string too long")
}
h := memHash(sb) & stringmask
off := int(s.stringsTable[h]) - 1
end := off + len(sb)
if off >= 0 && end <= len(s.stringBuf) {
found := s.stringBuf[off:end]
if bytes.Equal(found, sb) {
return uint64(off)
}
// It didn't match :(
}
off = len(s.stringBuf)
s.stringBuf = append(s.stringBuf, sb...)
s.stringsTable[h] = uint32(off + 1)
s.stringWr.Write(sb)
return uint64(off)
}
//go:noescape
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr
// memHash is the hash function used by go map, it utilizes available hardware instructions (behaves
// as aeshash if aes instruction is available).
// NOTE: The hash seed changes for every process. So, this cannot be used as a persistent hash.
func memHash(data []byte) uint64 {
ss := (*stringStruct)(unsafe.Pointer(&data))
return uint64(memhash(ss.str, 0, uintptr(ss.len)))
}
type stringStruct struct {
str unsafe.Pointer
len int
}