Skip to content

Commit e38ce1c

Browse files
committed
support permanent storage
1 parent dc7f440 commit e38ce1c

File tree

5 files changed

+127
-13
lines changed

5 files changed

+127
-13
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ The latest version of the library is available at `main` branch of this reposito
3030
import "github.com/kshard/atom"
3131

3232
// Create new atoms table
33-
atoms := atom.New(atom.NewMemMap())
33+
atoms := atom.New(atom.NewEphemeralMap())
3434

3535
// Convert string to atom
3636
code := atoms.Atom("String interning")
@@ -39,6 +39,8 @@ code := atoms.Atom("String interning")
3939
atoms.String(code)
4040
```
4141

42+
Use `atom.NewPermanentMap()` to implement atoms backed by either local permanent or external storage.
43+
4244
## How To Contribute
4345

4446
The library is [MIT](LICENSE) licensed and accepts contributions via GitHub pull requests:

examples/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const n = 1000000
2020
func main() {
2121
var err error
2222

23-
atoms := atom.New(atom.NewMemMap())
23+
atoms := atom.New(atom.NewEphemeralMap())
2424
ids := make([]atom.Atom, n)
2525

2626
for i := 0; i < n; i++ {

hashmap.go

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,26 @@
88

99
package atom
1010

11-
import "sync"
11+
import (
12+
"encoding/binary"
13+
"sync"
14+
"unsafe"
15+
)
1216

13-
type hashmap struct {
17+
//------------------------------------------------------------------------------
18+
19+
type ephemeral struct {
1420
sync.RWMutex
1521
kv map[Atom]string
1622
}
1723

18-
func NewMemMap() HashMap {
19-
return &hashmap{
24+
func NewEphemeralMap() HashMap {
25+
return &ephemeral{
2026
kv: make(map[Atom]string),
2127
}
2228
}
2329

24-
func (m *hashmap) Get(key Atom) (string, error) {
30+
func (m *ephemeral) Get(key Atom) (string, error) {
2531
m.RLock()
2632
val, has := m.kv[key]
2733
m.RUnlock()
@@ -32,10 +38,54 @@ func (m *hashmap) Get(key Atom) (string, error) {
3238
return val, nil
3339
}
3440

35-
func (m *hashmap) Put(key Atom, val string) error {
41+
func (m *ephemeral) Put(key Atom, val string) error {
3642
m.Lock()
3743
m.kv[key] = val
3844
m.Unlock()
3945

4046
return nil
4147
}
48+
49+
//------------------------------------------------------------------------------
50+
51+
type permanent struct {
52+
store Store
53+
}
54+
55+
func NewPermanentMap(store Store) HashMap {
56+
return &permanent{store: store}
57+
}
58+
59+
func (m *permanent) Get(key Atom) (string, error) {
60+
var bkey [5]byte
61+
bkey[0] = ':'
62+
binary.LittleEndian.PutUint32(bkey[1:], key)
63+
64+
val, err := m.store.Get(bkey[:])
65+
if err != nil {
66+
return "", err
67+
}
68+
69+
// This is copied from runtime. It relies on the string
70+
// header being a prefix of the slice header!
71+
str := *(*string)(unsafe.Pointer(&val))
72+
73+
return str, nil
74+
}
75+
76+
func (m *permanent) Put(key Atom, val string) error {
77+
var bkey [5]byte
78+
bkey[0] = ':'
79+
binary.LittleEndian.PutUint32(bkey[1:], key)
80+
81+
// This is copied from runtime. It relies on the string
82+
// header being a prefix of the slice header!
83+
bval := *(*[]byte)(unsafe.Pointer(&val))
84+
85+
err := m.store.Put(bkey[:], bval)
86+
if err != nil {
87+
return err
88+
}
89+
90+
return nil
91+
}

symbol_test.go

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package atom_test
22

33
import (
4+
"bytes"
45
"strconv"
56
"testing"
67
"time"
@@ -14,8 +15,29 @@ const (
1415
sc = "interning"
1516
)
1617

17-
func TestPut(t *testing.T) {
18-
s := atom.New(atom.NewMemMap())
18+
func TestEphemeralPut(t *testing.T) {
19+
s := atom.New(atom.NewEphemeralMap())
20+
21+
for val, expected := range map[string]uint32{
22+
sa: 1247594388,
23+
sb: 3572195896,
24+
sc: 1304336027,
25+
} {
26+
sym, err := s.Atom(val)
27+
if err != nil {
28+
t.Errorf("failed to assign symbol: %s", err)
29+
}
30+
if sym != expected {
31+
t.Errorf("failed to assign symbol: %d, expected %d", sym, expected)
32+
}
33+
if val != s.String(sym) {
34+
t.Errorf("failed to lookup string")
35+
}
36+
}
37+
}
38+
39+
func TestPermanentPut(t *testing.T) {
40+
s := atom.New(atom.NewPermanentMap(&none{}))
1941

2042
for val, expected := range map[string]uint32{
2143
sa: 1247594388,
@@ -38,8 +60,21 @@ func TestPut(t *testing.T) {
3860
// ---------------------------------------------------------------
3961

4062
// go test -run=^$ -bench=. -cpu=1 -benchtime=10s -cpuprofile profile.out
41-
func BenchmarkPut(b *testing.B) {
42-
s := atom.New(atom.NewMemMap())
63+
func BenchmarkEphemeralPut(b *testing.B) {
64+
s := atom.New(atom.NewEphemeralMap())
65+
66+
b.ReportAllocs()
67+
b.ResetTimer()
68+
69+
t := time.Now().Nanosecond()
70+
71+
for n := 0; n < b.N; n++ {
72+
s.Atom("https://pkg.go.dev/hash/fnv@go1.20." + strconv.Itoa(t+n))
73+
}
74+
}
75+
76+
func BenchmarkPermanentPut(b *testing.B) {
77+
s := atom.New(atom.NewPermanentMap(&none{}))
4378

4479
b.ReportAllocs()
4580
b.ResetTimer()
@@ -55,7 +90,7 @@ func BenchmarkPut(b *testing.B) {
5590

5691
// go test -fuzz=FuzzSymbolOf
5792
func FuzzSymbolOf(f *testing.F) {
58-
s := atom.New(atom.NewMemMap())
93+
s := atom.New(atom.NewEphemeralMap())
5994

6095
f.Add("abc")
6196

@@ -66,3 +101,24 @@ func FuzzSymbolOf(f *testing.F) {
66101
}
67102
})
68103
}
104+
105+
// ---------------------------------------------------------------
106+
107+
type none struct {
108+
key []byte
109+
val []byte
110+
}
111+
112+
func (n *none) Get(key []byte) ([]byte, error) {
113+
if bytes.Equal(n.key, key) {
114+
return n.val, nil
115+
}
116+
117+
return nil, nil
118+
}
119+
120+
func (n *none) Put(key []byte, val []byte) error {
121+
n.key = key
122+
n.val = val
123+
return nil
124+
}

types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ type HashMap interface {
2121
Getter
2222
Putter
2323
}
24+
25+
// abstraction of permanent storage
26+
type Store interface {
27+
Get([]byte) ([]byte, error)
28+
Put([]byte, []byte) error
29+
}

0 commit comments

Comments
 (0)