ฉันเรียนรู้อย่างหนักกับสตริงที่อยู่ภายในซึ่ง Basile แนะนำซึ่งการค้นหาสตริงแปลเป็นดัชนีแบบ 32 บิตเพื่อจัดเก็บและเปรียบเทียบ สิ่งนี้มีประโยชน์ในกรณีของฉันเนื่องจากบางครั้งฉันมีชิ้นส่วนนับแสนถึงล้านชิ้นที่มีคุณสมบัติชื่อ "x" เช่นซึ่งยังคงต้องเป็นชื่อสตริงที่ใช้งานง่ายเนื่องจากผู้ใช้สามารถเข้าถึงได้โดย scripters ตามชื่อ
ฉันใช้ trie สำหรับการค้นหา (ทดลองด้วยunordered_map
แต่ trie ที่ปรับแล้วของฉันสำรองไว้โดยพูลหน่วยความจำอย่างน้อยก็เริ่มทำงานได้ดีขึ้นและยังง่ายต่อการทำเธรดที่ปลอดภัยโดยไม่ต้องล็อคทุกครั้งที่เข้าถึงโครงสร้าง) std::string
ได้อย่างรวดเร็วสำหรับการก่อสร้างการสร้าง ประเด็นคือเพื่อเพิ่มความเร็วในการดำเนินการที่ตามมาเช่นการตรวจสอบความเท่าเทียมกันของสตริงซึ่งในกรณีของฉันเพียงแค่เดือดลงไปที่การตรวจสอบจำนวนเต็มสองจำนวนเพื่อความเท่าเทียมกันและเพื่อลดการใช้หน่วยความจำอย่างมาก
ฉันเดาว่าตัวเลือกหนึ่งจะรักษารีจิสทรีของค่าที่จัดสรรไว้บางส่วน แต่เป็นไปได้หรือไม่ที่จะทำให้การค้นหารีจิสทรีเร็วขึ้นกว่าการจัดสรรหน่วยความจำซ้ำซ้อน
นั่นจะเป็นเรื่องยากที่จะทำการค้นหาผ่านโครงสร้างข้อมูลได้เร็วกว่าการค้นหาเพียงครั้งเดียว malloc
เช่นหากคุณมีกรณีที่คุณกำลังอ่านจำนวนเรือของสตริงจากอินพุตภายนอกเช่นไฟล์จากนั้นสิ่งล่อใจของฉันจะใช้ตัวจัดสรรแบบลำดับหากเป็นไปได้ ที่มาพร้อมกับข้อเสียที่คุณไม่สามารถเพิ่มหน่วยความจำของแต่ละสายได้ หน่วยความจำทั้งหมดที่รวบรวมโดยตัวจัดสรรจะต้องเป็นอิสระในครั้งเดียวหรือไม่เลย แต่ตัวจัดสรรแบบต่อเนื่องอาจมีประโยชน์ในกรณีที่คุณเพียงแค่ต้องจัดสรรจำนวนหน่วยความจำขนาดเล็กในรูปแบบเรียงลำดับแบบต่อเนื่องเพียงเพื่อที่จะโยนมันออกไปในภายหลัง ฉันไม่ทราบว่ามีการใช้งานในกรณีของคุณหรือไม่ แต่ถ้าเป็นไปได้อาจเป็นวิธีที่ง่ายในการแก้ไขฮอตสปอตที่เกี่ยวข้องกับการจัดสรรหน่วยความจำเล็ก ๆ บ่อย ๆ (ซึ่งอาจมีส่วนเกี่ยวข้องกับการคิดถึงแคช อัลกอริทึมที่ใช้โดย, พูด, malloc
)
การจัดสรรที่มีขนาดคงที่นั้นทำได้ง่ายขึ้นโดยไม่มีข้อ จำกัด ของตัวจัดสรรแบบลำดับที่ป้องกันไม่ให้คุณเพิ่มหน่วยความจำที่เฉพาะเจาะจงเพื่อนำมาใช้ซ้ำในภายหลัง แต่การจัดสรรขนาดแบบผันแปรเร็วกว่าตัวจัดสรรเริ่มต้นค่อนข้างยาก โดยพื้นฐานแล้วการจัดสรรหน่วยความจำชนิดใดที่เร็วกว่าmalloc
โดยทั่วไปจะยากมากหากคุณไม่ใช้ข้อ จำกัด ที่ จำกัด การบังคับใช้ให้แคบลง ทางออกหนึ่งคือการใช้ตัวจัดสรรขนาดคงที่สำหรับพูดสตริงทั้งหมดที่มีขนาด 8 ไบต์หรือน้อยกว่าหากคุณมี boatload ของพวกเขาและสตริงที่ยาวกว่านั้นเป็นกรณีที่หายาก (ซึ่งคุณสามารถใช้ตัวจัดสรรเริ่มต้น) นั่นหมายความว่า 7 ไบต์จะสูญเปล่าสำหรับสตริง 1 ไบต์ แต่ควรกำจัดฮอตสปอตที่เกี่ยวข้องกับการจัดสรรถ้าพูด 95% ของเวลาสตริงของคุณสั้นมาก
อีกวิธีหนึ่งที่เพิ่งเกิดขึ้นกับฉันคือการใช้รายการลิงก์ที่ไม่ได้ควบคุมซึ่งอาจฟังดูบ้า แต่ได้ยินฉัน
แนวคิดในที่นี้คือการทำให้แต่ละโหนดที่ไม่ถูกควบคุมเป็นขนาดคงที่แทนที่จะเป็นขนาดตัวแปร เมื่อคุณทำเช่นนั้นคุณสามารถใช้ตัวจัดสรรก้อนขนาดคงที่ที่รวดเร็วซึ่งหน่วยความจำพูลการจัดสรรชิ้นขนาดคงที่สำหรับสตริงขนาดผันแปรที่เชื่อมโยงเข้าด้วยกัน ที่จะไม่ลดการใช้หน่วยความจำก็จะมีแนวโน้มที่จะเพิ่มเพราะค่าใช้จ่ายของการเชื่อมโยง แต่คุณสามารถเล่นกับขนาดที่ไม่ได้ควบคุมเพื่อหาสมดุลที่เหมาะสมกับความต้องการของคุณ เป็นแนวคิดที่แปลกประหลาด แต่ควรกำจัดฮอตสปอตที่เกี่ยวข้องกับหน่วยความจำเนื่องจากตอนนี้คุณสามารถจัดสรรหน่วยความจำได้อย่างมีประสิทธิภาพแล้วในบล็อกที่อยู่ติดกันขนาดใหญ่และยังคงมีประโยชน์ในการเพิ่มสตริงทีละรายการ นี่คือตัวจัดสรรค่าคงที่ ol ที่เรียบง่ายที่ฉันเขียน (ตัวอย่างที่ฉันทำเพื่อคนอื่นไม่มีการผลิตปุยที่เกี่ยวข้องกับการผลิต) ซึ่งคุณสามารถใช้ได้อย่างอิสระ:
#ifndef FIXED_ALLOCATOR_HPP
#define FIXED_ALLOCATOR_HPP
class FixedAllocator
{
public:
/// Creates a fixed allocator with the specified type and block size.
explicit FixedAllocator(int type_size, int block_size = 2048);
/// Destroys the allocator.
~FixedAllocator();
/// @return A pointer to a newly allocated chunk.
void* allocate();
/// Frees the specified chunk.
void deallocate(void* mem);
private:
struct Block;
struct FreeElement;
FreeElement* free_element;
Block* head;
int type_size;
int num_block_elements;
};
#endif
#include "FixedAllocator.hpp"
#include <cstdlib>
struct FixedAllocator::FreeElement
{
FreeElement* next_element;
};
struct FixedAllocator::Block
{
Block* next;
char* mem;
};
FixedAllocator::FixedAllocator(int type_size, int block_size): free_element(0), head(0)
{
type_size = type_size > sizeof(FreeElement) ? type_size: sizeof(FreeElement);
num_block_elements = block_size / type_size;
if (num_block_elements == 0)
num_block_elements = 1;
}
FixedAllocator::~FixedAllocator()
{
// Free each block in the list, popping a block until the stack is empty.
while (head)
{
Block* block = head;
head = head->next;
free(block->mem);
free(block);
}
free_element = 0;
}
void* FixedAllocator::allocate()
{
// Common case: just pop free element and return.
if (free_element)
{
void* mem = free_element;
free_element = free_element->next_element;
return mem;
}
// Rare case when we're out of free elements.
// Create new block.
Block* new_block = static_cast<Block*>(malloc(sizeof(Block)));
new_block->mem = malloc(type_size * num_block_elements);
new_block->next = head;
head = new_block;
// Push all but one of the new block's elements to the free stack.
char* mem = new_block->mem;
for (int j=1; j < num_block_elements; ++j)
{
void* ptr = mem + j*type_size;
FreeElement* element = static_cast<FreeElement*>(ptr);
element->next_element = free_element;
free_element = element;
}
return mem;
}
void FixedAllocator::deallocate(void* mem)
{
// Just push a free element to the stack.
FreeElement* element = static_cast<FreeElement*>(mem);
element->next_element = free_element;
free_element = element;
}