кольцевой буфер с numpy / ctypes - PullRequest
6 голосов
/ 18 января 2012

Я разрабатываю клиент, который будет получать данные [EEG] через tcp и записывать их в кольцевой буфер. Я подумал, что может быть очень удобно иметь буфер в виде массива ctypes или numpy, потому что можно создать «тупое» представление в любом месте такого буфера и читать / записывать / обрабатывать данные без каких-либо операций копирования. Или это вообще плохая идея?

Однако я не вижу, как реализовать кольцевой буфер фиксированного размера таким образом. Предположим, я создал буферный объект, который является непрерывным в памяти. Каков наилучший способ записи данных при достижении конца буфера?

Один из возможных способов - это начать перезаписывать (уже старые) байты с начала, когда указатель записи достигает конца буферного массива. Однако вблизи границ не может быть создано пустое представление некоторого фрагмента (для обработки) (или может ли это?) В этом случае, потому что некоторые из них все еще могут находиться в конце массива буфера, в то время как другие уже находятся в его начало. Я читал, что невозможно создать такие круглые ломтики. Как это решить?

UPD: Спасибо всем за ответы. В случае, если кто-то также сталкивается с той же проблемой, здесь - это последний код, который я получил.

Ответы [ 4 ]

4 голосов
/ 18 января 2012

Если вам нужно окно из 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
2 голосов
/ 21 июля 2014

Я думаю, вам нужно отступить от мышления в стиле C здесь. Обновление кольцевого буфера для каждой вставки никогда не будет эффективным. Кольцевой буфер принципиально отличается от непрерывного интерфейса блока памяти, который требуется для массивов; включая FFT, о котором вы говорите, что хотите сделать.

Естественное решение - пожертвовать небольшим количеством памяти ради производительности. Например, если количество элементов, которое вам нужно хранить в буфере, равно N, выделите массив из N + 1024 (или некоторого разумного числа). Тогда вам нужно всего лишь перемещать N элементов вокруг каждых 1024 вставок, и у вас всегда есть непрерывное представление о N элементах, чтобы действовать непосредственно на них.

РЕДАКТИРОВАТЬ: вот фрагмент кода, который реализует вышеупомянутое, и должен дать хорошую производительность. Тем не менее, обратите внимание, что вам будет лучше добавлять куски, а не по элементам. В противном случае преимущества производительности при использовании 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
2 голосов
/ 18 января 2012

Один из возможных способов - начать перезапись (уже старых) байтов с начала, когда указатель записи достигнет конца буферного массива.

Это единственная опция в кольцевом буфере фиксированного размера.

Я читал, что невозможно создать такие круглые кусочки.

Именно поэтому я бы не стал делать это с видом Numpy. Вместо этого вы можете создать оболочку class вокруг ndarray, удерживая буфер / массив, емкость и указатель (индекс) в точке вставки. Если вы хотите получить содержимое в виде массива Numpy, вам нужно будет сделать копию следующим образом:

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

Вы по-прежнему можете устанавливать значения элементов на месте, если реализуете __setitem__ и __setslice__.

0 голосов
/ 20 июля 2014

Вариант ответа @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 в порядке времени.

...