Kısaltma SSO'sunun std :: string bağlamındaki anlamı


155

Gelen optimizasyonu ve kod stili hakkında bir C ++ sorusuna , birkaç cevaplar kopyalarını optimize bağlamında "TOA" atıfta std::string. TOA bu bağlamda ne anlama geliyor?

Açıkça "tek oturum açma" değil. "Paylaşılan dize optimizasyonu", belki de?


57
Bu, "2 + 2 nedir" ifadesinin "200/50 sonucu nedir" ifadesinin kopyası olduğu gibi yalnızca bir kopyadır. Cevap aynı. Soru tamamen farklı. Birden çok kişi aynı * soruyu sorduğunda "Yinelenenle kapat" ın kullanılması amaçlanmıştır. Bir kişi "nasıl std::stringuygulandığını" ve diğeri "TOA'nın ne anlama geldiğini" sorduğunda, aynı soru olduğunu düşünmek için kesinlikle deli olmalısınız
jalf

1
@jalf: Bu sorunun kapsamını tam olarak kapsayan mevcut bir Q + A varsa, bunun bir kopya olduğunu düşünürdüm (OP'nin kendisi için arama yapması gerektiğini söylemiyorum, sadece buradaki herhangi bir cevap, zaten kapsanmıştır.)
Oliver Charlesworth

47
OP'ye etkili bir şekilde "sorunuzun yanlış olduğunu söylüyorsunuz. Ama ne sormanız gerektiğini bilmek için cevabı bilmeniz gerekiyordu ". İnsanları SO kapatmak için güzel bir yol. Ayrıca, ihtiyaç duyduğunuz bilgileri bulmanızı gereksiz yere zorlaştırır. İnsanlar soru sormazlarsa (ve kapanış etkili bir şekilde "bu soru sorulmamalıydı" diyorsa), o zaman cevabı zaten bilmeyen insanların bu sorunun cevabını almasının olası bir yolu olmaz
jalf

7
@jalf: Hiç de değil. IMO, "oyu kapatmak" demek "kötü soru" anlamına gelmez. Bunun için downvotes kullanıyorum. Cevabı "tanımsız davranış" olan tüm sayısız soruların (i = i ++ vb.) Birbirinin kopyası olduğu anlamında bir kopya olduğunu düşünüyorum. Farklı bir notta, neden yinelenmemişse kimse soruyu cevaplamadı?
Oliver Charlesworth

5
@jalf: Oli ile aynı fikirdeyim, soru kopya değil, ama cevap, bu yüzden cevapların zaten uygun olduğu başka bir soruya yönlendirme olacaktır. Kopyalar olarak kapatılan sorular kaybolmaz, bunun yerine cevabın yer aldığı başka bir soruya işaretçi olurlar. TOA'yı arayan bir sonraki kişi burada sona erecek, yönlendirmeyi takip edecek ve cevabını bulacak.
Matthieu M.Nisan

Yanıtlar:


213

Arka Plan / Genel Bakış

Otomatik değişkenler üzerindeki işlemler (çağrmadan malloc/ oluşturmadan oluşturduğunuz değişkenler olan "yığıntan" new) genellikle serbest depoyu ("yığın" kullanılarak oluşturulan değişkenlerden) daha hızlıdır new. Bununla birlikte, otomatik dizilerin boyutu derleme zamanında sabittir, ancak serbest depodaki dizilerin boyutu sabit değildir. Üstelik, yığın boyutu sınırlıdır (genellikle birkaç MiB), ancak serbest depolama alanı yalnızca sisteminizin belleği ile sınırlıdır.

TOA, Kısa / Küçük Dize Optimizasyonudur. A std::stringgenellikle dizeyi, serbest depoya ("yığın") bir işaretçi olarak kaydeder new char [size]; Bu, çok büyük dizeler için yığın taşmasını önler, ancak özellikle kopyalama işlemlerinde daha yavaş olabilir. Bir optimizasyon olarak, birçok uygulama std::stringküçük bir otomatik dizi oluşturur char [20]. 20 karakter veya daha küçük bir dizeniz varsa (bu örnekte gerçek boyut değişir), dizeyi doğrudan bu dizide saklar. Bu, telefon etme ihtiyacını ortadan kaldırır new, bu da işleri biraz hızlandırır.

DÜZENLE:

Bu cevabın bu kadar popüler olmasını beklemiyordum, ama öyle olduğu için, daha vahşi bir SSO uygulamasını hiç okumadığım konusunda daha gerçekçi bir uygulama yapmama izin verin.

Uygulama ayrıntıları

En azından, std::stringaşağıdaki bilgilerin depolanması gerekir:

  • Boyut
  • Kapasite
  • Verilerin yeri

Boyut bir std::string::size_typeveya işaretçi olarak saklanabilir . Tek fark, kullanıcı aradığında iki işaretçi çıkarmak sizeveya kullanıcı aradığında size_typebir işaretçiye a eklemek isteyip istemediğinizdir end. Kapasite her iki şekilde de saklanabilir.

Kullanmadığın şeyin bedelini ödemiyorsun.

İlk olarak, yukarıda ana hatlarıma dayanarak naif uygulamayı düşünü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;
};

64 bitlik bir sistem için, bu genellikle std::stringdize başına 24 bayt 'ek yükü', artı SSO tamponu için başka bir 16 (dolgu gereksinimleri nedeniyle burada 20 yerine 16 seçilmiştir) anlamına gelir. Basitleştirilmiş örnekte olduğu gibi, bu üç veri üyesini ve yerel bir karakter dizisini saklamak gerçekten mantıklı olmaz. Eğer m_size <= 16, o zaman tüm verileri koyacağım m_sso, bu yüzden zaten kapasiteyi biliyorum ve verilere işaretçiye ihtiyacım yok. Eğer m_size > 16öyleyse ihtiyacım yok m_sso. Hepsine ihtiyacım olduğu yerde kesinlikle çakışma yok. Yer kaplamayan daha akıllı bir çözüm, bunun gibi bir şeye benzeyecektir (denenmemiş, sadece örnek amaçlı):

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

Çoğu uygulamanın daha çok benzediğini varsayıyorum.


7
İşte bazı gerçek uygulamaların iyi bir açıklaması: stackoverflow.com/a/28003328/203044
BillT

Çoğu geliştirici const referanslarını kullanarak std :: string'i geçtiğinde TOA gerçekten pratik mi?
Gupta

1
TOA'nın kopyalamayı daha ucuz hale getirmenin ötesinde iki avantajı vardır. Birincisi, dize boyutunuz küçük arabellek boyutuna sığarsa, ilk yapıya tahsis etmeniz gerekmez. İkincisi, bir işlev a'yı kabul std::string const &ettiğinde, verilere başvurunun tek bir bellek dolaylaması olmasıdır, çünkü veriler referansın bulunduğu yerde saklanır. Küçük dize optimizasyonu yoksa, verilere erişmek için iki bellek indirmesi gerekir (önce dizeye referansı yüklemek ve içeriğini okumak, ikincisi dizedeki veri işaretçisinin içeriğini okumak için).
David Stone

34

SSO, küçük dizelerin ayrı ayrı ayrılmış bir tampon kullanmak yerine dize sınıfının gövdesine gömüldüğü bir teknik olan "Küçük Dize Optimizasyonu" nun kısaltmasıdır.


15

Diğer cevaplarda daha önce açıklandığı gibi, TOA, Küçük / Kısa Dizi Optimizasyonu anlamına gelir . Bu optimizasyonun ardındaki motivasyon, genel olarak uygulamaların daha uzun dizelerden çok daha kısa dizeleri ele aldıkları yadsınamaz delillerdir.

David Stone tarafından yukarıdaki cevabında açıklandığı gibi , std::stringsınıf içeriği belirli bir uzunluğa kadar saklamak için dahili bir tampon kullanır ve bu, belleği dinamik olarak tahsis etme ihtiyacını ortadan kaldırır. Bu, kodu daha verimli ve daha hızlı hale getirir .

Bu diğer ilgili cevap , dahili tamponun boyutunun std::stringplatformdan platforma değişen uygulamaya bağlı olduğunu açıkça göstermektedir (aşağıdaki kıyaslama sonuçlarına bakın).

Deneyler

Burada, aynı uzunlukta çok sayıda dizenin kopyalama işlemini karşılaştıran küçük bir program var. Uzunluk = 1 olan 10 milyon dizeyi kopyalamak için zaman yazdırmaya başlar. Sonra uzunluk = 2 olan dizelerle tekrarlanır. Uzunluk 50 olana kadar devam eder.

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

Bu programı çalıştırmak istiyorsanız ./a.out > /dev/null, dizeleri yazdırma süresinin sayılmaması için bunu yapmalısınız . Önemli sayılar yazdırılır stderr, böylece konsolda görünürler.

MacBook ve Ubuntu makinelerimin çıktılarıyla grafikler oluşturdum. Uzunluk belirli bir noktaya ulaştığında dizeleri kopyalamak için büyük bir sıçrama olduğunu unutmayın. Bu, dizelerin artık dahili arabelleğe sığmadığı ve bellek ayırma işleminin kullanılması gerektiği andır.

Ayrıca linux makinede atlama dizesinin uzunluğu 16'ya ulaştığında gerçekleşir. Macbook'ta atlama 23'e ulaştığında gerçekleşir. Bu, TOA'nın platform uygulamasına bağlı olduğunu doğrular.

Ubuntu Ubuntu'da TOA karşılaştırması

Macbook Pro Macbook Pro'da TOA karşılaştırması

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.