Skip to content

Commit a4dc379

Browse files
committed
property tree instead of builtin map
benchmark old ns/op new ns/op delta BenchmarkAllocProperty/props0 83.4 83.0 -0.48% BenchmarkAllocProperty/props1 254 172 -32.28% BenchmarkAllocProperty/props2 492 292 -40.65% BenchmarkAllocProperty/props3 804 443 -44.90% BenchmarkAllocProperty/props4 1112 624 -43.88% BenchmarkAllocProperty/props5 1503 828 -44.91% BenchmarkAllocProperty/props6 1866 1062 -43.09% BenchmarkAllocProperty/props7 2322 1375 -40.78% BenchmarkAllocProperty/props8 2733 1591 -41.79% benchmark old allocs new allocs delta BenchmarkAllocProperty/props0 1 1 +0.00% BenchmarkAllocProperty/props1 4 3 -25.00% BenchmarkAllocProperty/props2 7 6 -14.29% BenchmarkAllocProperty/props3 10 10 +0.00% BenchmarkAllocProperty/props4 13 14 +7.69% BenchmarkAllocProperty/props5 16 19 +18.75% BenchmarkAllocProperty/props6 19 25 +31.58% BenchmarkAllocProperty/props7 22 32 +45.45% BenchmarkAllocProperty/props8 25 37 +48.00% benchmark old bytes new bytes delta BenchmarkAllocProperty/props0 96 96 +0.00% BenchmarkAllocProperty/props1 448 240 -46.43% BenchmarkAllocProperty/props2 800 432 -46.00% BenchmarkAllocProperty/props3 1152 672 -41.67% BenchmarkAllocProperty/props4 1504 912 -39.36% BenchmarkAllocProperty/props5 1856 1200 -35.34% BenchmarkAllocProperty/props6 2208 1536 -30.43% BenchmarkAllocProperty/props7 2560 1920 -25.00% BenchmarkAllocProperty/props8 2912 2208 -24.18%
1 parent 449f457 commit a4dc379

File tree

6 files changed

+80
-28
lines changed

6 files changed

+80
-28
lines changed

error.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type Error struct {
1616
underlying []error
1717
stackTrace *stackTrace
1818
transparent bool
19-
properties map[int64]interface{}
19+
properties *propertyMap
2020
}
2121

2222
var _ fmt.Formatter = (*Error)(nil)
@@ -27,17 +27,7 @@ var _ fmt.Formatter = (*Error)(nil)
2727
// Dynamic properties is a brittle mechanism and should therefore be used with care and in a simple and robust manner.
2828
func (e *Error) WithProperty(key Property, value interface{}) *Error {
2929
errorCopy := *e
30-
31-
if errorCopy.properties == nil {
32-
errorCopy.properties = make(map[int64]interface{}, 1)
33-
} else {
34-
errorCopy.properties = make(map[int64]interface{}, len(e.properties)+1)
35-
for k, v := range e.properties {
36-
errorCopy.properties[k] = v
37-
}
38-
}
39-
40-
errorCopy.properties[key.id] = value
30+
errorCopy.properties = errorCopy.properties.with(key, value)
4131
return &errorCopy
4232
}
4333

@@ -69,7 +59,7 @@ func (e *Error) WithUnderlyingErrors(errs ...error) *Error {
6959
func (e *Error) Property(key Property) (interface{}, bool) {
7060
cause := e
7161
for cause != nil {
72-
value, ok := cause.properties[key.id]
62+
value, ok := cause.properties.get(key)
7363
if ok {
7464
return value, true
7565
}

id.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package errorx
22

3-
import "sync/atomic"
3+
import (
4+
"sync"
5+
)
46

5-
var internalID int64
7+
var internalID uint64 = 0x123456789abcdef0
8+
var idMtx sync.Mutex
69

710
// nextInternalID creates next unique id for errorx entities.
811
// All equality comparison should take id into account, lest there be some false positive matches.
9-
func nextInternalID() int64 {
10-
return atomic.AddInt64(&internalID, 1)
12+
func nextInternalID() uint64 {
13+
idMtx.Lock()
14+
// xorshift
15+
x := internalID
16+
x ^= x << 13
17+
x ^= x >> 7
18+
x ^= x << 17
19+
id := x + internalID
20+
internalID = x
21+
idMtx.Unlock()
22+
return id
1123
}

namespace.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,15 @@ import "fmt"
1111
//
1212
type Namespace struct {
1313
parent *Namespace
14-
id int64
14+
id uint64
1515
name string
1616
traits []Trait
1717
modifiers modifiers
1818
}
1919

2020
// NamespaceKey is a comparable descriptor of a Namespace.
2121
type NamespaceKey struct {
22-
id int64
23-
name string
22+
id uint64
2423
}
2524

2625
// NewNamespace defines a namespace with a name and, optionally, a number of inheritable traits.
@@ -51,8 +50,7 @@ func (n Namespace) NewType(typeName string, traits ...Trait) *Type {
5150
// Key returns a comparison key for namespace.
5251
func (n Namespace) Key() NamespaceKey {
5352
return NamespaceKey{
54-
id: n.id,
55-
name: n.name,
53+
id: n.id,
5654
}
5755
}
5856

property.go

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import (
88
// Property value belongs to an error instance only, never inherited from a type.
99
// Property visibility is hindered by Wrap, preserved by Decorate.
1010
type Property struct {
11-
id int64
11+
*property
12+
}
13+
14+
type property struct {
15+
id uint64
1216
label string
1317
}
1418

@@ -70,8 +74,56 @@ var (
7074
)
7175

7276
func newProperty(label string) Property {
73-
return Property{
74-
id: nextInternalID(),
75-
label: label,
77+
p := Property{
78+
&property{
79+
id: nextInternalID(),
80+
label: label,
81+
},
82+
}
83+
return p
84+
}
85+
86+
// propertyMap represents map of properties.
87+
// Compared to builtin type, it uses less allocations and reallocations on copy.
88+
// It is simple binary search tree that relies on property id randomness for balancing.
89+
type propertyMap struct {
90+
p Property
91+
value interface{}
92+
left *propertyMap
93+
right *propertyMap
94+
}
95+
96+
// withProperty creates new map with property added or overwritten
97+
func (pm *propertyMap) with(p Property, value interface{}) *propertyMap {
98+
if pm == nil {
99+
return &propertyMap{p: p, value: value}
100+
} else if pm.p == p {
101+
return &propertyMap{
102+
p: p,
103+
value: value,
104+
left: pm.left,
105+
right: pm.right,
106+
}
107+
} else {
108+
copy := *pm
109+
if copy.p.id < p.id {
110+
copy.right = copy.right.with(p, value)
111+
} else {
112+
copy.left = copy.left.with(p, value)
113+
}
114+
return &copy
115+
}
116+
}
117+
118+
func (pm *propertyMap) get(p Property) (value interface{}, ok bool) {
119+
switch {
120+
case pm == nil:
121+
return nil, false
122+
case pm.p == p:
123+
return pm.value, true
124+
case pm.p.id < p.id:
125+
return pm.right.get(p)
126+
default:
127+
return pm.left.get(p)
76128
}
77129
}

trait.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package errorx
44
// All errors of a specific type possess exactly the same traits.
55
// Traits are both defined along with an error and inherited from a supertype and a namespace.
66
type Trait struct {
7-
id int64
7+
id uint64
88
label string
99
}
1010

type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
type Type struct {
1212
namespace Namespace
1313
parent *Type
14-
id int64
14+
id uint64
1515
fullName string
1616
traits map[Trait]bool
1717
modifiers modifiers

0 commit comments

Comments
 (0)