ความหมายของคำย่อ SSO ในบริบทของ std :: string


155

ในc ++ คำถามเกี่ยวกับการเพิ่มประสิทธิภาพและรูปแบบรหัสหลายคำตอบที่เรียกว่า "สปส." std::stringในบริบทของการเพิ่มประสิทธิภาพสำเนาของ SSO หมายถึงอะไรในบริบทนั้น

เห็นได้ชัดว่าไม่ใช่ "การลงชื่อเข้าใช้ครั้งเดียว" "การเพิ่มประสิทธิภาพสตริงที่ใช้ร่วมกัน" อาจจะ?


57
นั่นเป็นเพียงการซ้ำซ้อนในลักษณะเดียวกับที่ "อะไรคือ 2 + 2" นั้นซ้ำกันของ "ผลลัพธ์ของ 200/50" คืออะไร คำตอบเหมือนกัน คำถามนั้นแตกต่างอย่างสิ้นเชิง "ปิดเหมือนกัน" มีวัตถุประสงค์เพื่อใช้เมื่อมีคนถามคำถามเดียวกัน เมื่อคนคนหนึ่งถามว่า "วิธีการที่จะstd::stringดำเนินการ" และอีกถามว่า "สิ่งที่ไม่ SSO หมายความว่า" คุณจะต้องเป็นอย่างบ้าที่จะต้องพิจารณาพวกเขาจะเหมือนกันคำถาม
jalf

1
@jalf: หากมี Q + A อยู่แล้วซึ่งครอบคลุมขอบเขตของคำถามนี้ฉันคิดว่ามันซ้ำกัน (ฉันไม่ได้บอกว่า OP ควรค้นหาด้วยตัวเอง แต่เพียงคำตอบใด ๆ ที่นี่จะครอบคลุมพื้นดินที่ ได้รับการคุ้มครองแล้ว)
Oliver Charlesworth

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

7
@jalf: ไม่เลย IMO "การลงคะแนนเพื่อปิด" ไม่ได้หมายความถึง "คำถามที่ไม่ดี" ฉันใช้ downvote เพื่อสิ่งนั้น ฉันคิดว่ามันซ้ำซ้อนในแง่ที่ว่าคำถามมากมาย (i = i ++ ฯลฯ ) ซึ่งคำตอบคือ "พฤติกรรมที่ไม่ได้กำหนด" นั้นซ้ำซ้อนกัน ในบันทึกอื่นทำไมไม่มีใครตอบคำถามหากไม่ซ้ำกัน
Oliver Charlesworth

5
@jalf: ฉันเห็นด้วยกับ Oli คำถามไม่ซ้ำกัน แต่คำตอบจะเป็นดังนั้นจึงเปลี่ยนเส้นทางไปยังคำถามอื่นที่ดูเหมือนคำตอบที่เหมาะสมอยู่แล้ว คำถามที่ปิดเนื่องจากการทำซ้ำไม่ได้หายไปแทนที่จะทำหน้าที่เป็นตัวชี้ไปยังคำถามอื่นที่คำตอบวางอยู่ บุคคลต่อไปที่กำลังมองหา SSO จะสิ้นสุดที่นี่ติดตามการเปลี่ยนเส้นทางและค้นหาคำตอบของเธอ
Matthieu M.

คำตอบ:


213

ความเป็นมา / ภาพรวม

การดำเนินการกับตัวแปรอัตโนมัติ ("จากสแต็ก" ซึ่งเป็นตัวแปรที่คุณสร้างโดยไม่ต้องโทรmalloc/ new) โดยทั่วไปเร็วกว่าที่เกี่ยวข้องกับร้านค้าฟรี ("ฮีป" ซึ่งเป็นตัวแปรที่สร้างขึ้นโดยใช้new) อย่างไรก็ตามขนาดของอาร์เรย์อัตโนมัติจะถูกกำหนดไว้ที่เวลาคอมไพล์ แต่ขนาดของอาร์เรย์จากร้านค้าฟรีนั้นไม่ใช่ ยิ่งไปกว่านั้นขนาดสแต็กมี จำกัด (โดยทั่วไปจะเป็นเพียงไม่กี่ MiB) ในขณะที่ร้านค้าฟรีจะถูก จำกัด ด้วยหน่วยความจำของระบบของคุณเท่านั้น

SSO คือการเพิ่มประสิทธิภาพสตริงสั้น / ขนาดเล็ก std::stringมักจะเก็บสตริงเป็นตัวชี้ไปยังร้านค้าฟรี ( "กอง") new char [size]ซึ่งจะช่วยให้ลักษณะการทำงานที่คล้ายกันเช่นถ้าคุณมีการโทร สิ่งนี้จะป้องกันการโอเวอร์โฟลว์ของสแต็กสำหรับสตริงที่มีขนาดใหญ่มาก แต่อาจช้าลงโดยเฉพาะอย่างยิ่งกับการคัดลอก การเพิ่มประสิทธิภาพการใช้งานหลายสร้างอาร์เรย์อัตโนมัติขนาดเล็กบางอย่างเช่นstd::string char [20]หากคุณมีสตริงที่มี 20 ตัวอักษรหรือเล็กกว่า (ตามตัวอย่างนี้ขนาดจริงจะแตกต่างกันไป) มันจะเก็บมันไว้ในอาร์เรย์นั้นโดยตรง วิธีนี้จะช่วยหลีกเลี่ยงความต้องการในการโทรnewเลยซึ่งจะช่วยเพิ่มความเร็ว

แก้ไข:

ฉันไม่ได้คาดหวังว่าคำตอบนี้จะได้รับความนิยมอย่างมาก แต่เนื่องจากเป็นเช่นนั้นฉันขอให้มีการนำไปใช้จริงมากขึ้นด้วยข้อแม้ที่ฉันไม่เคยอ่านการใช้งาน SSO "ในป่า"

รายละเอียดการใช้งาน

อย่างน้อยที่สุดstd::stringความต้องการในการจัดเก็บข้อมูลต่อไปนี้:

  • ขนาด
  • ความจุ
  • ตำแหน่งของข้อมูล

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

คุณไม่ต้องจ่ายเงินสำหรับสิ่งที่คุณไม่ได้ใช้

ก่อนอื่นให้พิจารณาการนำไปปฏิบัติที่ไร้เดียงสาตามสิ่งที่ฉันระบุไว้ข้างต้น:

class string {
public:
    // all 83 member functions
private:
    std::unique_ptr<char[]> m_data;
    size_type m_size;
    size_type m_capacity;
    std::array<char, 16> m_sso;
};

สำหรับระบบ 64 บิตซึ่งโดยทั่วไปหมายความว่าstd::stringมี 24 ไบต์ของ 'ค่าใช้จ่าย' ต่อสตริงและอีก 16 สำหรับบัฟเฟอร์ SSO (16 เลือกที่นี่แทนที่จะเป็น 20 เนื่องจากข้อกำหนดด้านการขยาย) มันไม่สมเหตุสมผลเลยที่จะจัดเก็บข้อมูลสมาชิกทั้งสามเหล่านั้นรวมทั้งอาเรย์ของตัวละครในท้องถิ่นเช่นในตัวอย่างที่เรียบง่ายของฉัน ถ้าm_size <= 16ฉันจะใส่ข้อมูลทั้งหมดลงm_ssoไปฉันก็รู้แล้วว่าความจุและฉันไม่ต้องการตัวชี้ไปยังข้อมูล หากแล้วฉันไม่จำเป็นต้องm_size > 16 m_ssoไม่มีการทับซ้อนอย่างที่ฉันต้องการทั้งหมด โซลูชันที่ชาญฉลาดกว่าที่ไม่มีที่ว่างจะมีลักษณะคล้ายนี้เล็กน้อย (วัตถุประสงค์ที่ยังไม่ทดลองตัวอย่างเท่านั้น):

class string {
public:
    // all 83 member functions
private:
    size_type m_size;
    union {
        class {
            // This is probably better designed as an array-like class
            std::unique_ptr<char[]> m_data;
            size_type m_capacity;
        } m_large;
        std::array<char, sizeof(m_large)> m_small;
    };
};

ฉันคิดว่าการใช้งานส่วนใหญ่มีลักษณะเช่นนี้


7
นี่คือคำอธิบายที่ดีเกี่ยวกับการใช้งานจริง ๆ : stackoverflow.com/a/28003328/203044
BillT

SSO ใช้งานได้จริงหรือไม่เมื่อนักพัฒนาส่วนใหญ่ใช้ std :: string โดยใช้การอ้างอิง const
Gupta

1
SSO มีประโยชน์สองประการนอกเหนือจากการทำสำเนาที่ถูกกว่า อย่างแรกคือถ้าขนาดสตริงของคุณพอดีกับขนาดบัฟเฟอร์เล็ก ๆ คุณไม่จำเป็นต้องจัดสรรในการสร้างครั้งแรก อย่างที่สองก็คือเมื่อฟังก์ชั่นยอมรับ a std::string const &การรับข้อมูลจะเป็นการอ้อมหน่วยความจำเดียวเนื่องจากข้อมูลถูกเก็บไว้ที่ตำแหน่งของการอ้างอิง หากไม่มีการอ็อพติไมซ์สตริงขนาดเล็กการเข้าถึงข้อมูลจะต้องใช้หน่วยความจำสองทิศทาง (ก่อนอื่นทำการโหลดการอ้างอิงไปยังสตริงและอ่านเนื้อหาจากนั้นวินาทีที่จะอ่านเนื้อหาของตัวชี้ข้อมูลในสตริง)
David Stone

34

SSO เป็นตัวย่อสำหรับ "Small String Optimization" ซึ่งเป็นเทคนิคที่สตริงขนาดเล็กฝังอยู่ในเนื้อหาของคลาสสตริงแทนที่จะใช้บัฟเฟอร์ที่จัดสรรแยกต่างหาก


15

ตามที่ได้อธิบายไว้แล้วโดยคำตอบอื่น ๆ , SSO หมายความว่าขนาดเล็ก / String สั้นเพิ่มประสิทธิภาพ แรงจูงใจเบื้องหลังการปรับให้เหมาะสมนี้เป็นหลักฐานที่ปฏิเสธไม่ได้ว่าแอปพลิเคชันโดยทั่วไปจัดการกับสตริงที่สั้นกว่ามากกว่าสตริงที่ยาวกว่า

ตามที่อธิบายโดยเดวิดสโตนในคำตอบของเขาดังกล่าวข้างต้นที่std::stringชั้นใช้บัฟเฟอร์ภายในเนื้อหาเก็บได้ถึงระยะเวลาที่กำหนดและขจัดนี้จำเป็นที่จะต้องจัดสรรหน่วยความจำแบบไดนามิก นี้จะทำให้รหัสมีประสิทธิภาพมากขึ้นและเร็วขึ้น

คำตอบที่เกี่ยวข้องอื่น ๆ นี้แสดงให้เห็นอย่างชัดเจนว่าขนาดของบัฟเฟอร์ภายในขึ้นอยู่กับการstd::stringใช้งานซึ่งแตกต่างกันไปในแต่ละแพลตฟอร์ม (ดูผลการวัดประสิทธิภาพด้านล่าง)

มาตรฐาน

นี่คือโปรแกรมขนาดเล็กที่ทำหน้าที่คัดลอกการทำงานของสตริงจำนวนมากที่มีความยาวเท่ากัน มันเริ่มพิมพ์เวลาในการคัดลอก 10 ล้านสายที่มีความยาว = 1 จากนั้นจะซ้ำกับสตริงที่มีความยาว = 2 มันจะดำเนินต่อไปจนกระทั่งความยาวเท่ากับ 50

#include <string>
#include <iostream>
#include <vector>
#include <chrono>

static const char CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static const int ARRAY_SIZE = sizeof(CHARS) - 1;

static const int BENCHMARK_SIZE = 10000000;
static const int MAX_STRING_LENGTH = 50;

using time_point = std::chrono::high_resolution_clock::time_point;

void benchmark(std::vector<std::string>& list) {
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

    // force a copy of each string in the loop iteration
    for (const auto s : list) {
        std::cout << s;
    }

    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    std::cerr << list[0].length() << ',' << duration << '\n';
}

void addRandomString(std::vector<std::string>& list, const int length) {
    std::string s(length, 0);
    for (int i = 0; i < length; ++i) {
        s[i] = CHARS[rand() % ARRAY_SIZE];
    }
    list.push_back(s);
}

int main() {
    std::cerr << "length,time\n";

    for (int length = 1; length <= MAX_STRING_LENGTH; length++) {
        std::vector<std::string> list;
        for (int i = 0; i < BENCHMARK_SIZE; i++) {
            addRandomString(list, length);
        }
        benchmark(list);
    }

    return 0;
}

หากคุณต้องการเรียกใช้โปรแกรมนี้คุณควรทำเช่น./a.out > /dev/nullนั้นเพื่อไม่ให้นับเวลาในการพิมพ์สตริง ตัวเลขที่สำคัญจะถูกพิมพ์ไปstderrดังนั้นพวกเขาจะปรากฏขึ้นในคอนโซล

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

โปรดสังเกตว่าบนเครื่อง linux การกระโดดเกิดขึ้นเมื่อความยาวของสตริงถึง 16 บน macbook การกระโดดเกิดขึ้นเมื่อความยาวถึง 23 ซึ่งเป็นการยืนยันว่า SSO ขึ้นอยู่กับการนำแพลตฟอร์มไปใช้งาน

อูบุนตู มาตรฐาน SSO บน Ubuntu

Macbook Pro มาตรฐาน SSO บน Macbook Pro

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