Python 具有numpy/ctypes的环形缓冲区

Python 具有numpy/ctypes的环形缓冲区,python,numpy,ctypes,circular-buffer,Python,Numpy,Ctypes,Circular Buffer,我正在开发一个客户端,它将通过tcp接收[EEG]数据并将其写入环形缓冲区。我认为将缓冲区作为ctypes或numpy数组是非常方便的,因为可以创建一个numpy“视图”到此类缓冲区的任何位置,并在不进行任何复制操作的情况下读取/写入/处理数据。或者这是一个坏主意 但是,我不知道如何以这种方式实现固定大小的循环缓冲区。假设我创建了一个在内存中连续的缓冲区对象。到达缓冲区末尾时写入数据的最佳方式是什么 一种可能的方法是,当写指针到达缓冲区数组的末尾时,从一开始就开始覆盖(已经旧的)字节。但是,在这

我正在开发一个客户端,它将通过tcp接收[EEG]数据并将其写入环形缓冲区。我认为将缓冲区作为ctypes或numpy数组是非常方便的,因为可以创建一个numpy“视图”到此类缓冲区的任何位置,并在不进行任何复制操作的情况下读取/写入/处理数据。或者这是一个坏主意

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

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

UPD:谢谢大家的回答。如果有人也面临同样的问题,这就是我的最终代码

一种可能的方法是,当写指针到达缓冲区数组的末尾时,从一开始就开始覆盖(已经旧的)字节

这是固定大小的环形缓冲区中的唯一选项

我读过,不可能制作出这样的圆形切片

这就是为什么我不会以一种不切实际的观点来做这件事。您可以在
ndarray
周围创建一个
class
包装,保存缓冲区/数组、容量和指向插入点的指针(索引)。如果要以Numpy数组的形式获取内容,则必须制作如下副本:

buf = np.array([1,2,3,4])
indices = [3,0,1,2]
contents = buf[indices]    # copy

如果实现
\uuuu setitem\uuuuuuuuuu
\uuuuu setslice\uuuuuuuuu

,仍然可以将元素的值设置到位。如果需要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

@Janne Karila答案的一个变体,用于C而不是numpy:
如果环形缓冲区很宽,比如nx1g,那么与其将整个缓冲区加倍, 将一个包含2*N个指针的数组加倍到其行。 例如,对于N=3,初始化

bufp = { buf[0], buf[1], buf[2], buf[0], buf[1], buf[2] };

然后只写一次数据,
anyfunc(bufp[j:j+3])
按时间顺序查看
buf
中的行。

我认为您需要从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
将numpy导入为np
类环形缓冲区(对象):
def uuu init uuuu(自身、大小、填充=无):
self.size=大小
self.padding=如果padding不是其他padding,则为size
self.buffer=np.zero(self.size+self.padding)
self.counter=0
def附加(自身、数据):
“”“这是一个O(n)操作”“”
data=data[-self.padding:]
n=len(数据)
如果self.remaining
谢谢。但是,如果无论如何都必须复制这样的块,那么最好使用collections.deque作为缓冲区,然后执行
numpy.array(list(itertools.islice(buf,chstart,chend))
?或者速度要慢得多?我想避免复制,因为对该数据执行滑动窗口FFT将意味着每次新的数据点复制几乎相同的数据块arrives@dmytro:您必须测量
deque
是否更快。如果要将环形缓冲区存储的数据获取到数组中,恐怕要避免复制并不容易。yepp。这就是我现在要写的。而不是2*N的缓冲区,我有一个任意长度+N的缓冲区,并遵循相同的想法。无论如何谢谢你!如果性能根本不受关注,这是很好的;但考虑到你的申请,我对此表示怀疑。感谢Eelco,您最好使用矢量化解决方案来解决问题。那么一组视图根本不起作用?但如何检测到这一点,并将整个Aptr替换为平面副本?Aptr[0]。标志有OWNDATA False,令人困惑。我不确定您所说的视图数组是什么意思;视图很容易动态构建,因此您实际上不需要将它们保留在阵列中。问题的关键是,如果您试图在任何po上使用数据作为numpy数组