Ý nghĩa của từ viết tắt SSO trong ngữ cảnh của std :: string


155

Trong câu hỏi C ++ về tối ưu hóa và kiểu mã , một số câu trả lời được gọi là "SSO" trong bối cảnh tối ưu hóa các bản sao của std::string. SSO có nghĩa là gì trong bối cảnh đó?

Rõ ràng không phải là "một dấu hiệu duy nhất". "Tối ưu hóa chuỗi chia sẻ", có lẽ?


57
Đó chỉ là một bản sao theo cùng một cách mà "2 + 2" là một bản sao của "kết quả của 200/50" là gì. Câu trả lời là như nhau. Câu hỏi hoàn toàn khác nhau. "Đóng như trùng lặp" được dự định sẽ được sử dụng khi nhiều người hỏi cùng một câu hỏi *. Khi một người hỏi "làm thế nào được std::stringthực hiện", và một người khác hỏi "những gì hiện SSO bình", bạn có thể hoàn toàn mất trí để xem xét họ có cùng một câu hỏi
jalf

1
@jalf: Nếu có Q + A hiện có bao gồm chính xác phạm vi của câu hỏi này, tôi sẽ coi đó là một bản sao (Tôi không nói rằng OP nên tự tìm kiếm câu hỏi này, chỉ là bất kỳ câu trả lời nào ở đây sẽ bao gồm đã được bảo hiểm.)
Oliver Charlesworth

47
Bạn đang nói với OP một cách hiệu quả rằng "câu hỏi của bạn sai. Nhưng bạn cần biết câu trả lời để biết những gì bạn nên hỏi". Cách tốt đẹp để tắt mọi người SO. Nó cũng làm cho nó khó khăn để tìm thấy thông tin bạn cần. Nếu mọi người không đặt câu hỏi (và kết thúc là nói một cách hiệu quả "câu hỏi này không nên được hỏi"), thì sẽ không có cách nào khả thi cho những người chưa biết câu trả lời, để có câu trả lời cho câu hỏi này
jalf

7
@jalf: Không hề. IMO, "bỏ phiếu để đóng" không có nghĩa là "câu hỏi xấu". Tôi sử dụng downvote cho điều đó. Tôi coi đó là một bản sao theo nghĩa là tất cả vô số câu hỏi (i = i ++, v.v.) có câu trả lời là "hành vi không xác định" là trùng lặp với nhau. Trên một lưu ý khác, tại sao không ai trả lời câu hỏi nếu nó không phải là một bản sao?
Oliver Charlesworth

5
@jalf: Tôi đồng ý với Oli, câu hỏi không phải là một bản sao, nhưng câu trả lời sẽ là, do đó chuyển hướng đến một câu hỏi khác trong đó các câu trả lời đã được đặt có vẻ phù hợp. Các câu hỏi đóng lại khi các bản sao không biến mất, thay vào đó chúng đóng vai trò là con trỏ hướng tới một câu hỏi khác trong đó câu trả lời đặt ra. Người tiếp theo tìm kiếm SSO sẽ kết thúc tại đây, theo dõi chuyển hướng và tìm câu trả lời của cô ấy.
Matthieu M.

Câu trả lời:


212

Bối cảnh / Tổng quan

Các thao tác trên các biến tự động ("từ ngăn xếp", là các biến mà bạn tạo mà không gọi malloc/ new) thường nhanh hơn nhiều so với các biến liên quan đến cửa hàng miễn phí ("heap", là các biến được tạo bằng cách sử dụng new). Tuy nhiên, kích thước của mảng tự động được cố định tại thời gian biên dịch, nhưng kích thước của mảng từ cửa hàng miễn phí thì không. Hơn nữa, kích thước ngăn xếp bị giới hạn (thường là một vài MiB), trong khi đó cửa hàng miễn phí chỉ bị giới hạn bởi bộ nhớ hệ thống của bạn.

SSO là Tối ưu hóa chuỗi ngắn / nhỏ. Một std::stringthường lưu trữ chuỗi dưới dạng một con trỏ đến cửa hàng miễn phí ("heap"), cung cấp các đặc tính hiệu suất tương tự như khi bạn gọi new char [size]. Điều này ngăn chặn tràn ngăn xếp cho các chuỗi rất lớn, nhưng nó có thể chậm hơn, đặc biệt là với các hoạt động sao chép. Như một tối ưu hóa, nhiều triển khai std::stringtạo ra một mảng tự động nhỏ, đại loại như thế char [20]. Nếu bạn có một chuỗi có 20 ký tự hoặc nhỏ hơn (được đưa ra ví dụ này, kích thước thực tế khác nhau), nó sẽ lưu nó trực tiếp trong mảng đó. Điều này tránh sự cần thiết phải gọi newtất cả, làm tăng tốc mọi thứ lên một chút.

BIÊN TẬP:

Tôi không mong đợi câu trả lời này khá phổ biến, nhưng vì nó, hãy để tôi đưa ra một triển khai thực tế hơn, với lời cảnh báo rằng tôi chưa bao giờ thực sự đọc bất kỳ triển khai SSO nào "trong tự nhiên".

Chi tiết thực hiện

Tối thiểu, std::stringcần lưu trữ các thông tin sau:

  • Kích cỡ
  • Công suất
  • Vị trí của dữ liệu

Kích thước có thể được lưu trữ dưới dạng std::string::size_typehoặc như một con trỏ đến cuối. Sự khác biệt duy nhất là bạn có muốn trừ hai con trỏ khi người dùng gọi sizehoặc thêm size_typemột con trỏ khi người dùng gọi end. Khả năng có thể được lưu trữ một trong hai cách là tốt.

Bạn không trả tiền cho những gì bạn không sử dụng.

Đầu tiên, hãy xem xét việc thực hiện ngây thơ dựa trên những gì tôi đã nêu ở trên:

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

Đối với hệ thống 64 bit, điều đó thường có nghĩa là std::stringcó 24 byte 'phí' trên mỗi chuỗi, cộng thêm 16 cho bộ đệm SSO (16 được chọn ở đây thay vì 20 do yêu cầu đệm). Sẽ thực sự có ý nghĩa khi lưu trữ ba thành viên dữ liệu đó cộng với một mảng các ký tự cục bộ, như trong ví dụ đơn giản hóa của tôi. Nếu m_size <= 16, sau đó tôi sẽ đưa tất cả dữ liệu vào m_sso, vì vậy tôi đã biết dung lượng và tôi không cần con trỏ tới dữ liệu. Nếu m_size > 16, thì tôi không cần m_sso. Hoàn toàn không có sự chồng chéo nơi tôi cần tất cả chúng. Một giải pháp thông minh hơn mà không lãng phí không gian sẽ trông giống một cái gì đó giống như thế này (chỉ mục đích chưa được kiểm tra):

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

Tôi cho rằng hầu hết các triển khai trông giống như thế này.


7
Đây là một lời giải thích tốt về một số triển khai thực tế: stackoverflow.com/a/28003328/203044
BillT

SSO có thực sự thiết thực khi hầu hết các nhà phát triển vượt qua std :: string bằng cách sử dụng tham chiếu const?
Gupta

1
SSO có hai lợi ích ngoài việc sao chép rẻ hơn. Đầu tiên là nếu kích thước chuỗi của bạn phù hợp với kích thước bộ đệm nhỏ, bạn không cần phân bổ vào cấu trúc ban đầu. Thứ hai là khi một hàm chấp nhận a std::string const &, việc lấy dữ liệu là một chỉ định bộ nhớ duy nhất, vì dữ liệu được lưu trữ tại vị trí của tham chiếu. Nếu không có tối ưu hóa chuỗi nhỏ, việc truy cập dữ liệu sẽ yêu cầu hai lần nhập bộ nhớ (đầu tiên để tải tham chiếu đến chuỗi và đọc nội dung của chuỗi, sau đó lần thứ hai để đọc nội dung của con trỏ dữ liệu trong chuỗi).
David Stone

34

SSO là tên viết tắt của "Tối ưu hóa chuỗi nhỏ", một kỹ thuật trong đó các chuỗi nhỏ được nhúng trong phần thân của lớp chuỗi thay vì sử dụng bộ đệm được phân bổ riêng.


15

Như đã được giải thích bởi các câu trả lời khác, SSO có nghĩa là Tối ưu hóa chuỗi nhỏ / ngắn . Động lực đằng sau sự tối ưu hóa này là bằng chứng không thể phủ nhận rằng các ứng dụng nói chung xử lý các chuỗi ngắn hơn nhiều so với các chuỗi dài hơn.

Như David Stone đã giải thích trong câu trả lời của mình ở trên , std::stringlớp sử dụng bộ đệm bên trong để lưu trữ nội dung có độ dài nhất định và điều này giúp loại bỏ nhu cầu phân bổ bộ nhớ một cách linh hoạt. Điều này làm cho mã hiệu quả hơnnhanh hơn .

Câu trả lời liên quan khác này cho thấy rõ rằng kích thước của bộ đệm nội bộ phụ thuộc vào việc std::stringtriển khai, thay đổi tùy theo nền tảng (xem kết quả điểm chuẩn bên dưới).

Điểm chuẩn

Đây là một chương trình nhỏ đánh giá hoạt động sao chép của nhiều chuỗi có cùng độ dài. Nó bắt đầu in thời gian để sao chép 10 triệu chuỗi có độ dài = 1. Sau đó, nó lặp lại với chuỗi có độ dài = 2. Nó tiếp tục cho đến khi độ dài là 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;
}

Nếu bạn muốn chạy chương trình này, bạn nên làm như ./a.out > /dev/nullvậy để thời gian in chuỗi không được tính. Các số quan trọng được in ra stderr, vì vậy chúng sẽ hiển thị trong bảng điều khiển.

Tôi đã tạo các biểu đồ với đầu ra từ máy MacBook và Ubuntu của tôi. Lưu ý rằng có một bước nhảy lớn trong thời gian để sao chép các chuỗi khi độ dài đạt đến một điểm nhất định. Đó là thời điểm khi các chuỗi không vừa với bộ đệm bên trong nữa và phải sử dụng cấp phát bộ nhớ.

Cũng lưu ý rằng trên máy linux, bước nhảy xảy ra khi độ dài của chuỗi đạt 16. Trên macbook, bước nhảy xảy ra khi độ dài đạt 23. Điều này xác nhận rằng SSO phụ thuộc vào việc triển khai nền tảng.

Ubuntu Điểm chuẩn SSO trên Ubuntu

Macbook Pro Điểm chuẩn SSO trên Macbook Pro

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.