Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,28 @@ Instruction pipeline is 3.3x faster and SIMD is 3.8x faster (due to [bounds chec
```
go test -run=^$ -bench=. -cpu=1

BenchmarkPureEuclideanF32 4072329 270.5 ns/op
BenchmarkNoAsmEuclideanF32 14938154 81.59 ns/op
BenchmarkSIMDEuclideanF32 16888236 71.15 ns/op
BenchmarkPureCosineF32 3987426 299.8 ns/op
BenchmarkNoAsmCosineF32 11738499 102.2 ns/op
BenchmarkPureEuclideanF32 4072329 270.50 ns/op
BenchmarkNoAsmEuclideanF32 14938154 81.59 ns/op
BenchmarkSIMDEuclideanF32 16888236 71.15 ns/op
BenchmarkContraMapEuclidean 16595740 71.25 ns/op
BenchmarkPureCosineF32 3987426 299.80 ns/op
BenchmarkNoAsmCosineF32 11738499 102.20 ns/op
```

## High-level abstraction

**ContraMap** turn morphisms around `f: B ⟼ A`.

```go
type Node struct {
ID int
Vector vector.F32
}

vector.ContraMap[vector.F32, Node]{
Surface: vector.Euclidean(),
ContraMap: func(n Node) []float32 { return n.Vector },
}
```


Expand All @@ -103,7 +120,8 @@ go test

Checklist:
1. No new bounds check introduced: `go build -gcflags="-d=ssa/check_bce"`
2. No performance degradations: `go test -run=^$ -bench=. -cpu=1`
2. Check inlining: `go build -gcflags "-m"`
3. No performance degradations: `go test -run=^$ -bench=. -cpu=1`


### commit message
Expand Down
87 changes: 87 additions & 0 deletions distance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// Copyright (C) 2024 Dmitry Kolesnikov
//
// This file may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.
// https://github.com/kshard/vector
//

package vector

import (
"github.com/kshard/vector/internal/noasm"
"github.com/kshard/vector/internal/pure"
"github.com/kshard/vector/internal/simd"
)

//
// Euclidean
//

const (
EUCLIDEAN_WITH_PURE = iota
EUCLIDEAN_WITH_NOASM
EUCLIDEAN_WITH_SIMD
)

// Squared Euclidean distance between two vectors
func Euclidean() interface{ Distance(F32, F32) float32 } {
switch euclideanConfig() {
case EUCLIDEAN_WITH_PURE:
return pure.Euclidean(0)
case EUCLIDEAN_WITH_NOASM:
return noasm.Euclidean(0)
case EUCLIDEAN_WITH_SIMD:
return simd.Euclidean{}
}

return nil
}

func euclideanConfig() int {
if simd.ENABLED_EUCLIDEAN {
return EUCLIDEAN_WITH_SIMD
}

if noasm.ENABLED_EUCLIDEAN {
return EUCLIDEAN_WITH_NOASM
}

return EUCLIDEAN_WITH_PURE
}

//
// Cosine
//

const (
COSINE_WITH_PURE = iota
COSINE_WITH_NOASM
COSINE_WITH_SIMD
)

// Cosine Distance
func Cosine() interface{ Distance(F32, F32) float32 } {
switch cosineConfig() {
case COSINE_WITH_PURE:
return pure.Cosine(0)
case COSINE_WITH_NOASM:
return noasm.Cosine(0)
// case COSINE_WITH_SIMD:
// return simd.Euclidean{}
}

return nil
}

func cosineConfig() int {
if simd.ENABLED_COSINE {
return COSINE_WITH_SIMD
}

if noasm.ENABLED_COSINE {
return COSINE_WITH_NOASM
}

return COSINE_WITH_PURE
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.22.0

require (
github.com/chewxy/math32 v1.10.1
github.com/fogfish/golem/pure v0.10.1
golang.org/x/sys v0.17.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/chewxy/math32 v1.10.1 h1:LFpeY0SLJXeaiej/eIp2L40VYfscTvKh/FSEZ68uMkU=
github.com/chewxy/math32 v1.10.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs=
github.com/fogfish/golem/pure v0.10.1 h1:0+cnvdaV9zF+0NN8SZMgR5bgFM6yNfBHU4rynYSDfmE=
github.com/fogfish/golem/pure v0.10.1/go.mod h1:kLPfgu5uKP0CrwVap7jejisRwV7vo1q8Eyqnc/Z0qyw=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
24 changes: 24 additions & 0 deletions info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Copyright (C) 2024 Dmitry Kolesnikov
//
// This file may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.
// https://github.com/kshard/vector
//

package vector

const (
CONFIG_EUCLIDEAN = "euclidean"
CONFIG_COSINE = "cosine"
)

// Info about config
func Info() map[string]int {
info := map[string]int{}

info[CONFIG_EUCLIDEAN] = euclideanConfig()
info[CONFIG_COSINE] = cosineConfig()

return info
}
93 changes: 19 additions & 74 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,89 +9,34 @@
package vector

import (
"github.com/kshard/vector/internal/noasm"
"github.com/kshard/vector/internal/pure"
"github.com/kshard/vector/internal/simd"
"github.com/fogfish/golem/pure"
)

// Vector of float32
type F32 = []float32

const (
EUCLIDEAN_WITH_PURE = iota
EUCLIDEAN_WITH_NOASM
EUCLIDEAN_WITH_SIMD
)

// Squared Euclidean distance between two vectors
func Euclidean() interface{ Distance(F32, F32) float32 } {
switch euclideanConfig() {
case EUCLIDEAN_WITH_PURE:
return pure.Euclidean(0)
case EUCLIDEAN_WITH_NOASM:
return noasm.Euclidean(0)
case EUCLIDEAN_WITH_SIMD:
return simd.Euclidean{}
}

return nil
}

func euclideanConfig() int {
if simd.ENABLED_EUCLIDEAN {
return EUCLIDEAN_WITH_SIMD
}

if noasm.ENABLED_EUCLIDEAN {
return EUCLIDEAN_WITH_NOASM
}

return EUCLIDEAN_WITH_PURE
}

const (
COSINE_WITH_PURE = iota
COSINE_WITH_NOASM
COSINE_WITH_SIMD
)

// Cosine Distance
func Cosine() interface{ Distance(F32, F32) float32 } {
switch cosineConfig() {
case COSINE_WITH_PURE:
return pure.Cosine(0)
case COSINE_WITH_NOASM:
return noasm.Cosine(0)
// case COSINE_WITH_SIMD:
// return simd.Euclidean{}
}

return nil
// Generic trait to estimate "distance" between two vectors
type Surface[Vector any] interface {
Distance(Vector, Vector) float32
}

func cosineConfig() int {
if simd.ENABLED_COSINE {
return COSINE_WITH_SIMD
}
// From is a combinator that lifts V ⟼ V ⟼ float32 function to
// an instance of Distance type trait
type From[Vector any] func(Vector, Vector) float32

if noasm.ENABLED_COSINE {
return COSINE_WITH_NOASM
}
func (f From[Vector]) Distance(a, b Vector) float32 { return f(a, b) }

return COSINE_WITH_PURE
// ContraMap is a combinator that build a new instance of type trait Surface[V] using
// existing instance of Distance[A] and f: b ⟼ a
type ContraMap[A, B any] struct {
Surface[A]
pure.ContraMap[A, B]
}

const (
CONFIG_EUCLIDEAN = "euclidean"
CONFIG_COSINE = "cosine"
)

// Info about config
func Info() map[string]int {
info := map[string]int{}

info[CONFIG_EUCLIDEAN] = euclideanConfig()
info[CONFIG_COSINE] = cosineConfig()

return info
// Distance implementation of contra variant functor
func (f ContraMap[A, B]) Distance(a, b B) float32 {
return f.Surface.Distance(
f.ContraMap(a),
f.ContraMap(b),
)
}
66 changes: 57 additions & 9 deletions vector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,31 @@ import (
"github.com/kshard/vector/internal/simd"
)

//
//
//

type Node struct {
ID int
Vector vector.F32
}

func ntov(n Node) []float32 { return n.Vector }

var (
n = 300
d float32
a vector.F32
b vector.F32
n = 300
d float32
a vector.F32
b vector.F32
n1 Node
n2 Node
)

func init() {
a = randF32()
b = randF32()

for i := 0; i < n; i++ {
a[i] = rand.Float32()
b[i] = rand.Float32()
}
n1 = Node{ID: 1, Vector: a}
n2 = Node{ID: 2, Vector: b}
}

func randF32() vector.F32 {
Expand All @@ -48,6 +58,10 @@ func equal(a, b float32) bool {
return -1e-4 < d && d < 1e-4
}

//
//
//

func TestNoAsmEuclideanF32(t *testing.T) {
euc := pure.Euclidean(0)
sut := noasm.Euclidean(0)
Expand Down Expand Up @@ -84,6 +98,25 @@ func TestSIMDEuclideanF32(t *testing.T) {
}
}

func TestContraMapEuclidean(t *testing.T) {
euc := pure.Euclidean(0)
sut := vector.ContraMap[vector.F32, Node]{
Surface: vector.Euclidean(),
ContraMap: func(n Node) []float32 { return n.Vector },
}

for i := 0; i < n*100; i++ {
a := randF32()
b := randF32()

g := euc.Distance(a, b)
d := sut.Distance(Node{Vector: a}, Node{Vector: b})
if !equal(d, g) {
t.Errorf("failed distance")
}
}
}

func TestNoAsmCosineF32(t *testing.T) {
euc := pure.Cosine(0)
sut := noasm.Cosine(0)
Expand All @@ -100,6 +133,10 @@ func TestNoAsmCosineF32(t *testing.T) {
}
}

//
// Benchmark
//

func BenchmarkPureEuclideanF32(t *testing.B) {
euc := pure.Euclidean(0)

Expand All @@ -124,6 +161,17 @@ func BenchmarkSIMDEuclideanF32(t *testing.B) {
}
}

func BenchmarkContraMapEuclidean(t *testing.B) {
euc := vector.ContraMap[vector.F32, Node]{
Surface: vector.Euclidean(),
ContraMap: ntov,
}

for i := t.N; i > 0; i-- {
d = euc.Distance(n1, n2)
}
}

func BenchmarkPureCosineF32(t *testing.B) {
cos := pure.Cosine(0)

Expand Down