Skip to content

Commit ee6e423

Browse files
Merge pull request #3 from ef-ds/simplify-push-logic
Simplified push method; updated first and max slice sizes
2 parents 2b8ceb7 + 75300c5 commit ee6e423

4 files changed

Lines changed: 26 additions & 214 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# stack [![Build Status](https://travis-ci.com/ef-ds/stack.svg?branch=master)](https://travis-ci.com/ef-ds/stack) [![codecov](https://codecov.io/gh/ef-ds/stack/branch/master/graph/badge.svg)](https://codecov.io/gh/ef-ds/stack) [![Go Report Card](https://goreportcard.com/badge/github.com/ef-ds/stack)](https://goreportcard.com/report/github.com/ef-ds/stack) [![GoDoc](https://godoc.org/github.com/ef-ds/stack?status.svg)](https://godoc.org/github.com/ef-ds/stack)
22

3-
Package stack implements a very fast and efficient general purpose Last-In-First-Out (LIFO) stack data structure that is specifically optimized to perform when used by Microservices and serverless services running in production environments. Internally, stack stores the elements in a dynamic growing semi-circular doubly linked list of arrays.
3+
Package stack implements a very fast and efficient general purpose Last-In-First-Out (LIFO) stack data structure that is specifically optimized to perform when used by Microservices and serverless services running in production environments. Internally, stack stores the elements in a dynamic growing semi-circular inverted singly linked list of arrays.
44

55

66
## Install
@@ -64,7 +64,7 @@ See the [benchmark tests](https://github.com/ef-ds/stack-bench-tests/blob/master
6464

6565

6666
## Performance
67-
Stack has constant time (O(1)) on all its operations (Push/Pop/Back/Len). It's not amortized constant because stack never copies more than 256 (maxInternalSliceSize/sliceGrowthFactor) items and when it expands or grow, it never does so by more than 1024 (maxInternalSliceSize) items in a single operation.
67+
Stack has constant time (O(1)) on all its operations (Push/Pop/Back/Len). It's not amortized constant because stack never copies more than 256 (maxInternalSliceSize/2) items and when it expands or grow, it never does so by more than 512 (maxInternalSliceSize) items in a single operation.
6868

6969
Stack offers either the best or very competitive performance across all test sets, suites and ranges.
7070

@@ -74,9 +74,9 @@ See [performance](https://github.com/ef-ds/stack-bench-tests/blob/master/PERFORM
7474

7575

7676
## Design
77-
The Efficient Data Structures (ef-ds) stack employs a new, modern stack design: a semi-circular shaped, linked slices design.
77+
The Efficient Data Structures (ef-ds) stack employs a new, modern stack design: a dynamic growing semi-circular inverted singly linked list of slices.
7878

79-
That means the [LIFO stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) is a [doubly-linked list](https://en.wikipedia.org/wiki/Doubly_linked_list) where each node value is a fixed size [slice](https://tour.golang.org/moretypes/7). It is semi-circular in shape because the first node in the linked list points to itself, but the last one points to nil.
79+
That means the [LIFO stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) is a [singly-linked list](https://en.wikipedia.org/wiki/Singly_linked_list) where each node value is a fixed size [slice](https://tour.golang.org/moretypes/7). It is inverted singly linked list because each node points only to the previous one (instead of next) and it is semi-circular in shape because the first node in the linked list points to itself, but the last one points to nil.
8080

8181
![ns/op](testdata/stack.jpg?raw=true "stack Design")
8282

@@ -151,7 +151,7 @@ One sofware engineer can't change the world him/herself, but a whole bunch of us
151151

152152

153153
## Competition
154-
We're extremely interested in improving stack. Please let us know your suggestions for possible improvements and if you know of other high performance stacks not tested here, let us know and we're very glad to benchmark them.
154+
We're extremely interested in improving stack and we're on an endless quest for better efficiency and more performance. Please let us know your suggestions for possible improvements and if you know of other high performance stacks not tested here, let us know and we're very glad to benchmark them.
155155

156156

157157
## Releases

stack.go

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,16 @@ package stack
2626

2727
const (
2828
// firstSliceSize holds the size of the first slice.
29-
firstSliceSize = 4
30-
31-
// sliceGrowthFactor determines by how much and how fast the first internal
32-
// slice should grow. A growth factor of 4, firstSliceSize = 4 and
33-
// maxInternalSliceSize = 256, the first slice will start with size 4,
34-
// then 16 (4*4), then 64 (16*4), then 256 (64*4), then 1024 (256*4).
35-
// The growth factor should be tweaked together with firstSliceSize and
36-
// maxInternalSliceSize and for maximum efficiency.
37-
// sliceGrowthFactor only applies to the very first slice creates. All other
38-
// subsequent slices are created with fixed size of maxInternalSliceSize.
39-
sliceGrowthFactor = 4
29+
firstSliceSize = 8
4030

4131
// maxInternalSliceSize holds the maximum size of each internal slice.
42-
maxInternalSliceSize = 1024
32+
maxInternalSliceSize = 512
4333
)
4434

4535
// Stack implements an unbounded, dynamically growing Last-In-First-Out (LIFO)
4636
// stack data structure.
4737
// The zero value for stack is an empty stack ready to use.
4838
type Stack struct {
49-
// Head points to the first node of the linked list.
50-
head *node
51-
5239
// Tail points to the last node of the linked list.
5340
// In an empty stack, head and tail points to the same node.
5441
tail *node
@@ -63,9 +50,6 @@ type node struct {
6350
// v holds the list of user added values in this node.
6451
v []interface{}
6552

66-
// n points to the next node in the linked list.
67-
n *node
68-
6953
// p points to the previous node in the linked list.
7054
p *node
7155
}
@@ -99,31 +83,14 @@ func (s *Stack) Back() (interface{}, bool) {
9983
// Push adds value v to the the back of the stack.
10084
// The complexity is O(1).
10185
func (s *Stack) Push(v interface{}) {
102-
switch {
103-
case s.head == nil:
104-
// No nodes present yet.
105-
h := &node{v: make([]interface{}, 0, firstSliceSize)}
106-
h.p = h
107-
s.head = h
108-
s.tail = h
109-
case len(s.tail.v) < cap(s.tail.v):
110-
// There's room in the tail slice.
111-
case cap(s.tail.v) < maxInternalSliceSize:
112-
// We're on the first slice and it hasn't grown large enough yet.
113-
l := len(s.tail.v)
114-
nv := make([]interface{}, l, l*sliceGrowthFactor)
115-
copy(nv, s.tail.v)
116-
s.tail.v = nv
117-
case s.tail.n != nil:
118-
// There's at least one unused slice between head and tail nodes.
119-
n := s.tail.n
120-
s.tail = n
121-
default:
122-
// No available nodes, so make one.
123-
n := &node{v: make([]interface{}, 0, maxInternalSliceSize)}
124-
n.p = s.tail
125-
s.tail.n = n
126-
s.tail = n
86+
if s.tail == nil {
87+
s.tail = &node{v: make([]interface{}, 0, firstSliceSize)}
88+
s.tail.p = s.tail
89+
} else if len(s.tail.v) >= maxInternalSliceSize {
90+
s.tail = &node{
91+
v: make([]interface{}, 0, maxInternalSliceSize),
92+
p: s.tail,
93+
}
12794
}
12895
s.len++
12996
s.tail.v = append(s.tail.v, v)
@@ -137,16 +104,15 @@ func (s *Stack) Pop() (interface{}, bool) {
137104
if s.len == 0 {
138105
return nil, false
139106
}
107+
140108
s.len--
141109
tp := len(s.tail.v) - 1
142110
vp := &s.tail.v[tp]
143111
v := *vp
144112
*vp = nil // Avoid memory leaks
145113
s.tail.v = s.tail.v[:tp]
146114
if tp <= 0 {
147-
// Move to the previous slice as all elements
148-
// in the current one were removed.
149-
s.tail = s.tail.p
115+
s.tail = s.tail.p // Move to the previous slice.
150116
}
151117
return v, true
152118
}

testdata/stack.jpg

-830 Bytes
Loading

0 commit comments

Comments
 (0)