Modern C ++ 'a anahtar / değer deposu geliştirme


9

Cassandra'ya benzer bir veritabanı sunucusu geliştiriyorum.

C'de gelişme başladı, ancak sınıflar olmadan işler çok karmaşık hale geldi.

Şu anda her şeyi C ++ 11'de taşıdım, ancak hala "modern" C ++ öğreniyorum ve birçok şey hakkında şüphem var.

Veritabanı Anahtar / Değer çiftleriyle çalışacaktır. Her çiftin daha fazla bilgisi vardır - ne zaman sona ereceği de oluşturulur (süresi dolmazsa 0). Her Çifti değişmezdir.

Anahtar C string, Value void *, ama en azından C string olarak değerle çalıştığım an için.

Soyut bir IListsınıf var. Üç sınıftan miras alınır

  • VectorList - C dinamik dizi - std :: vector'a benzer, ancak kullanır realloc
  • LinkList - kontroller ve performans karşılaştırması için yapılmıştır
  • SkipList - sonunda kullanılacak olan sınıf.

Gelecekte ben de Red Blackağaç yapabilirim .

Her biri IList, anahtarlara göre sıralanmış olarak sıfır veya daha fazla çiftçi işaretçisi içerir .

IListÇok uzun olursa , diske özel bir dosyaya kaydedilebilir. Bu özel dosya bir çeşittir read only list.

Bir anahtar aramanız gerekiyorsa,

  • bellekteki ilk IListarama (edilir SkipList, SkipListya da LinkList).
  • Daha sonra arama tarihe göre sıralanmış dosyalara gönderilir
    (önce en yeni dosya, en eski dosya - son).
    Tüm bu dosyalar bellekte eşlenir.
  • Hiçbir şey bulunamazsa, anahtar bulunamadı.

IListİşlerin uygulanması konusunda hiçbir kuşkum yok .


Şu anda beni şaşırtan şey şu:

Çiftler farklı büyüklüktedir, onlar tarafından tahsis edilir new()ve onlara std::shared_ptrişaret etmişlerdir.

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];
};

"buffer" üye değişkeni farklı boyuta sahip olan değişkendir. Anahtar + değerini depolar.
Örneğin, anahtar 10 karakterse ve değer başka bir 10 bayt ise, tüm nesne olacaktır sizeof(Pair::Blob) + 20(arabellek iki boş sonlandırma baytı nedeniyle 2 başlangıç ​​boyutuna sahiptir)

Aynı düzen diskte de kullanılır, bu yüzden böyle bir şey yapabilirim:

// 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);

Ancak bu farklı boyut, C ++ kodu olan birçok yerde bir sorundur.

Mesela kullanamıyorum std::make_shared(). Bu benim için önemlidir, çünkü 1M Çiftlerim varsa 2M tahsislerim olurdu.

Diğer taraftan, dinamik diziye "tampon" yaparsam (örn. Yeni karakter [123]), mmap "hüner" i kaybederim, anahtarı kontrol etmek istersem iki dereferences yapacağım ve tek bir işaretçi ekleyeceğim - Sınıfa 8 bayt.

Ben de , sadece tampon olmak için , tüm üyeleri Pair::Blobiçine "çekmeye" çalıştı , ama ben test zaman, muhtemelen nesne verileri etrafında kopyalama nedeniyle, oldukça yavaştı.PairPair::Blob

Ben de düşünüyorum başka bir değişiklik Pairsınıf kaldırmak ve yerine std::shared_ptrve tüm yöntemleri geri "itmek" Pair::Blob, ama bu bana değişken boyut Pair::Blobsınıf ile yardımcı olmaz .

Daha fazla C ++ dostu olmak için nesne tasarımını nasıl geliştirebileceğimi merak ediyorum.


Tam kaynak kodu burada:
https://github.com/nmmmnu/HM3


2
Neden kullanmıyorsun std::mapya da std::unordered_map? Değerler neden (anahtarlarla ilişkili) bazıları void*? Muhtemelen bir noktada onları yok etmeniz gerekir; nasıl, ne zaman? Neden şablon kullanmıyorsunuz?
Basile Starynkevitch

Geçerli durum için std :: map'den daha iyi bir şey yapmaya inandığım (veya en azından denediğim) için std :: map kullanmıyorum. Ama evet bir noktada std :: map'i sarmayı ve IList olarak performansını kontrol etmeyi düşünüyorum.
Nick

Yer değiştirme ve d-tor'ları çağırma, elemanın olduğu yerde IList::removeveya IList tahrip edildiğinde yapılır . Çok zaman alıyor, ama ayrı bir iş parçacığında yapacağım. Kolay olacak çünkü IList std::unique_ptr<IList>yine de olacak . bu yüzden yeni listeyle "değiştirebiliyorum" ve eski nesneyi d-tor diyebileceğim bir yerde tutabileceğim.
Nick

Şablonları denedim. Burada en iyi çözüm değildir, çünkü bu kullanıcı kütüphanesi değildir, anahtar her zaman C stringve veriler her zaman bir arabellektir void *veya char *char dizisini geçebilirsiniz. Sen benzer bulabilirsiniz redisya memcached. Bir noktada std::stringanahtar için char dizisi kullanmaya veya sabit diziye karar verebildim , ancak altını çizmek hala C string olacak.
Nick

6
4 yorum eklemek yerine, sorunuzu düzenlemelisiniz
Basile Starynkevitch

Yanıtlar:


3

Önerebileceğim yaklaşım, mümkün olduğunca temiz ve olabildiğince kısıtlayıcı olmayan anahtar değer mağazanızın arayüzüne odaklanmaktır; nasıl uygulanır.

Ardından, herhangi bir performans kaygısı olmadan mümkün olduğunca çıplak ve mümkün olduğunca temiz bir uygulama sağlamanızı öneririm. Bana göre unordered_mapilk tercihiniz gibi görünüyor olabilir, ya da belki mapbir çeşit anahtar sıralaması arabirim tarafından ortaya çıkarılmalıdır.

Yani, önce temiz ve minimal bir şekilde çalışmasını sağlayın; daha sonra gerçek bir uygulamada kullanın; bunu yaparken, arayüzde hangi sorunları ele almanız gerektiğini bulacaksınız; sonra devam edin ve onlara hitap edin. Çoğu olasılık, arayüzün değiştirilmesinin bir sonucu olarak, uygulamanın büyük bölümlerini yeniden yazmanız gerekeceğinden, uygulamanın ilk yinelemesine daha önce yatırım yapmak için gereken minimum sürenin ötesinde yatırım yaptığınız her zaman zar zor iş zaman kaybıdır.

Ardından, profili değiştirin ve arabirimi değiştirmeden uygulamada nelerin iyileştirilmesi gerektiğini görün. Ya da profil oluşturmadan önce uygulamanın nasıl geliştirileceği hakkında kendi fikirleriniz olabilir. Bu iyi, ama bu fikirler üzerinde daha erken bir zamanda çalışmak için hala bir neden yok.

Bundan daha iyisini yapmayı umduğunu söylüyorsun map; bunun hakkında söylenebilecek iki şey vardır:

a) muhtemelen yapmayacaksınız;

b) ne pahasına olursa olsun erken optimizasyondan kaçının.

Uygulama ile ilgili olarak, ana sorununuz bellek ayırma gibi görünüyor, çünkü bellek ayırma ile ilgili olacağını tahmin ettiğiniz problemleri çözmek için tasarımınızı nasıl yapılandıracağınızla ilgili gibi görünüyorsunuz. C ++ 'da bellek ayırma kaygılarını gidermenin en iyi yolu, etrafındaki tasarımı bükerek ve bükerek değil, uygun bir bellek ayırma yönetimi uygulamaktır. Java ve C # gibi dillerin aksine, dil çalışma zamanının sunduğu şeylerle neredeyse sıkışmış olduğunuz kendi bellek ayırma yönetiminizi yapmanıza izin veren C ++ kullandığınız için kendinizi şanslı saymalısınız.

C ++ 'da bellek yönetimine geçmenin çeşitli yolları vardır ve newoperatörün aşırı yüklenmesi yeteneği kullanışlı olabilir. Projeniz için basit bir bellek ayırıcı, çok sayıda baytın yerini değiştirir ve bunu yığın olarak kullanır. ( byte* heap.) firstFreeByteÖbekteki ilk boş baytı gösteren, sıfıra sıfırlanmış bir dizininiz olur. Talebi olduğunda Nbayt gelir, sen adresi döndürebilir heap + firstFreeByteve eklemek Niçin firstFreeByte. Böylece, bellek ayırma o kadar hızlı ve verimli olur ki neredeyse hiç sorun olmaz.

Tabii ki, tüm belleğinizin önceden konumlandırılması iyi bir fikir olmayabilir, bu nedenle yığınınızı talep üzerine tahsis edilen bankalara bölmeniz ve herhangi bir anda en yeni bankadan tahsis talepleri sunmaya devam etmeniz gerekebilir.

Verileriniz değişmez olduğu için bu iyi bir çözümdür. Değişken uzunluktaki nesneler fikrinden vazgeçmenize ve her birinin Pairverisine gerektiği gibi bir işaretçi içermesine izin verir , çünkü veriler için fazladan bellek ayırmanın neredeyse hiçbir maliyeti yoktur.

Nesneleri bellekten geri alabilmek için öbekten atmak istiyorsanız, işler daha karmaşık hale gelir: her zaman işaretçileri değil, işaretçileri kullanmanız gerekir, böylece nesneleri her zaman taşıyabilirsiniz silinen nesnelerin alanını geri kazanmak için yığınlar etrafında. Ekstra dolaylama nedeniyle her şey biraz yavaşlar, ancak standart çalışma zamanı kitaplığı bellek ayırma rutinlerini kullanmaya kıyasla her şey hala hızlıdır.

Ancak, veritabanınızın basit, çıplak-minimal çalışan bir sürümünü oluşturmaz ve gerçek bir uygulamada kullanmaya başlarsanız, tüm bunlar elbette endişe duymak için gerçekten işe yaramaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.