我正在开发一个客户端,它将通过tcp接收[EEG]数据并将其写入环形缓冲区。我认为将缓冲区作为ctypes或numpy数组非常方便,因为它可以为这样的缓冲区的任何位置创建一个numpy“视图”,并且可以读取/写入/处理数据而无需任何复制操作。或者一般来说这是一个坏主意吗?
但是,我没有看到如何以这种方式实现固定大小的循环缓冲区。假设我创建了一个在内存中连续的缓冲区对象。到达缓冲区末尾时写入数据的最佳方法是什么?
一种可能的方法是在写指针到达缓冲区数组的末尾时开始覆盖(已经很旧的)字节。然而,在边界附近,在这种情况下,某些块(用于处理)的numpy视图无法创建(或者可以吗?),因为其中一些仍然可以位于缓冲区数组的末尾,而另一个已经在它的开始。我读过它是不可能创建这样的圆形切片。怎么解决这个问题?
UPD:感谢大家的答案。如果有人也面临同样的问题,here是我得到的最终代码。
答案 0 :(得分:4)
如果需要一个N字节的窗口,请将缓冲区设为2 * N字节,并将所有输入写入两个位置:i % N
和i % N + N
,其中i
是字节计数器。这样,缓冲区中总有N个连续字节。
data = 'Data to buffer'
N = 4
buf = 2*N*['\00']
for i,c in enumerate(data):
j = i % N
buf[j] = c
buf[j+N] = c
if i >= N-1:
print ''.join(buf[j+1:j+N+1])
打印
Data
ata
ta t
a to
to
to b
o bu
buf
buff
uffe
ffer
答案 1 :(得分:2)
一种可能的方法是,当写指针到达缓冲区数组的末尾时,从开始时覆盖(已经是旧的)字节。
这是固定大小的环形缓冲区中唯一的选择。
我读过它是不可能创建这样的圆形切片。
这就是为什么我不会在Numpy视图中这样做的原因。您可以在class
周围创建一个ndarray
包装器,将缓冲区/数组,容量和指针(索引)保存到插入点。如果你想把内容作为Numpy数组,你必须像这样制作一个副本:
buf = np.array([1,2,3,4])
indices = [3,0,1,2]
contents = buf[indices] # copy
如果您实施__setitem__
和__setslice__
,仍可以就地设置元素值。
答案 2 :(得分:2)
我认为你需要从C风格的思维中退后一步。为每次插入更新一个ringbuffer永远不会有效。环形缓冲区与numpy阵列所需的连续内存块接口根本不同;包括你想提到的fft。
一个自然的解决方案是为了性能而牺牲一点内存。例如,如果缓冲区中需要保存的元素数量为N,则分配N + 1024(或某个合理数字)的数组。然后你只需要在每1024个插入周围移动N个元素,并且你总是有一个N个元素的连续视图来直接可用。
编辑:这是一个实现上述功能的代码片段,应该提供良好的性能。但请注意,建议您以块的形式附加,而不是按元素追加。否则,无论你如何实现你的ringbuffer,使用numpy的性能优势都会很快失效。
import numpy as np
class RingBuffer(object):
def __init__(self, size, padding=None):
self.size = size
self.padding = size if padding is None else padding
self.buffer = np.zeros(self.size+self.padding)
self.counter = 0
def append(self, data):
"""this is an O(n) operation"""
data = data[-self.padding:]
n = len(data)
if self.remaining < n: self.compact()
self.buffer[self.counter+self.size:][:n] = data
self.counter += n
@property
def remaining(self):
return self.padding-self.counter
@property
def view(self):
"""this is always an O(1) operation"""
return self.buffer[self.counter:][:self.size]
def compact(self):
"""
note: only when this function is called, is an O(size) performance hit incurred,
and this cost is amortized over the whole padding space
"""
print 'compacting'
self.buffer[:self.size] = self.view
self.counter = 0
rb = RingBuffer(10)
for i in range(4):
rb.append([1,2,3])
print rb.view
rb.append(np.arange(15))
print rb.view #test overflow
答案 3 :(得分:-1)
@Janne Karila的答案的变体,对于C但不是numpy:
如果环形缓冲区很宽,比如N x 1G,那么不要将整个事物加倍,
将其行的2 * N指针数组加倍。
例如。对于N = 3,初始化
bufp = { buf[0], buf[1], buf[2], buf[0], buf[1], buf[2] };
然后您只编写一次数据,anyfunc( bufp[j:j+3] )
按时间顺序查看buf
中的行。