まず、見出し。ビットintを使用してヘッドとテールの「ポインター」を保持し、完全に同期するようにサイズを変更する場合は、バッファーをラップするためにモジュロ演算は必要ありません。IE:12ビットのunsigned intに詰め込まれた4096は、それ自体が0であり、いかなる方法でも問題がありません。2の累乗であっても、モジュロ演算を排除すると、速度が2倍になります(ほぼ正確に)。
VisualStudio2010のC++コンパイラをデフォルトのインライン化で使用する第3世代i7DellXPS 8500では、あらゆるタイプのデータ要素の4096バッファの充填と排出を1,000万回繰り返すと、52秒かかり、その1/8192回でデータを処理します。
私はRXでmain()のテストループを書き直して、フローを制御しなくなるようにします。これは、バッファがいっぱいまたは空であることを示す戻り値と、それに伴うブレークによって制御されます。ステートメント。IE:フィラーとドレイナーは、破損や不安定さなしに互いにぶつかることができる必要があります。ある時点で、このコードをマルチスレッド化することを望んでいます。その場合、その動作が重要になります。
QUEUE_DESC(キュー記述子)および初期化関数は、このコード内のすべてのバッファーを2の累乗にするように強制します。それ以外の場合、上記のスキームは機能しません。この件に関しては、QUEUE_DESCはハードコーディングされていないことに注意してください。その構築には、マニフェスト定数(#define BITS_ELE_KNT)が使用されます。(ここでは2の累乗で十分な柔軟性があると思います)
バッファサイズを実行時に選択可能にするために、さまざまなアプローチ(ここには示されていません)を試し、FIFOバッファ[USHRT]を管理できるHead、Tail、EleKntにUSHRTを使用することにしました。モジュロ演算を回避するために、Head、Tailを使用して&&のマスクを作成しましたが、そのマスクは(EleKnt -1)であることが判明したため、それを使用してください。ビットintの代わりに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;
}