带有numpy / ctypes的环形缓冲区

时间:2012-01-18 11:03:52

标签: python numpy ctypes circular-buffer

我正在开发一个客户端,它将通过tcp接收[EEG]数据并将其写入环形缓冲区。我认为将缓冲区作为ctypes或numpy数组非常方便,因为它可以为这样的缓冲区的任何位置创建一个numpy“视图”,并且可以读取/写入/处理数据而无需任何复制操作。或者一般来说这是一个坏主意吗?

但是,我没有看到如何以这种方式实现固定大小的循环缓冲区。假设我创建了一个在内存中连续的缓冲区对象。到达缓冲区末尾时写入数据的最佳方法是什么?

一种可能的方法是在写指针到达缓冲区数组的末尾时开始覆盖(已经很旧的)字节。然而,在边界附近,在这种情况下,某些块(用于处理)的numpy视图无法创建(或者可以吗?),因为其中一些仍然可以位于缓冲区数组的末尾,而另一个已经在它的开始。我读过它是不可能创建这样的圆形切片。怎么解决这个问题?

UPD:感谢大家的答案。如果有人也面临同样的问题,here是我得到的最终代码。

4 个答案:

答案 0 :(得分:4)

如果需要一个N字节的窗口,请将缓冲区设为2 * N字节,并将所有输入写入两个位置:i % Ni % 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中的行。