任意のタイプのオブジェクトを保持できる固定サイズ(コンパイル時ではなく実行時に作成時に選択可能)の循環バッファが必要であり、very高性能である必要があります。マルチタスクの組み込み環境ではありますが、タスク自体がそれを管理できるように協同的な環境であるため、リソース競合の問題はないと思います。
私の最初の考えは、タイプ(単純な列挙/定義)とペイロードへのvoidポインターを含む単純な構造体をバッファーに格納することでしたが、これを可能な限り高速にしたいので、バイパスを含む提案を受け入れますヒープ。
実際、生の速度のために標準ライブラリをバイパスするのはうれしいです-私がコードから見たところから、それはCPUにあまり最適化されていません:strcpy()
のようなもののためにCコードをコンパイルしたようですなど、手動でコーディングされたアセンブリはありません。
コードやアイデアは大歓迎です。必要な操作は次のとおりです。
バッファをコーディングするときに必要な型を列挙できますか、それとも実行時に動的呼び出しを介して型を追加できる必要がありますか?前者の場合、バッファをn個の構造体のヒープに割り当てられた配列として作成します。各構造体は、データ型を識別する列挙型タグとすべてのデータ型の結合の2つの要素で構成されます。小さい要素の追加ストレージに関して失われるものは、割り当て/割り当て解除とその結果のメモリの断片化を処理する必要がないという点で補います。次に、バッファの先頭と末尾の要素を定義する開始インデックスと終了インデックスを追跡し、インデックスをインクリメント/デクリメントするときにmod nを計算する必要があります。
最も簡単な解決策は、アイテムのサイズとアイテムの数を追跡し、適切なバイト数のバッファーを作成することです。
typedef struct circular_buffer
{
void *buffer; // data buffer
void *buffer_end; // end of data buffer
size_t capacity; // maximum number of items in the buffer
size_t count; // number of items in the buffer
size_t sz; // size of each item in the buffer
void *head; // pointer to head
void *tail; // pointer to tail
} circular_buffer;
void cb_init(circular_buffer *cb, size_t capacity, size_t sz)
{
cb->buffer = malloc(capacity * sz);
if(cb->buffer == NULL)
// handle error
cb->buffer_end = (char *)cb->buffer + capacity * sz;
cb->capacity = capacity;
cb->count = 0;
cb->sz = sz;
cb->head = cb->buffer;
cb->tail = cb->buffer;
}
void cb_free(circular_buffer *cb)
{
free(cb->buffer);
// clear out other fields too, just to be safe
}
void cb_Push_back(circular_buffer *cb, const void *item)
{
if(cb->count == cb->capacity){
// handle error
}
memcpy(cb->head, item, cb->sz);
cb->head = (char*)cb->head + cb->sz;
if(cb->head == cb->buffer_end)
cb->head = cb->buffer;
cb->count++;
}
void cb_pop_front(circular_buffer *cb, void *item)
{
if(cb->count == 0){
// handle error
}
memcpy(item, cb->tail, cb->sz);
cb->tail = (char*)cb->tail + cb->sz;
if(cb->tail == cb->buffer_end)
cb->tail = cb->buffer;
cb->count--;
}
// Note power of two buffer size
#define kNumPointsInMyBuffer 1024
typedef struct _ringBuffer {
UInt32 currentIndex;
UInt32 sizeOfBuffer;
double data[kNumPointsInMyBuffer];
} ringBuffer;
// Initialize the ring buffer
ringBuffer *myRingBuffer = (ringBuffer *)calloc(1, sizeof(ringBuffer));
myRingBuffer->sizeOfBuffer = kNumPointsInMyBuffer;
myRingBuffer->currentIndex = 0;
// A little function to write into the buffer
// N.B. First argument of writeIntoBuffer() just happens to have the
// same as the one calloc'ed above. It will only point to the same
// space in memory if the calloc'ed pointer is passed to
// writeIntoBuffer() as an arg when the function is called. Consider
// using another name for clarity
void writeIntoBuffer(ringBuffer *myRingBuffer, double *myData, int numsamples) {
// -1 for our binary modulo in a moment
int buffLen = myRingBuffer->sizeOfBuffer - 1;
int lastWrittenSample = myRingBuffer->currentIndex;
int idx;
for (int i=0; i < numsamples; ++i) {
// modulo will automagically wrap around our index
idx = (i + lastWrittenSample) & buffLen;
myRingBuffer->data[idx] = myData[i];
}
// Update the current index of our ring buffer.
myRingBuffer->currentIndex += numsamples;
myRingBuffer->currentIndex &= myRingBuffer->sizeOfBuffer - 1;
}
リングバッファの長さが2の累乗である限り、信じられないほど高速なバイナリ「&」操作により、インデックスがラップされます。私のアプリケーションでは、マイクから取得した音声のリングバッファーからユーザーに音声のセグメントを表示しています。
画面に表示できるオーディオの最大量がリングバッファーのサイズよりもはるかに少ないことを常に確認します。それ以外の場合は、同じチャンクから読み取りと書き込みを行っている可能性があります。これにより、奇妙な表示アーティファクトが発生する可能性があります。
まず、見出し。ビット整数を使用してヘッドとテールの「ポインター」を保持し、それらが完全に同期するようにサイズを調整する場合、バッファーをラップするためのモジュロ演算は必要ありません。 IE:12ビットの符号なしintに詰め込まれた4096は、それ自体が0であり、どのような方法であっても無害です。モジュロ演算を排除すると、2の累乗であっても速度が2倍になります-ほぼ正確に。
任意のタイプのデータ要素の4096バッファーを充填および排出する1,000万回の反復には、デフォルトのインライン化を備えたVisual Studio 2010のC++コンパイラーを使用する第3世代i7 Dell XPS 8500で52秒かかります。
RXはmain()のテストループを書き換えて、フローを制御しないようにします。これは、バッファーがいっぱいまたは空であることを示す戻り値、およびアテンダントブレークによって制御されます。ステートメント。 IE:フィラーとドレーナーは、破損や不安定なしにお互いにぶつかるはずです。ある時点で、このコードをマルチスレッド化し、その動作が重要になることを期待しています。
QUEUE_DESC(キュー記述子)および初期化関数は、このコードのすべてのバッファーを2のべき乗に強制します。上記のスキームは、他の方法では機能しません。主題については、QUEUE_DESCはハードコードされていないことに注意してください。構築にはマニフェスト定数(#define BITS_ELE_KNT)を使用します。 (ここでは、2の累乗で十分な柔軟性があると仮定しています)
実行時にバッファサイズを選択可能にするために、さまざまなアプローチ(ここには示されていません)を試し、FIFOバッファ[USHRT]を管理できるHead、Tail、EleKntにUSHRTを使用することに決めました。モジュロ演算を回避するために、Head、Tailで&&にマスクを作成しましたが、そのマスクは(EleKnt -1)であるため、そのまま使用します。ビット整数の代わりにUSHRTSを使用すると、静かなマシンでパフォーマンスが15%向上しました。 Intel CPUコアは常にバスよりも高速であるため、ビジーな共有マシンでは、データ構造を圧縮することで、他の競合するスレッドより先にロードして実行できます。トレードオフ。
バッファーの実際のストレージはcalloc()でヒープに割り当てられ、ポインターは構造体のベースにあるため、構造体とポインターはまったく同じアドレスを持っていることに注意してください。 IE;レジスタを固定するために構造体アドレスにオフセットを追加する必要はありません。
同じように、バッファのサービスに付随する変数はすべて、物理的にバッファに隣接し、同じ構造体にバインドされているため、コンパイラは美しいアセンブリ言語を作成できます。アセンブリを表示するには、インライン最適化を強制終了する必要があります。そうしないと、アセンブリが忘れられてしまいます。
任意のデータ型の多態性をサポートするために、割り当ての代わりにmemcpy()を使用しました。コンパイルごとに1つのランダム変数タイプをサポートする柔軟性のみが必要な場合、このコードは完全に機能します。
ポリモーフィズムの場合、タイプとストレージ要件を知るだけで済みます。記述子のDATA_DESC配列は、QUEUE_DESC.pBufferに格納される各データを追跡して、適切に取得できるようにする方法を提供します。最大のデータ型のすべての要素を保持するのに十分なpBufferメモリを割り当てますが、DATA_DESC.dBytesで特定のデータが実際に使用しているストレージの量を追跡します。別の方法は、ヒープマネージャーを再発明することです。
つまり、QUEUE_DESCのUCHAR * pBufferには、データ型とサイズを追跡するための並列コンパニオン配列がありますが、pBufferのデータの保存場所は現在のままです。新しいメンバーは、そのような前方参照を使用してコンパイラーを打ち負かす方法を見つけることができる場合、DATA_DESC * pDataDesc、または、おそらくDATA_DESC DataDesc [2 ^ BITS_ELE_KNT]のようなものになります。これらの状況では、Calloc()は常により柔軟です。
Q_Put()、Q_Getのmemcpy()を引き続き使用しますが、実際にコピーされるバイト数は、QUEUE_DESC.EleBytesではなくDATA_DESC.dBytesによって決定されます。要素は、潜在的にすべての特定のputまたはgetの異なるタイプ/サイズです。
このコードは速度とバッファサイズの要件を満たし、6つの異なるデータ型の要件を満たすように作成できると思います。 printf()ステートメントの形式で多くのテストフィクスチャを残したので、コードが適切に動作することを自分で満足する(またはしない)ことができます。乱数ジェネレーターは、コードが任意のランダムなヘッド/テールコンボで機能することを示しています。
enter code here
// Queue_Small.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <time.h>
#include <limits.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <math.h>
#define UCHAR unsigned char
#define ULONG unsigned long
#define USHRT unsigned short
#define dbl double
/* Queue structure */
#define QUEUE_FULL_FLAG 1
#define QUEUE_EMPTY_FLAG -1
#define QUEUE_OK 0
//
#define BITS_ELE_KNT 12 //12 bits will create 4.096 elements numbered 0-4095
//
//typedef struct {
// USHRT dBytes:8; //amount of QUEUE_DESC.EleBytes storage used by datatype
// USHRT dType :3; //supports 8 possible data types (0-7)
// USHRT dFoo :5; //unused bits of the unsigned short Host's storage
// } DATA_DESC;
// This descriptor gives a home to all the Housekeeping variables
typedef struct {
UCHAR *pBuffer; // pointer to storage, 16 to 4096 elements
ULONG Tail :BITS_ELE_KNT; // # elements, with range of 0-4095
ULONG Head :BITS_ELE_KNT; // # elements, with range of 0-4095
ULONG EleBytes :8; // sizeof(elements) with range of 0-256 bytes
// some unused bits will be left over if BITS_ELE_KNT < 12
USHRT EleKnt :BITS_ELE_KNT +1;// 1 extra bit for # elements (1-4096)
//USHRT Flags :(8*sizeof(USHRT) - BITS_ELE_KNT +1); // flags you can use
USHRT IsFull :1; // queue is full
USHRT IsEmpty :1; // queue is empty
USHRT Unused :1; // 16th bit of USHRT
} QUEUE_DESC;
// ---------------------------------------------------------------------------
// Function prototypes
QUEUE_DESC *Q_Init(QUEUE_DESC *Q, int BitsForEleKnt, int DataTypeSz);
int Q_Put(QUEUE_DESC *Q, UCHAR *pNew);
int Q_Get(UCHAR *pOld, QUEUE_DESC *Q);
// ---------------------------------------------------------------------------
QUEUE_DESC *Q_Init(QUEUE_DESC *Q, int BitsForEleKnt, int DataTypeSz) {
memset((void *)Q, 0, sizeof(QUEUE_DESC));//init flags and bit integers to zero
//select buffer size from powers of 2 to receive modulo
// arithmetic benefit of bit uints overflowing
Q->EleKnt = (USHRT)pow(2.0, BitsForEleKnt);
Q->EleBytes = DataTypeSz; // how much storage for each element?
// Randomly generated head, tail a test fixture only.
// Demonstrates that the queue can be entered at a random point
// and still perform properly. Normally zero
srand(unsigned(time(NULL))); // seed random number generator with current time
Q->Head = Q->Tail = Rand(); // supposed to be set to zero here, or by memset
Q->Head = Q->Tail = 0;
// allocate queue's storage
if(NULL == (Q->pBuffer = (UCHAR *)calloc(Q->EleKnt, Q->EleBytes))) {
return NULL;
} else {
return Q;
}
}
// ---------------------------------------------------------------------------
int Q_Put(QUEUE_DESC *Q, UCHAR *pNew)
{
memcpy(Q->pBuffer + (Q->Tail * Q->EleBytes), pNew, Q->EleBytes);
if(Q->Tail == (Q->Head + Q->EleKnt)) {
// Q->IsFull = 1;
Q->Tail += 1;
return QUEUE_FULL_FLAG; // queue is full
}
Q->Tail += 1; // the unsigned bit int MUST wrap around, just like modulo
return QUEUE_OK; // No errors
}
// ---------------------------------------------------------------------------
int Q_Get(UCHAR *pOld, QUEUE_DESC *Q)
{
memcpy(pOld, Q->pBuffer + (Q->Head * Q->EleBytes), Q->EleBytes);
Q->Head += 1; // the bit int MUST wrap around, just like modulo
if(Q->Head == Q->Tail) {
// Q->IsEmpty = 1;
return QUEUE_EMPTY_FLAG; // queue Empty - nothing to get
}
return QUEUE_OK; // No errors
}
//
// ---------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[]) {
// constrain buffer size to some power of 2 to force faux modulo arithmetic
int LoopKnt = 1000000; // for benchmarking purposes only
int k, i=0, Qview=0;
time_t start;
QUEUE_DESC Queue, *Q;
if(NULL == (Q = Q_Init(&Queue, BITS_ELE_KNT, sizeof(int)))) {
printf("\nProgram failed to initialize. Aborting.\n\n");
return 0;
}
start = clock();
for(k=0; k<LoopKnt; k++) {
//printf("\n\n Fill'er up please...\n");
//Q->Head = Q->Tail = Rand();
for(i=1; i<= Q->EleKnt; i++) {
Qview = i*i;
if(QUEUE_FULL_FLAG == Q_Put(Q, (UCHAR *)&Qview)) {
//printf("\nQueue is full at %i \n", i);
//printf("\nQueue value of %i should be %i squared", Qview, i);
break;
}
//printf("\nQueue value of %i should be %i squared", Qview, i);
}
// Get data from queue until completely drained (empty)
//
//printf("\n\n Step into the lab, and see what's on the slab... \n");
Qview = 0;
for(i=1; i; i++) {
if(QUEUE_EMPTY_FLAG == Q_Get((UCHAR *)&Qview, Q)) {
//printf("\nQueue value of %i should be %i squared", Qview, i);
//printf("\nQueue is empty at %i", i);
break;
}
//printf("\nQueue value of %i should be %i squared", Qview, i);
}
//printf("\nQueue head value is %i, tail is %i\n", Q->Head, Q->Tail);
}
printf("\nQueue time was %5.3f to fill & drain %i element queue %i times \n",
(dbl)(clock()-start)/(dbl)CLOCKS_PER_SEC,Q->EleKnt, LoopKnt);
printf("\nQueue head value is %i, tail is %i\n", Q->Head, Q->Tail);
getchar();
return 0;
}
Cの簡単なソリューションを次に示します。各機能について割り込みがオフになっていると仮定します。ポリモーフィズムやものはなく、常識です。
#define BUFSIZE 128
char buf[BUFSIZE];
char *pIn, *pOut, *pEnd;
char full;
// init
void buf_init()
{
pIn = pOut = buf; // init to any slot in buffer
pEnd = &buf[BUFSIZE]; // past last valid slot in buffer
full = 0; // buffer is empty
}
// add char 'c' to buffer
int buf_put(char c)
{
if (pIn == pOut && full)
return 0; // buffer overrun
*pIn++ = c; // insert c into buffer
if (pIn >= pEnd) // end of circular buffer?
pIn = buf; // wrap around
if (pIn == pOut) // did we run into the output ptr?
full = 1; // can't add any more data into buffer
return 1; // all OK
}
// get a char from circular buffer
int buf_get(char *pc)
{
if (pIn == pOut && !full)
return 0; // buffer empty FAIL
*pc = *pOut++; // pick up next char to be returned
if (pOut >= pEnd) // end of circular buffer?
pOut = buf; // wrap around
full = 0; // there is at least 1 slot
return 1; // *pc has the data to be returned
}
単純な実装は次のもので構成できます。
データを書き込むたびに、書き込みポインターを進め、カウンターをインクリメントします。データを読み取るときは、読み取りポインターを増やしてカウンターをデクリメントします。いずれかのポインターがnに達した場合、ゼロに設定します。
Counter = nの場合、書き込めません。 counter = 0の場合、読み取ることができません。
Cスタイル、整数用の単純なリングバッファ。最初にputとgetを使用するよりもinitを使用します。バッファにデータが含まれていない場合、「0」ゼロが返されます。
//=====================================
// ring buffer address based
//=====================================
#define cRingBufCount 512
int sRingBuf[cRingBufCount]; // Ring Buffer
int sRingBufPut; // Input index address
int sRingBufGet; // Output index address
Bool sRingOverWrite;
void GetRingBufCount(void)
{
int r;
` r= sRingBufPut - sRingBufGet;
if ( r < cRingBufCount ) r+= cRingBufCount;
return r;
}
void InitRingBuffer(void)
{
sRingBufPut= 0;
sRingBufGet= 0;
}
void PutRingBuffer(int d)
{
sRingBuffer[sRingBufPut]= d;
if (sRingBufPut==sRingBufGet)// both address are like ziro
{
sRingBufPut= IncRingBufferPointer(sRingBufPut);
sRingBufGet= IncRingBufferPointer(sRingBufGet);
}
else //Put over write a data
{
sRingBufPut= IncRingBufferPointer(sRingBufPut);
if (sRingBufPut==sRingBufGet)
{
sRingOverWrite= Ture;
sRingBufGet= IncRingBufferPointer(sRingBufGet);
}
}
}
int GetRingBuffer(void)
{
int r;
if (sRingBufGet==sRingBufPut) return 0;
r= sRingBuf[sRingBufGet];
sRingBufGet= IncRingBufferPointer(sRingBufGet);
sRingOverWrite=False;
return r;
}
int IncRingBufferPointer(int a)
{
a+= 1;
if (a>= cRingBufCount) a= 0;
return a;
}