Skip to content

Commit

Permalink
Merge pull request #3 from ef-ds/simplify-push-logic
Browse files Browse the repository at this point in the history
Simplified push method; updated first and max slice sizes
  • Loading branch information
christianrpetrin authored Jan 6, 2019
2 parents 2b8ceb7 + 75300c5 commit ee6e423
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 214 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 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)

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.
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.


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


## Performance
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.
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.

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

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


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

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.
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.

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

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


## Competition
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.
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.


## Releases
Expand Down
58 changes: 12 additions & 46 deletions stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,16 @@ package stack

const (
// firstSliceSize holds the size of the first slice.
firstSliceSize = 4

// sliceGrowthFactor determines by how much and how fast the first internal
// slice should grow. A growth factor of 4, firstSliceSize = 4 and
// maxInternalSliceSize = 256, the first slice will start with size 4,
// then 16 (4*4), then 64 (16*4), then 256 (64*4), then 1024 (256*4).
// The growth factor should be tweaked together with firstSliceSize and
// maxInternalSliceSize and for maximum efficiency.
// sliceGrowthFactor only applies to the very first slice creates. All other
// subsequent slices are created with fixed size of maxInternalSliceSize.
sliceGrowthFactor = 4
firstSliceSize = 8

// maxInternalSliceSize holds the maximum size of each internal slice.
maxInternalSliceSize = 1024
maxInternalSliceSize = 512
)

// Stack implements an unbounded, dynamically growing Last-In-First-Out (LIFO)
// stack data structure.
// The zero value for stack is an empty stack ready to use.
type Stack struct {
// Head points to the first node of the linked list.
head *node

// Tail points to the last node of the linked list.
// In an empty stack, head and tail points to the same node.
tail *node
Expand All @@ -63,9 +50,6 @@ type node struct {
// v holds the list of user added values in this node.
v []interface{}

// n points to the next node in the linked list.
n *node

// p points to the previous node in the linked list.
p *node
}
Expand Down Expand Up @@ -99,31 +83,14 @@ func (s *Stack) Back() (interface{}, bool) {
// Push adds value v to the the back of the stack.
// The complexity is O(1).
func (s *Stack) Push(v interface{}) {
switch {
case s.head == nil:
// No nodes present yet.
h := &node{v: make([]interface{}, 0, firstSliceSize)}
h.p = h
s.head = h
s.tail = h
case len(s.tail.v) < cap(s.tail.v):
// There's room in the tail slice.
case cap(s.tail.v) < maxInternalSliceSize:
// We're on the first slice and it hasn't grown large enough yet.
l := len(s.tail.v)
nv := make([]interface{}, l, l*sliceGrowthFactor)
copy(nv, s.tail.v)
s.tail.v = nv
case s.tail.n != nil:
// There's at least one unused slice between head and tail nodes.
n := s.tail.n
s.tail = n
default:
// No available nodes, so make one.
n := &node{v: make([]interface{}, 0, maxInternalSliceSize)}
n.p = s.tail
s.tail.n = n
s.tail = n
if s.tail == nil {
s.tail = &node{v: make([]interface{}, 0, firstSliceSize)}
s.tail.p = s.tail
} else if len(s.tail.v) >= maxInternalSliceSize {
s.tail = &node{
v: make([]interface{}, 0, maxInternalSliceSize),
p: s.tail,
}
}
s.len++
s.tail.v = append(s.tail.v, v)
Expand All @@ -137,16 +104,15 @@ func (s *Stack) Pop() (interface{}, bool) {
if s.len == 0 {
return nil, false
}

s.len--
tp := len(s.tail.v) - 1
vp := &s.tail.v[tp]
v := *vp
*vp = nil // Avoid memory leaks
s.tail.v = s.tail.v[:tp]
if tp <= 0 {
// Move to the previous slice as all elements
// in the current one were removed.
s.tail = s.tail.p
s.tail = s.tail.p // Move to the previous slice.
}
return v, true
}
Binary file modified testdata/stack.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ee6e423

Please sign in to comment.