Use a slice instead of a persistent list for temporary accumulation of lists

Avoids creating O(n) garbage by using a slice with enough capacity to
hold the temporary head-portion of the list accumulated while walking
the list to insert element(s).
This commit is contained in:
Bryan Boreham
2016-07-11 11:11:03 +01:00
parent 2ea04aa159
commit 7d579122aa

View File

@@ -109,14 +109,14 @@ func last(t1, t2 time.Time) time.Time {
return t2
}
// revCons appends acc to the head of curr, where acc is in reverse order.
// acc must never be nil, curr can be.
func revCons(acc, curr ps.List) ps.List {
// concat returns a new list formed by adding each element of acc to curr
// acc or curr can be nil.
func concat(acc []interface{}, curr ps.List) ps.List {
if curr == nil {
return acc.Reverse()
curr = ps.NewList()
}
for !acc.IsNil() {
acc, curr = acc.Tail(), curr.Cons(acc.Head())
for i := len(acc) - 1; i >= 0; i-- {
curr = curr.Cons(acc[i])
}
return curr
}
@@ -128,29 +128,28 @@ func (m Metric) Add(t time.Time, v float64) Metric {
// your new element in the list. NB we want to dedupe entries with
// equal timestamps.
// This should be O(1) to insert a latest element, and O(n) in general.
curr, acc := m.Samples, ps.NewList()
curr, acc := m.Samples, make([]interface{}, 0, m.Len()+1)
for {
if curr == nil || curr.IsNil() {
acc = acc.Cons(Sample{t, v})
break
}
currSample := curr.Head().(Sample)
if currSample.Timestamp.Equal(t) {
acc, curr = acc.Cons(Sample{t, v}), curr.Tail()
curr = curr.Tail()
break
}
if currSample.Timestamp.Before(t) {
acc = acc.Cons(Sample{t, v})
break
}
acc, curr = acc.Cons(curr.Head()), curr.Tail()
acc, curr = append(acc, curr.Head()), curr.Tail()
}
acc = revCons(acc, curr)
acc = append(acc, Sample{t, v})
curr = concat(acc, curr)
return Metric{
Samples: acc,
Samples: curr,
Max: math.Max(m.Max, v),
Min: math.Min(m.Min, v),
First: first(m.First, t),
@@ -161,14 +160,15 @@ func (m Metric) Add(t time.Time, v float64) Metric {
// Merge combines the two Metrics and returns a new result.
func (m Metric) Merge(other Metric) Metric {
// Merge two lists of samples in O(n)
curr1, curr2, acc := m.Samples, other.Samples, ps.NewList()
curr1, curr2, acc := m.Samples, other.Samples, make([]interface{}, 0, m.Len()+other.Len())
var newSamples ps.List
for {
if curr1 == nil || curr1.IsNil() {
acc = revCons(acc, curr2)
newSamples = concat(acc, curr2)
break
} else if curr2 == nil || curr2.IsNil() {
acc = revCons(acc, curr1)
newSamples = concat(acc, curr1)
break
}
@@ -176,16 +176,16 @@ func (m Metric) Merge(other Metric) Metric {
s2 := curr2.Head().(Sample)
if s1.Timestamp.Equal(s2.Timestamp) {
curr1, curr2, acc = curr1.Tail(), curr2.Tail(), acc.Cons(s1)
curr1, curr2, acc = curr1.Tail(), curr2.Tail(), append(acc, s1)
} else if s1.Timestamp.After(s2.Timestamp) {
curr1, acc = curr1.Tail(), acc.Cons(s1)
curr1, acc = curr1.Tail(), append(acc, s1)
} else {
curr2, acc = curr2.Tail(), acc.Cons(s2)
curr2, acc = curr2.Tail(), append(acc, s2)
}
}
return Metric{
Samples: acc,
Samples: newSamples,
Max: math.Max(m.Max, other.Max),
Min: math.Min(m.Min, other.Min),
First: first(m.First, other.First),