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?
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?
Yanıtlar:
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::string
genellikle 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::string
küçü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.
En azından, std::string
aşağıdaki bilgilerin depolanması gerekir:
Boyut bir std::string::size_type
veya işaretçi olarak saklanabilir . Tek fark, kullanıcı aradığında iki işaretçi çıkarmak size
veya kullanıcı aradığında size_type
bir işaretçiye a eklemek isteyip istemediğinizdir end
. Kapasite her iki şekilde de saklanabilir.
İ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::string
dize 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.
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).
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.
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::string
sı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::string
platformdan platforma değişen uygulamaya bağlı olduğunu açıkça göstermektedir (aşağıdaki kıyaslama sonuçlarına bakın).
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.
std::string
uygulandığı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