Sto sviluppando un server di database simile a Cassandra.
Lo sviluppo è iniziato in C, ma le cose sono diventate molto complicate senza classi.
Attualmente ho portato tutto in C ++ 11, ma sto ancora imparando il C ++ "moderno" e dubito di molte cose.
Il database funzionerà con coppie chiave / valore. Ogni coppia ha qualche informazione in più - quando viene creata anche quando scadrà (0 se non scade). Ogni coppia è immutabile.
La chiave è una stringa C, Value is void *, ma almeno per il momento sto operando anche con il valore come stringa C.
Ci sono classi IList
astratte. È ereditato da tre classi
-
VectorList
- Array dinamico C - simile a std :: vector, ma utilizzarealloc
-
LinkList
- creato per il confronto tra controlli e prestazioni -
SkipList
: la classe che verrà infine utilizzata.
In futuro potrei fare anche Red Black
albero.
Ogni IList
contiene zero o più puntatori a coppie, ordinati per chiave.
Se IList
è diventato troppo lungo, può essere salvato sul disco in un file speciale. Questo file speciale è di tipo read only list
.
Se hai bisogno di cercare un tasto,
- prima viene cercata la memoria
IList
(SkipList
,SkipList
oLinkList
). - Quindi la ricerca viene inviata ai file ordinati per data
(file più nuovo prima, file più vecchio - ultimo).
Tutti questi file sono salvati in memoria. - Se non viene trovato nulla, la chiave non viene trovata.
Non ho dubbi sull'implementazione di IList
cose.
Quello che attualmente mi sta sconcertando è il seguente:
Le coppie hanno dimensioni diverse , sono allocate da new()
e hanno std::shared_ptr
puntate su di esse.
class Pair{
public:
// several methods...
private:
struct Blob;
std::shared_ptr<const Blob> _blob;
};
struct Pair::Blob{
uint64_t created;
uint32_t expires;
uint32_t vallen;
uint16_t keylen;
uint8_t checksum;
char buffer[2];
};
La variabile membro "buffer" è quella con dimensioni diverse. Memorizza il valore chiave +.
Per esempio. se la chiave è di 10 caratteri e il valore è di altri 10 byte, l'intero oggetto sarà sizeof(Pair::Blob) + 20
(il buffer ha una dimensione iniziale di 2, a causa di due byte di terminazione null)
Questo stesso layout è usato anche sul disco, quindi posso fare qualcosa del genere:
// get the blob
Pair::Blob *blob = (Pair::Blob *) & mmaped_array[pos];
// create the pair, true makes std::shared_ptr not to delete the memory,
// since it does not own it.
Pair p = Pair(blob, true);
// however if I want the Pair to own the memory,
// I can copy it, but this is slower operation.
Pair p2 = Pair(blob);
Tuttavia questa diversa dimensione è un problema in molti posti con codice C ++.
Ad esempio, non posso usare std::make_shared()
. Questo è importante per me, perché se ho coppie 1M, avrei allocazioni 2M.
Dall'altro lato, se faccio "buffer" all'array dinamico (es. nuovo char [123]), perderò mmap "trucco", avrò due dereferenze se voglio controllare la chiave e lo farò aggiungi un puntatore singolo - 8 byte alla classe.
Ho anche provato a "tirare" tutti i membri da Pair::Blob
a Pair
, quindi Pair::Blob
è solo il buffer, ma quando l'ho testato, era piuttosto lento, probabilmente a causa della copia dei dati dell'oggetto intorno .
Un altro cambiamento che sto pensando è di rimuovere Pair
class e sostituirlo con std::shared_ptr
e di "spingere" tutti i metodi nuovamente a Pair::Blob
, ma questo non mi aiuterà con la classe di% var% di% della variabile.
Mi chiedo come posso migliorare il design degli oggetti per essere più amichevole con il C ++.
Il codice sorgente completo è qui:
link