การพัฒนาที่เก็บคีย์ / ค่าที่ย้ายไปยัง C ++ ที่ทันสมัย


9

ฉันกำลังพัฒนาเซิร์ฟเวอร์ฐานข้อมูลที่คล้ายกับ Cassandra

การพัฒนาเริ่มต้นใน C แต่สิ่งต่าง ๆ ก็ซับซ้อนมากโดยไม่ต้องเรียน

ขณะนี้ฉันได้ย้ายทุกอย่างใน C ++ 11 แต่ฉันยังคงเรียนรู้ C ++ ที่ทันสมัยและยังมีข้อสงสัยมากมาย

ฐานข้อมูลจะทำงานกับคู่ของคีย์ / ค่า ทุกคู่มีข้อมูลเพิ่มเติม - เมื่อถูกสร้างขึ้นด้วยเมื่อมันจะหมดอายุ (0 ถ้าไม่หมดอายุ) แต่ละคู่นั้นไม่เปลี่ยนรูป

Key คือสตริง C ค่าเป็นโมฆะ * แต่อย่างน้อยตอนนี้ฉันทำงานด้วยค่าเป็นสตริง C เช่นกัน

มีIListระดับนามธรรม มันสืบทอดมาจากสามชั้น

  • VectorList - C dynamic array - คล้ายกับ std :: vector แต่ใช้ realloc
  • LinkList - ทำเพื่อการตรวจสอบและเปรียบเทียบประสิทธิภาพ
  • SkipList - คลาสที่ใช้ในที่สุด

ในอนาคตฉันอาจทำRed Blackต้นไม้ด้วย

แต่ละคนIListมีศูนย์หรือมากกว่าตัวชี้ไปยังคู่เรียงตามคีย์

หากIListยาวเกินไปก็สามารถบันทึกลงดิสก์ในไฟล์พิเศษได้ read only listแฟ้มพิเศษนี้เป็นชนิดของ

หากคุณต้องการค้นหากุญแจ

  • ครั้งแรกในความทรงจำIListจะค้นหา ( SkipList, SkipListหรือLinkList)
  • จากนั้นการค้นหาจะถูกส่งไปยังไฟล์ที่เรียงลำดับตามวันที่
    (ไฟล์ใหม่สุดก่อน, ไฟล์ที่เก่าที่สุด - สุดท้าย)
    ไฟล์ทั้งหมดเหล่านี้เป็น mmap-ed ในหน่วยความจำ
  • หากไม่พบสิ่งใดแสดงว่าไม่พบรหัส

ฉันไม่มีข้อสงสัยเกี่ยวกับการนำIListสิ่งต่าง ๆ มาใช้


สิ่งที่ทำให้ฉันสับสนคือกำลังติดตาม:

ทั้งคู่มีขนาดแตกต่างกันพวกเขาจัดสรรโดยnew()และพวกเขาstd::shared_ptrชี้ไปที่พวกเขา

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" เป็นตัวแปรที่มีขนาดต่างกัน มันเก็บคีย์ + ค่า
เช่นถ้าคีย์คือ 10 อักขระและค่าเป็น 10 ไบต์อีกทั้งวัตถุจะเป็นsizeof(Pair::Blob) + 20(บัฟเฟอร์มีขนาดเริ่มต้นเท่ากับ 2 เนื่องจากมีสองไบต์ที่สิ้นสุดด้วยค่า null)

เลย์เอาต์เดียวกันนี้ใช้กับดิสก์ด้วยดังนั้นฉันสามารถทำสิ่งนี้:

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

อย่างไรก็ตามขนาดที่แตกต่างกันนี้เป็นปัญหาในหลาย ๆ ที่ด้วยรหัส C ++

std::make_shared()ตัวอย่างเช่นฉันไม่สามารถใช้ สิ่งนี้สำคัญสำหรับฉันเพราะถ้าฉันมี 1M Pairs ฉันจะมีการจัดสรร 2M

จากอีกด้านหนึ่งถ้าฉันทำ "บัฟเฟอร์" เป็นอาร์เรย์แบบไดนามิก (เช่นอักขระใหม่ [123]) ฉันจะสูญเสีย "mmap" เคล็ดลับ "ฉันจะทำสอง dereferences ถ้าฉันต้องการตรวจสอบคีย์และฉันจะเพิ่มตัวชี้เดียว - 8 ไบต์ถึงคลาส

ฉันยังพยายามที่จะ "ดึง" สมาชิกทั้งหมดPair::Blobเข้ามาPairเพื่อPair::Blobเป็นเพียงบัฟเฟอร์ แต่เมื่อฉันทดสอบมันช้ามากอาจเป็นเพราะการคัดลอกข้อมูลวัตถุรอบ ๆ

การเปลี่ยนแปลงอีกอย่างที่ฉันคิดคือการลบPairคลาสและแทนที่ด้วยstd::shared_ptrและเพื่อ "ดัน" วิธีการทั้งหมดกลับไปPair::Blobแต่สิ่งนี้จะไม่ช่วยฉันในPair::Blobคลาสขนาดที่ผันแปรได้

ฉันสงสัยว่าฉันจะปรับปรุงการออกแบบวัตถุให้เป็นมิตรกับ C ++ ได้อย่างไร


ซอร์สโค้ดแบบเต็มอยู่ที่นี่:
https://github.com/nmmmnu/HM3


2
ทำไมคุณไม่ใช้std::mapหรือstd::unordered_map? ค่าทำไม (ที่เกี่ยวข้องกับคีย์) บางvoid*? คุณอาจจะต้องทำลายพวกเขาในบางจุด; อย่างไรและเมื่อไหร่? ทำไมคุณไม่ใช้เทมเพลต?
Basile Starynkevitch

ฉันไม่ได้ใช้ std :: map เพราะฉันเชื่อ (หรืออย่างน้อยลอง) ทำสิ่งที่ดีกว่า std :: map สำหรับกรณีปัจจุบัน แต่ใช่ฉันกำลังคิดที่จะห่อ std :: map และตรวจสอบประสิทธิภาพเป็น IList ด้วยเช่นกัน
Nick

การยกเลิกการจัดสรรและการเรียก d-tors เสร็จสิ้นเมื่ออิลิเมนต์คือIList::removeหรือเมื่อ IList ถูกทำลาย ใช้เวลานาน แต่ฉันจะทำในหัวข้อแยก มันจะง่ายเพราะ IList จะเป็นstd::unique_ptr<IList>เช่นนั้นต่อไป ดังนั้นฉันจะสามารถ "สลับ" ด้วยรายการใหม่และเก็บวัตถุเก่าไว้ที่ไหนสักแห่งที่ฉันสามารถเรียก d-tor
นิค

ฉันลองแม่แบบ นี่ไม่ใช่ทางออกที่ดีที่สุดเพราะนี่ไม่ใช่ห้องสมุดผู้ใช้ที่สำคัญคือเสมอC stringและข้อมูลมักจะเป็นบัฟเฟอร์void *หรือchar *ดังนั้นคุณสามารถส่งผ่านอาร์เรย์ถ่าน คุณสามารถค้นหาที่คล้ายกันในหรือredis memcachedในบางจุดฉันสามารถตัดสินใจที่จะใช้std::stringหรือแก้ไข char char สำหรับคีย์ แต่ขีดเส้นใต้มันจะยังคงเป็นสตริง C
Nick

6
แทนที่จะเพิ่มความคิดเห็น 4 ข้อคุณควรแก้ไขคำถามของคุณ
Basile Starynkevitch

คำตอบ:


3

วิธีที่ฉันแนะนำคือมุ่งเน้นไปที่อินเทอร์เฟซของที่เก็บคีย์ - ค่าของคุณเพื่อทำให้มันสะอาดที่สุดเท่าที่จะเป็นไปได้และไม่ จำกัด ให้มากที่สุดหมายความว่ามันควรอนุญาตให้ผู้โทรมีอิสระสูงสุด วิธีการใช้งาน

จากนั้นฉันจะแนะนำให้คุณให้ข้อมูลที่เปลือยเปล่าเท่าที่จะเป็นไปได้และสะอาดที่สุดเท่าที่จะทำได้โดยไม่ต้องคำนึงถึงประสิทธิภาพใด ๆ สำหรับฉันดูเหมือนว่าคุณunordered_mapควรจะเป็นตัวเลือกแรกของคุณหรือบางทีmapถ้าการสั่งซื้อบางอย่างของแป้นต้องถูกเปิดเผยโดยอินเตอร์เฟส

ดังนั้นก่อนอื่นมาทำความสะอาดให้น้อยที่สุด; จากนั้นนำไปใช้ในแอปพลิเคชันจริง ในการทำเช่นนั้นคุณจะพบปัญหาที่คุณต้องระบุในอินเทอร์เฟซ จากนั้นไปข้างหน้าและจัดการพวกมัน โอกาสส่วนใหญ่เกิดจากการเปลี่ยนอินเทอร์เฟซคุณจะต้องเขียนส่วนใหญ่ของการนำไปใช้ดังนั้นเมื่อใดก็ตามที่คุณลงทุนไปแล้วในการวนซ้ำครั้งแรกของการนำไปใช้งาน งานแทบจะไม่ต้องเสียเวลา

จากนั้นทำโปรไฟล์และดูสิ่งที่จำเป็นต้องได้รับการปรับปรุงในการนำไปใช้โดยไม่ต้องเปลี่ยนส่วนต่อประสาน หรือคุณอาจมีความคิดของคุณเองเกี่ยวกับวิธีปรับปรุงการใช้งานก่อนที่คุณจะโพรไฟล์ ไม่เป็นไร แต่ก็ยังไม่มีเหตุผลที่จะทำงานกับความคิดเหล่านี้ในเวลาก่อนหน้านี้

คุณบอกว่าคุณหวังว่าจะทำดีกว่าmap; มีสองสิ่งที่สามารถพูดได้เกี่ยวกับเรื่องนี้:

a) คุณอาจจะไม่;

b) หลีกเลี่ยงการปรับให้เหมาะสมก่อนเวลาอันควรด้วยค่าใช้จ่ายทั้งหมด

ประเด็นหลักของคุณดูเหมือนจะเป็นการจัดสรรหน่วยความจำเนื่องจากคุณดูเหมือนจะกังวลกับวิธีการออกแบบโครงสร้างของคุณเพื่อที่จะแก้ไขปัญหาที่คุณคาดหวังว่าคุณจะได้รับจากการจัดสรรหน่วยความจำ วิธีที่ดีที่สุดในการจัดการปัญหาการจัดสรรหน่วยความจำใน C ++ คือการใช้การจัดการการจัดสรรหน่วยความจำที่เหมาะสมไม่ใช่โดยการบิดและดัดการออกแบบรอบ ๆ คุณควรถือว่าตัวเองโชคดีที่คุณใช้ C ++ ซึ่งช่วยให้คุณสามารถจัดการการจัดสรรหน่วยความจำของคุณเองได้ซึ่งแตกต่างจากภาษาอย่าง Java และ C # ที่คุณติดอยู่กับสิ่งที่รันไทม์ของภาษา

มีหลายวิธีในการจัดการหน่วยความจำใน C ++ และความสามารถในการโอเวอร์โหลดตัวnewดำเนินการอาจมีประโยชน์ ตัวจัดสรรหน่วยความจำแบบง่ายสำหรับโครงการของคุณจะจัดสรรล่วงหน้าเป็นจำนวนมากและใช้เป็นฮีป ( byte* heap.) คุณจะมีfirstFreeByteดัชนีเริ่มต้นเป็นศูนย์ซึ่งระบุไบต์ฟรีครั้งแรกในฮีป เมื่อมีการร้องขอNไบต์มาคุณจะส่งคืนที่อยู่heap + firstFreeByteและเพิ่มNให้กับfirstFreeByteคุณ ดังนั้นการจัดสรรหน่วยความจำจะเร็วและมีประสิทธิภาพจนแทบไม่มีปัญหา

แน่นอนว่าการจัดสรรล่วงหน้าหน่วยความจำทั้งหมดของคุณอาจไม่ใช่ความคิดที่ดีดังนั้นคุณอาจต้องแบ่งฮีปของคุณเป็นธนาคารที่จัดสรรตามความต้องการและให้บริการคำขอการจัดสรรจากธนาคารที่ใหม่ที่สุด

เนื่องจากข้อมูลของคุณเปลี่ยนแปลงไม่ได้นี่เป็นทางออกที่ดี จะช่วยให้คุณละทิ้งความคิดของวัตถุที่มีความยาวผันแปรได้และให้แต่ละPairตัวมีตัวชี้ไปยังข้อมูลตามที่ควรเนื่องจากการจัดสรรหน่วยความจำเพิ่มเติมสำหรับข้อมูลไม่มีค่าใช้จ่าย

หากคุณต้องการที่จะทิ้งวัตถุจากกองเพื่อที่จะสามารถเรียกคืนหน่วยความจำของพวกเขาแล้วสิ่งที่ซับซ้อนมากขึ้น: คุณจะต้องใช้ไม่ได้เป็นพอยน์เตอร์ แต่ตัวชี้ไปยังพอยน์เตอร์เพื่อให้คุณสามารถย้ายวัตถุได้ตลอดเวลา รอบในกองเพื่อเรียกคืนพื้นที่ของวัตถุที่ถูกลบ ทุกอย่างช้าลงเล็กน้อยเนื่องจากมีการเปลี่ยนทิศทางเป็นพิเศษ แต่ทุกอย่างยังคงเร็วเมื่อเทียบกับการใช้รูทีนการจัดสรรหน่วยความจำไลบรารีมาตรฐานแบบรันไทม์

แต่ทั้งหมดนี้แน่นอนว่าไม่มีประโยชน์ที่จะต้องกังวลหากคุณไม่ได้สร้างฐานข้อมูลเวอร์ชั่นที่ใช้งานง่ายและไม่ซับซ้อนและนำไปใช้ในแอพพลิเคชันจริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.