7
votes

Tampon à anneau avec NUMPY / CTYPES

Je développe un client qui recevra les données [EEG] sur TCP et écrivez-la sur la mémoire tampon d'anneau. Je pensais que cela peut être très pratique d'avoir le tampon en tant que CTTYPES ou une matrice numpue, car il est possible de créer une "vue" numpue à n'importe quel emplacement de ce tampon et de lecture / écriture / traiter les données sans opérations de copie. Ou est-ce une mauvaise idée en général?

Cependant, je ne vois pas comment implémenter un tampon circulaire d'une taille fixe de cette façon. Supposons que j'ai créé un objet tampon contigu en mémoire. Quelle est la meilleure façon d'écrire les données lorsque la fin du tampon est atteinte?

Un moyen possible est de commencer à écraser les octets (déjà anciens) du début lorsque le pointeur d'écriture atteint la fin de la matrice tampon. Près des limites, cependant, la vue générale de certains morceaux (pour la transformation) ne peut pas être créée (ou peut-il?) Dans ce cas, car une partie de celle-ci peut toujours être située à la fin de la matrice tampon tandis que d'autres déjà dans son début. J'ai lu qu'il est impossible de créer de telles tranches circulaires. Comment résoudre ce problème?

UPD: Merci tout le monde pour les réponses. Au cas où quelqu'un est également confronté au même problème, ici '' s le code final que j'ai.


1 commentaires

J'ai écrit le numpy_ringbuffer paquet pour résoudre ce problème, lequel fournit une interface deque à un tampon sous-jacent


4 Réponses :


2
votes

Un moyen possible est de commencer à écraser les octets (déjà anciens) du début lorsque le pointeur d'écriture atteint la fin de la matrice tampon. P>

C'est la seule option dans un tampon à anneau de taille fixe. P>

J'ai lu qu'il est impossible de créer de telles tranches circulaires. P> BlockQuote>

C'est pourquoi je ne ferais pas cela avec une vue engourdie. Vous pouvez créer une liste code> wrapper autour d'un ndarray code> à la place du tampon / de la matrice, de la capacité et d'un pointeur (index) au point d'insertion. Si vous souhaitez obtenir le contenu sous forme de tableau NUMPY, vous devez effectuer une copie comme si: P>

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


3 commentaires

Merci. Mais, si ce morceau doit être copié de toute façon, il ne sera donc pas préférable d'utiliser des collections. ) ? Ou est-ce beaucoup plus lent?


Je voulais éviter de copier parce que faire des fenêtres glissantes sur ces données signifiera la copie de presque la même pièce de données à chaque fois qu'un nouveau DataPoint arrive


@dmytro: Vous devrez mesurer si deque est plus rapide. Je crains que cela ne soit pas facile d'éviter de copier si vous souhaitez obtenir des données stockées par la mémoire tampon dans un tableau.



7
votes

Si vous avez besoin d'une fenêtre de n octets, faites votre mémoire tampon 2 * N octets et écrivez toutes les entrées à deux emplacements: i% n et i% n + n , où i est un compteur d'octets. De cette façon, vous avez toujours N octets consécutifs dans le tampon. xxx

impression xxx


2 commentaires

Yepp. C'est ce que j'essaie d'écrire maintenant. Au lieu de 2 * N tampon, je possède une durée de la longueur arbitraire + N et suivez la même idée. Merci quand même!


C'est bien si la performance n'est pas une préoccupation du tout; Mais je doute que cela donne votre demande. Vous êtes probablement mieux en train d'utiliser une solution vectorisée à votre problème



-1
votes

Une variante de la réponse de @janne Karila, pour C mais pas Numpy:
Si la mémoire tampon d'anneau est très large, comme n x 1g, puis au lieu de doubler le tout, Doubler un tableau de 2 * N pointres à ses rangées. Par exemple. Pour n = 3, initialisez xxx

alors vous n'écrivez que des données une seule fois, et Anfuncunc (BUFP [J + 3]) voit les rangées dans buf dans l'ordre de temps.


0 commentaires

2
votes

Je pense que vous devez prendre un recul de la pensée de style C ici. La mise à jour d'une ringbuffer pour chaque insertion ne sera jamais efficace. Un tampon à anneau est fondamentalement différent de l'interface de bloc de mémoire contiguë que la demande numpopie demande; Y compris la FFT que vous mentionnerez que vous voulez faire.

Une solution naturelle consiste à sacrifier un peu de mémoire pour des performances. Par exemple, si le nombre d'éléments que vous devez conserver dans votre tampon est N, allouez un tableau de N + 1024 (ou d'un nombre sensible). Ensuite, il vous suffit de déplacer n éléments autour de toutes les insertions de 1024, et vous avez toujours une vue contiguë de N éléments à agir directement disponible. P>

Edit: Voici un extrait de code qui implémente ce qui précède et devrait donner de bonnes performances. Notez cependant que vous seriez bien conseillé d'ajouter des morceaux, plutôt que par élément. Sinon, les avantages de performance de l'utilisation de NUMPY sont rapidement annulés, peu importe la manière dont vous implémentez votre Runbuffer. P>

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 commentaires

Merci Eelco. Donc, une gamme de points de vue ne fonctionne tout simplement pas? Mais comment est-ce détecté et toute l'APTR remplacé par une copie à plat? APTR [0] .FLAGS a de la propriété fausse, déroutante.


Je ne suis pas sûr de ce que vous entendez par un éventail de vues; Les vues sont faciles à construire à la volée, vous n'avez donc pas besoin de les garder dans un tableau. Le creux de la question est que si vous cherchez à utiliser vos données comme une matrice numpue à tout moment, elle doit mentir en mémoire contiguë. Soit vous engagez une performance O (n) touchée à chaque fois que vous devez accéder à votre Ringbuffer contiguë après une annexe ou que vous devez attribuer une mémoire supplémentaire, vous pouvez donc retarder et amortir ces opérations nécessaires pour garder votre mémoire contiguë.