Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use 0 for empty value #34

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bits.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func metaMatchH2(m *metadata, h h2) bitset {
}

func metaMatchEmpty(m *metadata) bitset {
return hasZeroByte(castUint64(m) ^ hiBits)
return hasZeroByte(castUint64(m))
}

func nextMatch(b *bitset) uint32 {
Expand Down
4 changes: 2 additions & 2 deletions bits_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ const (
type bitset uint16

func metaMatchH2(m *metadata, h h2) bitset {
b := simd.MatchMetadata((*[16]int8)(m), int8(h))
b := simd.MatchMetadata((*[groupSize]uint8)(m), uint8(h))
return bitset(b)
}

func metaMatchEmpty(m *metadata) bitset {
b := simd.MatchMetadata((*[16]int8)(m), empty)
b := simd.MatchEmpty((*[groupSize]uint8)(m))
return bitset(b)
}

Expand Down
32 changes: 17 additions & 15 deletions bits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,40 @@ import (
func TestMatchMetadata(t *testing.T) {
var meta metadata
for i := range meta {
meta[i] = int8(i)
meta[i] = uint8(i) + h2Offset
}
t.Run("metaMatchH2", func(t *testing.T) {
for _, x := range meta {
mask := metaMatchH2(&meta, h2(x))
for i, m := range meta {
mask := metaMatchH2(&meta, h2(m))
assert.NotZero(t, mask)
assert.Equal(t, uint32(x), nextMatch(&mask))
assert.Equal(t, uint32(i), nextMatch(&mask))
}
})
t.Run("metaMatchEmpty", func(t *testing.T) {
mask := metaMatchEmpty(&meta)
assert.Equal(t, mask, bitset(0))

for i := range meta {
meta[i] = empty
mask = metaMatchEmpty(&meta)
assert.NotZero(t, mask)
assert.Equal(t, uint32(i), nextMatch(&mask))
meta[i] = int8(i)
meta[i] = uint8(i) + h2Offset
}
})
t.Run("nextMatch", func(t *testing.T) {
const needle = uint8(42) + h2Offset

// test iterating multiple matches
meta = newEmptyMetadata()
meta = metadata{}
mask := metaMatchEmpty(&meta)
for i := range meta {
assert.Equal(t, uint32(i), nextMatch(&mask))
}
for i := 0; i < len(meta); i += 2 {
meta[i] = int8(42)
meta[i] = needle
}
mask = metaMatchH2(&meta, h2(42))
mask = metaMatchH2(&meta, h2(needle))
for i := 0; i < len(meta); i += 2 {
assert.Equal(t, uint32(i), nextMatch(&mask))
}
Expand All @@ -66,7 +69,7 @@ func TestMatchMetadata(t *testing.T) {
func BenchmarkMatchMetadata(b *testing.B) {
var meta metadata
for i := range meta {
meta[i] = int8(i)
meta[i] = uint8(i)
}
var mask bitset
for i := 0; i < b.N; i++ {
Expand All @@ -90,14 +93,13 @@ func nextPow2(x uint32) uint32 {
}

func TestConstants(t *testing.T) {
c1, c2 := empty, tombstone
assert.Equal(t, byte(0b1000_0000), byte(c1))
assert.Equal(t, byte(0b1000_0000), reinterpretCast(c1))
assert.Equal(t, byte(0b1111_1110), byte(c2))
assert.Equal(t, byte(0b1111_1110), reinterpretCast(c2))
assert.Equal(t, byte(0b0000_0000), empty)
assert.Equal(t, byte(0b0000_0000), reinterpretCast(empty))
assert.Equal(t, byte(0b0000_0001), tombstone)
assert.Equal(t, byte(0b0000_0001), reinterpretCast(tombstone))
}

func reinterpretCast(i int8) byte {
func reinterpretCast(i uint8) byte {
return *(*byte)(unsafe.Pointer(&i))
}

Expand Down
35 changes: 12 additions & 23 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type Map[K comparable, V any] struct {
// metadata is the h2 metadata array for a group.
// find operations first probe the controls bytes
// to filter candidates before matching keys
type metadata [groupSize]int8
type metadata [groupSize]uint8

// group is a group of 16 key-value pairs
type group[K comparable, V any] struct {
Expand All @@ -47,15 +47,16 @@ type group[K comparable, V any] struct {
const (
h1Mask uint64 = 0xffff_ffff_ffff_ff80
h2Mask uint64 = 0x0000_0000_0000_007f
empty int8 = -128 // 0b1000_0000
tombstone int8 = -2 // 0b1111_1110
h2Offset = 2
empty uint8 = 0b0000_0000
tombstone uint8 = 0b0000_0001
)

// h1 is a 57 bit hash prefix
type h1 uint64

// h2 is a 7 bit hash suffix
type h2 int8
type h2 uint8

// NewMap constructs a Map.
func NewMap[K comparable, V any](sz uint32) (m *Map[K, V]) {
Expand All @@ -66,9 +67,6 @@ func NewMap[K comparable, V any](sz uint32) (m *Map[K, V]) {
hash: maphash.NewHasher[K](),
limit: groups * maxAvgGroupLoad,
}
for i := range m.ctrl {
m.ctrl[i] = newEmptyMetadata()
}
return
}

Expand Down Expand Up @@ -150,7 +148,7 @@ func (m *Map[K, V]) Put(key K, value V) {
s := nextMatch(&matches)
m.groups[g].keys[s] = key
m.groups[g].values[s] = value
m.ctrl[g][s] = int8(lo)
m.ctrl[g][s] = uint8(lo)
m.resident++
return
}
Expand Down Expand Up @@ -236,10 +234,8 @@ func (m *Map[K, V]) Iter(cb func(k K, v V) (stop bool)) {

// Clear removes all elements from the Map.
func (m *Map[K, V]) Clear() {
for i, c := range m.ctrl {
for j := range c {
m.ctrl[i][j] = empty
}
for i := range m.ctrl {
m.ctrl[i] = metadata{}
}
var k K
var v V
Expand Down Expand Up @@ -302,9 +298,6 @@ func (m *Map[K, V]) rehash(n uint32) {
groups, ctrl := m.groups, m.ctrl
m.groups = make([]group[K, V], n)
m.ctrl = make([]metadata, n)
for i := range m.ctrl {
m.ctrl[i] = newEmptyMetadata()
}
m.hash = maphash.NewSeed(m.hash)
m.limit = n * maxAvgGroupLoad
m.resident, m.dead = 0, 0
Expand Down Expand Up @@ -333,15 +326,11 @@ func numGroups(n uint32) (groups uint32) {
return
}

func newEmptyMetadata() (meta metadata) {
for i := range meta {
meta[i] = empty
}
return
}

// splitHash extracts the h1 and h2 components from a 64 bit hash.
// h1 is the upper 57 bits, h2 is the lower 7 bits plus two.
// By adding 2, it ensures that h2 is never uint8(0) or uint8(1).
func splitHash(h uint64) (h1, h2) {
return h1((h & h1Mask) >> 7), h2(h & h2Mask)
return h1((h & h1Mask) >> 7), h2(h&h2Mask) + h2Offset
}

func probeStart(hi h1, groups int) uint32 {
Expand Down
28 changes: 28 additions & 0 deletions map_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@ func BenchmarkInt64Maps(b *testing.B) {
}
}

func BenchmarkNewMap(b *testing.B) {
sizes := []int{16, 128, 1024, 8192, 131072}
for _, n := range sizes {
b.Run("n="+strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
m := NewMap[int, int](uint32(n))
m.Count()
}
})
}
}

func BenchmarkMap_Put_Growing(b *testing.B) {
sizes := []int{16, 128, 1024, 8192, 131072}
for _, n := range sizes {
b.Run("n="+strconv.Itoa(n), func(b *testing.B) {
data := generateInt64Data(n)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m := NewMap[int64, int64](uint32(n))
for _, k := range data {
m.Put(k, k)
}
}
})
}
}

func TestMemoryFootprint(t *testing.T) {
t.Skip("unskip for memory footprint stats")
var samples []float64
Expand Down
46 changes: 0 additions & 46 deletions simd/asm.go

This file was deleted.

17 changes: 14 additions & 3 deletions simd/match.s
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT.
// Code generated by command: go run asm.go -pkg simd -out ../match.s -stubs ../match_amd64.go. DO NOT EDIT.

//go:build amd64

#include "textflag.h"

// func MatchMetadata(metadata *[16]int8, hash int8) uint16
// func MatchMetadata(metadata *[16]uint8, hash uint8) uint16
// Requires: SSE2, SSSE3
TEXT ·MatchMetadata(SB), NOSPLIT, $0-18
MOVQ metadata+0(FP), AX
MOVBLSX hash+8(FP), CX
MOVBLZX hash+8(FP), CX
MOVD CX, X0
PXOR X1, X1
PSHUFB X1, X0
Expand All @@ -17,3 +17,14 @@ TEXT ·MatchMetadata(SB), NOSPLIT, $0-18
PMOVMSKB X0, AX
MOVW AX, ret+16(FP)
RET

// func MatchEmpty(metadata *[16]uint8) uint16
// Requires: SSE2
TEXT ·MatchEmpty(SB), NOSPLIT, $0-10
MOVQ metadata+0(FP), AX
PXOR X0, X0
MOVOU (AX), X1
PCMPEQB X1, X0
PMOVMSKB X0, AX
MOVW AX, ret+8(FP)
RET
8 changes: 6 additions & 2 deletions simd/match_amd64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions simd/src/asm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//go:generate go run asm.go -pkg simd -out ../match.s -stubs ../match_amd64.go
// Copyright 2023 Dolthub, 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 main

import (
. "github.com/mmcloughlin/avo/build"
. "github.com/mmcloughlin/avo/operand"
)

func main() {
ConstraintExpr("amd64")

{
TEXT("MatchMetadata", NOSPLIT, "func(metadata *[16]uint8, hash uint8) uint16")
Doc("MatchMetadata performs a 16-way probe of |metadata| using SSE instructions",
"nb: |metadata| must be an aligned pointer")
m := Mem{Base: Load(Param("metadata"), GP64())}
h := Load(Param("hash"), GP32())
mask := GP32()

x0, x1, x2 := XMM(), XMM(), XMM()
MOVD(h, x0)
PXOR(x1, x1)
PSHUFB(x1, x0)
MOVOU(m, x2)
PCMPEQB(x2, x0)
PMOVMSKB(x0, mask)

Store(mask.As16(), ReturnIndex(0))
RET()
}

{
TEXT("MatchEmpty", NOSPLIT, "func(metadata *[16]uint8) uint16")
Doc("MatchMetadata performs a 16-way probe of zero byte using SSE instructions",
"nb: |metadata| must be an aligned pointer")
m := Mem{Base: Load(Param("metadata"), GP64())}
mask := GP32()

x0, x1 := XMM(), XMM()
PXOR(x0, x0)
MOVOU(m, x1)
PCMPEQB(x1, x0)
PMOVMSKB(x0, mask)

Store(mask.As16(), ReturnIndex(0))
RET()
}
Generate()
}
10 changes: 10 additions & 0 deletions simd/src/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/dolthub/swiss/simd/src

go 1.22.4

require github.com/mmcloughlin/avo v0.6.0

require (
golang.org/x/mod v0.14.0 // indirect
golang.org/x/tools v0.16.1 // indirect
)
Loading