Erstens die Überschrift. Sie benötigen keine Modulo-Arithmetik, um den Puffer zu umbrechen, wenn Sie Bit-Ints verwenden, um die "Zeiger" von Kopf und Schwanz zu halten und sie so zu dimensionieren, dass sie perfekt synchron sind. IE: 4096, gefüllt mit einem 12-Bit-Int ohne Vorzeichen, ist 0 für sich und in keiner Weise belästigt. Das Eliminieren der Modulo-Arithmetik, selbst bei Zweierpotenzen, verdoppelt die Geschwindigkeit - fast genau.
10 Millionen Iterationen zum Füllen und Entleeren eines 4096-Puffers mit Datenelementen aller Art dauern auf meinem Dell XPS 8500 der 3. Generation i7 mit dem C ++ - Compiler von Visual Studio 2010 mit Standard-Inlining 52 Sekunden, und 1/8192. davon, um ein Datum zu warten.
Ich würde RX die Testschleifen in main () neu schreiben, damit sie den Fluss nicht mehr steuern - was durch die Rückgabewerte gesteuert wird und sollte, die anzeigen, dass der Puffer voll oder leer ist, und die damit verbundene Unterbrechung; Aussagen. IE: Der Füllstoff und der Abfluss sollten in der Lage sein, ohne Korruption oder Instabilität gegeneinander zu schlagen. Irgendwann hoffe ich, diesen Code mit mehreren Threads zu versehen, woraufhin dieses Verhalten entscheidend sein wird.
Die Funktion QUEUE_DESC (Warteschlangendeskriptor) und die Initialisierung erzwingen, dass alle Puffer in diesem Code eine Potenz von 2 haben. Das obige Schema funktioniert sonst NICHT. Beachten Sie, dass QUEUE_DESC nicht fest codiert ist, sondern eine Manifestkonstante (#define BITS_ELE_KNT) für seine Konstruktion verwendet. (Ich gehe davon aus, dass eine Potenz von 2 hier ausreichend Flexibilität ist)
Um die Laufzeit der Puffergröße auswählbar zu machen, habe ich verschiedene Ansätze (hier nicht gezeigt) ausprobiert und mich für die Verwendung von USHRTs für Head, Tail, EleKnt entschieden, die einen FIFO-Puffer [USHRT] verwalten können. Um Modulo-Arithmetik zu vermeiden, habe ich eine Maske für && mit Head, Tail erstellt, aber diese Maske stellt sich als (EleKnt -1) heraus. Verwenden Sie sie also einfach. Die Verwendung von USHRTS anstelle von Bit-Ints erhöhte die Leistung auf einer leisen Maschine um ~ 15%. Intel-CPU-Kerne waren schon immer schneller als ihre Busse. Wenn Sie also auf einem ausgelasteten, gemeinsam genutzten Computer Ihre Datenstrukturen packen, werden Sie vor anderen konkurrierenden Threads geladen und ausgeführt. Kompromisse.
Beachten Sie, dass der tatsächliche Speicher für den Puffer mit calloc () auf dem Heap zugewiesen wird und sich der Zeiger an der Basis der Struktur befindet, sodass die Struktur und der Zeiger genau dieselbe Adresse haben. IE; Es muss kein Offset zur Strukturadresse hinzugefügt werden, um Register zu binden.
In diesem Sinne befinden sich alle Variablen, die mit der Wartung des Puffers verbunden sind, physisch neben dem Puffer und sind in dieselbe Struktur eingebunden, sodass der Compiler eine schöne Assemblersprache erstellen kann. Sie müssen die Inline-Optimierung beenden, um eine Baugruppe zu sehen, da sie sonst in Vergessenheit gerät.
Um den Polymorphismus eines beliebigen Datentyps zu unterstützen, habe ich memcpy () anstelle von Zuweisungen verwendet. Wenn Sie nur die Flexibilität benötigen, einen Zufallsvariablentyp pro Kompilierung zu unterstützen, funktioniert dieser Code einwandfrei.
Für Polymorphismus müssen Sie nur den Typ und die Speicheranforderungen kennen. Das DATA_DESC-Array von Deskriptoren bietet eine Möglichkeit, jedes Datum zu verfolgen, das in QUEUE_DESC.pBuffer abgelegt wird, damit es ordnungsgemäß abgerufen werden kann. Ich würde nur genug pBuffer-Speicher zuweisen, um alle Elemente des größten Datentyps aufzunehmen, aber verfolgen, wie viel von diesem Speicher ein bestimmtes Datum tatsächlich in DATA_DESC.dBytes verwendet. Die Alternative besteht darin, einen Heap-Manager neu zu erfinden.
Dies bedeutet, dass der UCHAR * pBuffer von QUEUE_DESC über ein paralleles Begleitarray verfügt, um den Datentyp und die Größe zu verfolgen, während der Speicherort eines Datums in pBuffer unverändert bleibt. Das neue Mitglied wäre so etwas wie DATA_DESC * pDataDesc oder vielleicht DATA_DESC DataDesc [2 ^ BITS_ELE_KNT], wenn Sie einen Weg finden, Ihren Compiler mit einer solchen Vorwärtsreferenz zur Übermittlung zu bewegen. Calloc () ist in diesen Situationen immer flexibler.
Sie würden noch memcpy () in Q_Put (), Q_Get, aber die Anzahl der tatsächlich kopierten Bytes wird durch DATA_DESC.dBytes bestimmt, nicht durch QUEUE_DESC.EleBytes. Die Elemente haben möglicherweise alle unterschiedliche Typen / Größen für einen bestimmten Put oder Get.
Ich glaube, dieser Code erfüllt die Anforderungen an Geschwindigkeit und Puffergröße und kann erstellt werden, um die Anforderungen für 6 verschiedene Datentypen zu erfüllen. Ich habe die vielen Testgeräte in Form von printf () -Anweisungen belassen, damit Sie sich davon überzeugen können (oder nicht), dass der Code ordnungsgemäß funktioniert. Der Zufallszahlengenerator zeigt, dass der Code für jede zufällige Kopf / Schwanz-Kombination funktioniert.
enter code here
#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
#define QUEUE_FULL_FLAG 1
#define QUEUE_EMPTY_FLAG -1
#define QUEUE_OK 0
#define BITS_ELE_KNT 12
typedef struct {
UCHAR *pBuffer;
ULONG Tail :BITS_ELE_KNT;
ULONG Head :BITS_ELE_KNT;
ULONG EleBytes :8;
USHRT EleKnt :BITS_ELE_KNT +1;
USHRT IsFull :1;
USHRT IsEmpty :1;
USHRT Unused :1;
} QUEUE_DESC;
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));
Q->EleKnt = (USHRT)pow(2.0, BitsForEleKnt);
Q->EleBytes = DataTypeSz;
srand(unsigned(time(NULL)));
Q->Head = Q->Tail = rand();
Q->Head = Q->Tail = 0;
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->Tail += 1;
return QUEUE_FULL_FLAG;
}
Q->Tail += 1;
return QUEUE_OK;
}
int Q_Get(UCHAR *pOld, QUEUE_DESC *Q)
{
memcpy(pOld, Q->pBuffer + (Q->Head * Q->EleBytes), Q->EleBytes);
Q->Head += 1;
if(Q->Head == Q->Tail) {
return QUEUE_EMPTY_FLAG;
}
return QUEUE_OK;
}
int _tmain(int argc, _TCHAR* argv[]) {
int LoopKnt = 1000000;
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++) {
for(i=1; i<= Q->EleKnt; i++) {
Qview = i*i;
if(QUEUE_FULL_FLAG == Q_Put(Q, (UCHAR *)&Qview)) {
break;
}
}
Qview = 0;
for(i=1; i; i++) {
if(QUEUE_EMPTY_FLAG == Q_Get((UCHAR *)&Qview, Q)) {
break;
}
}
}
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;
}