go-datastructures icon indicating copy to clipboard operation
go-datastructures copied to clipboard

RingBuffer: 100% cpu usage

Open sheldonlyr opened this issue 8 years ago • 7 comments

$uname -a Linux Lee-Ubuntu 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $cat /etc/issue Linux Lee-Ubuntu 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $go env Linux Lee-Ubuntu 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Test code:


import (
    "fmt"
    "sync"
    "time"

"github.com/Workiva/go-datastructures/queue"


)

var wg sync.WaitGroup

func main() {
    wg.Add(2)
    q := queue.NewRingBuffer(4096)

go func() { defer wg.Done() for { time.Sleep(3 * time.Second) q.Offer("hello world") } }()

go func() { defer wg.Done() for { str, _ := q.Get() fmt.Println(str) } }()

wg.Wait()


}

sheldonlyr avatar Jul 18 '16 06:07 sheldonlyr

$go env GOARCH="amd64" GOBIN="" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/home/sheldon/Gopath" GORACE="" GOROOT="/usr/local/go" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GO15VENDOREXPERIMENT="1" CC="gcc" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0" CXX="g++" CGO_ENABLED="1"

sheldonlyr avatar Jul 18 '16 06:07 sheldonlyr

$go version go version go1.6.2 linux/amd64

sheldonlyr avatar Jul 18 '16 06:07 sheldonlyr

Im seeing the same. (Go 1.8)

package main

import (
  "github.com/Workiva/go-datastructures/queue"
)

func main() {
  rb := queue.NewRingBuffer(5)
  rb.Get()
}

100% cpu usage for above

riaan53 avatar Jan 31 '17 20:01 riaan53

100% CPU

any news for this?

kitech avatar Jun 13 '17 15:06 kitech

I need do this:

for rb.Len() == 0 { time.Sleep(50 * time.MillSecond) }
item, err := rb.Get()

kitech avatar Jun 14 '17 12:06 kitech

another patch use chan notification, run better now

diff --git a/queue/ring.go b/queue/ring.go
index dfd0b53..8b40dc9 100644
--- a/queue/ring.go
+++ b/queue/ring.go
@@ -57,6 +57,7 @@ type RingBuffer struct {
 	_padding2      [8]uint64
 	mask, disposed uint64
 	_padding3      [8]uint64
+	condC          chan bool
 	nodes          nodes
 }
 
@@ -67,6 +68,7 @@ func (rb *RingBuffer) init(size uint64) {
 		rb.nodes[i] = &node{position: i}
 	}
 	rb.mask = size - 1 // so we don't have to do this with every put/get operation
+	rb.condC = make(chan bool, 0)
 }
 
 // Put adds the provided item to the queue.  If the queue is full, this
@@ -115,6 +117,12 @@ L:
 
 	n.data = item
 	atomic.StoreUint64(&n.position, pos+1)
+	if rb.Len() < 3 {
+		select {
+		case rb.condC <- true:
+		default:
+		}
+	}
 	return true, nil
 }
 
@@ -146,6 +154,12 @@ L:
 			return nil, ErrDisposed
 		}
 
+		if rb.Len() == 0 {
+			select {
+			case <-rb.condC:
+			}
+		}
+
 		n = rb.nodes[pos&rb.mask]
 		seq := atomic.LoadUint64(&n.position)
 		switch dif := seq - (pos + 1); {
@@ -186,6 +200,10 @@ func (rb *RingBuffer) Cap() uint64 {
 // queue will return an error.
 func (rb *RingBuffer) Dispose() {
 	atomic.CompareAndSwapUint64(&rb.disposed, 0, 1)
+	select {
+	case rb.condC <- true:
+	default:
+	}
 }
 
 // IsDisposed will return a bool indicating if this queue has been

kitech avatar Jan 20 '19 08:01 kitech

Get() has 100% CPU usage because it uses busy waiting strategy when the buffer is empty.

Ideally we should add a new method which returns as soon as we find out the buffer is empty, and returns a boolean to allow users to decide what they want to do in this case (busy waiting, sleep, or something else). This is we do in Put() and Offer().

For now users can workaround this issue by replacing Get() with Poll(1), which returns (nil, ErrTimeout) after the buffer has been empty for 1ns.

chrisxue815 avatar Aug 29 '19 16:08 chrisxue815