Skip to content

Commit

Permalink
Performance improvements (#30)
Browse files Browse the repository at this point in the history
Notes:
- avoid allocating an Element when iterating using (*Element).Next() and (*Element).Prev()
- avoid allocating an Element when calling (*OrderedMap).Front() and (*OrderedMap).Back()
- avoid computing the length of the doubly linked list since it is not strictly needed

Advantages:
- no need of (*Element).list field: save both memory and assignations
- avoid comparisons in (*Element).Next() and (*Element).Prev(): save time
- allow implicit initialization: style
- avoid comparisons in (*list).Front() and (*list).Back(): save time
- keep almost the same execution time for (*list).Remove(), (*list).PushFront() and (*list).PushBack()
  • Loading branch information
MagnaboscoL committed Sep 4, 2022
1 parent 32f08ce commit b46f20e
Show file tree
Hide file tree
Showing 5 changed files with 796 additions and 166 deletions.
36 changes: 0 additions & 36 deletions v2/element.go

This file was deleted.

67 changes: 0 additions & 67 deletions v2/element_test.go

This file was deleted.

95 changes: 95 additions & 0 deletions v2/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package orderedmap

// Element is an element of a null terminated (non circular) intrusive doubly linked list that contains the key of the correspondent element in the ordered map too.
type Element[K comparable, V any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element[K, V]

// The key that corresponds to this element in the ordered map.
Key K

// The value stored with this element.
Value V
}

// Next returns the next list element or nil.
func (e *Element[K, V]) Next() *Element[K, V] {
return e.next
}

// Prev returns the previous list element or nil.
func (e *Element[K, V]) Prev() *Element[K, V] {
return e.prev
}

// list represents a null terminated (non circular) intrusive doubly linked list.
// The list is immediately usable after instantiation without the need of a dedicated initialization.
type list[K comparable, V any] struct {
root Element[K, V] // list head and tail
}

func (l *list[K, V]) IsEmpty() bool {
return l.root.next == nil
}

// Front returns the first element of list l or nil if the list is empty.
func (l *list[K, V]) Front() *Element[K, V] {
return l.root.next
}

// Back returns the last element of list l or nil if the list is empty.
func (l *list[K, V]) Back() *Element[K, V] {
return l.root.prev
}

// Remove removes e from its list
func (l *list[K, V]) Remove(e *Element[K, V]) {
if e.prev == nil {
l.root.next = e.next
} else {
e.prev.next = e.next
}
if e.next == nil {
l.root.prev = e.prev
} else {
e.next.prev = e.prev
}
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
}

// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *list[K, V]) PushFront(key K, value V) *Element[K, V] {
e := &Element[K, V]{Key: key, Value: value}
if l.root.next == nil {
// It's the first element
l.root.next = e
l.root.prev = e
return e
}

e.next = l.root.next
l.root.next.prev = e
l.root.next = e
return e
}

// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *list[K, V]) PushBack(key K, value V) *Element[K, V] {
e := &Element[K, V]{Key: key, Value: value}
if l.root.prev == nil {
// It's the first element
l.root.next = e
l.root.prev = e
return e
}

e.prev = l.root.prev
l.root.prev.next = e
l.root.prev = e
return e
}
82 changes: 19 additions & 63 deletions v2/orderedmap.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
package orderedmap

import (
"container/list"
)

type orderedMapElement[K comparable, V any] struct {
key K
value V
}

type OrderedMap[K comparable, V any] struct {
kv map[K]*list.Element
ll *list.List
kv map[K]*Element[K, V]
ll list[K, V]
}

func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
return &OrderedMap[K, V]{
kv: make(map[K]*list.Element),
ll: list.New(),
kv: make(map[K]*Element[K, V]),
}
}

Expand All @@ -26,7 +16,7 @@ func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
func (m *OrderedMap[K, V]) Get(key K) (value V, ok bool) {
v, ok := m.kv[key]
if ok {
value = v.Value.(*orderedMapElement[K, V]).value
value = v.Value
}

return
Expand All @@ -36,23 +26,22 @@ func (m *OrderedMap[K, V]) Get(key K) (value V, ok bool) {
// will be returned. The returned value will be false if the value was replaced
// (even if the value was the same).
func (m *OrderedMap[K, V]) Set(key K, value V) bool {
_, didExist := m.kv[key]

if !didExist {
element := m.ll.PushBack(&orderedMapElement[K, V]{key, value})
m.kv[key] = element
} else {
m.kv[key].Value.(*orderedMapElement[K, V]).value = value
_, alreadyExist := m.kv[key]
if alreadyExist {
m.kv[key].Value = value
return false
}

return !didExist
element := m.ll.PushBack(key, value)
m.kv[key] = element
return true
}

// GetOrDefault returns the value for a key. If the key does not exist, returns
// the default value instead.
func (m *OrderedMap[K, V]) GetOrDefault(key K, defaultValue V) V {
if value, ok := m.kv[key]; ok {
return value.Value.(*orderedMapElement[K, V]).value
return value.Value
}

return defaultValue
Expand All @@ -61,14 +50,9 @@ func (m *OrderedMap[K, V]) GetOrDefault(key K, defaultValue V) V {
// GetElement returns the element for a key. If the key does not exist, the
// pointer will be nil.
func (m *OrderedMap[K, V]) GetElement(key K) *Element[K, V] {
value, ok := m.kv[key]
element, ok := m.kv[key]
if ok {
element := value.Value.(*orderedMapElement[K, V])
return &Element[K, V]{
element: value,
Key: element.key,
Value: element.value,
}
return element
}

return nil
Expand All @@ -83,14 +67,10 @@ func (m *OrderedMap[K, V]) Len() int {
// replaced it will retain the same position. To ensure most recently set keys
// are always at the end you must always Delete before Set.
func (m *OrderedMap[K, V]) Keys() (keys []K) {
keys = make([]K, m.Len())

element := m.ll.Front()
for i := 0; element != nil; i++ {
keys[i] = element.Value.(*orderedMapElement[K, V]).key
element = element.Next()
keys = make([]K, 0, m.Len())
for el := m.Front(); el != nil; el = el.Next() {
keys = append(keys, el.Key)
}

return keys
}

Expand All @@ -109,45 +89,21 @@ func (m *OrderedMap[K, V]) Delete(key K) (didDelete bool) {
// Front will return the element that is the first (oldest Set element). If
// there are no elements this will return nil.
func (m *OrderedMap[K, V]) Front() *Element[K, V] {
front := m.ll.Front()
if front == nil {
return nil
}

element := front.Value.(*orderedMapElement[K, V])

return &Element[K, V]{
element: front,
Key: element.key,
Value: element.value,
}
return m.ll.Front()
}

// Back will return the element that is the last (most recent Set element). If
// there are no elements this will return nil.
func (m *OrderedMap[K, V]) Back() *Element[K, V] {
back := m.ll.Back()
if back == nil {
return nil
}

element := back.Value.(*orderedMapElement[K, V])

return &Element[K, V]{
element: back,
Key: element.key,
Value: element.value,
}
return m.ll.Back()
}

// Copy returns a new OrderedMap with the same elements.
// Using Copy while there are concurrent writes may mangle the result.
func (m *OrderedMap[K, V]) Copy() *OrderedMap[K, V] {
m2 := NewOrderedMap[K, V]()

for el := m.Front(); el != nil; el = el.Next() {
m2.Set(el.Key, el.Value)
}

return m2
}

0 comments on commit b46f20e

Please sign in to comment.