Esta resposta oferece uma boa visão geral de alto nível da otimização de string curta (SSO). Porém, gostaria de saber mais detalhadamente como funciona na prática, especificamente na implementação libc ++:
Quão curta a string deve ser para se qualificar para o SSO? Isso depende da arquitetura de destino?
Como a implementação distingue entre strings curtas e longas ao acessar os dados da string? É tão simples quanto
m_size <= 16
ou é um sinalizador que faz parte de alguma outra variável de membro? (Eu imagino quem_size
ou parte dele também possa ser usado para armazenar dados de string).
Eu fiz essa pergunta especificamente para libc ++ porque eu sei que ela usa SSO, isso é até mencionado na página inicial da libc ++ .
Aqui estão algumas observações após olhar para a fonte :
libc ++ pode ser compilado com dois layouts de memória ligeiramente diferentes para a classe string, isso é governado pelo _LIBCPP_ALTERNATE_STRING_LAYOUT
sinalizador. Ambos os layouts também distinguem entre máquinas little-endian e big-endian, o que nos deixa com um total de 4 variantes diferentes. Assumirei o layout "normal" e o little-endian no que segue.
Supondo ainda que size_type
sejam 4 bytes e value_type
1 byte, é assim que os primeiros 4 bytes de uma string seriam na memória:
// short string: (s)ize and 3 bytes of char (d)ata
sssssss0;dddddddd;dddddddd;dddddddd
^- is_long = 0
// long string: (c)apacity
ccccccc1;cccccccc;cccccccc;cccccccc
^- is_long = 1
Como o tamanho da string curta está nos 7 bits superiores, ela precisa ser alterada ao acessá-la:
size_type __get_short_size() const {
return __r_.first().__s.__size_ >> 1;
}
Da mesma forma, o getter e o setter para a capacidade de uma longa string usam __long_mask
para contornar o is_long
bit.
Ainda estou procurando uma resposta para minha primeira pergunta, ou seja, qual valor __min_cap
, a capacidade de strings curtas, teria para diferentes arquiteturas?
Outras implementações de biblioteca padrão
Esta resposta fornece uma boa visão geral dos std::string
layouts de memória em outras implementações de biblioteca padrão.
fonte
string
cabeçalho aqui , estou verificando no momento :)Respostas:
O libc ++
basic_string
é projetado para tersizeof
3 palavras em todas as arquiteturas, ondesizeof(word) == sizeof(void*)
. Você dissecou corretamente a bandeira longa / curta e o campo de tamanho na forma curta.Na forma curta, existem 3 palavras para trabalhar:
char
1 byte vai para o nulo final (a libc ++ sempre armazenará um nulo final atrás dos dados).Isso deixa 3 palavras menos 2 bytes para armazenar uma string curta (ou seja, a maior
capacity()
sem uma alocação).Em uma máquina de 32 bits, 10 caracteres caberão na string curta. sizeof (string) é 12.
Em uma máquina de 64 bits, 22 caracteres caberão na string curta. sizeof (string) é 24.
Um dos principais objetivos do projeto era minimizar
sizeof(string)
, ao mesmo tempo que tornava o buffer interno o maior possível. A lógica é acelerar a construção e a atribuição de movimentos. Quanto maior osizeof
, mais palavras você terá que mover durante uma construção de movimento ou atribuição de movimento.O formato longo precisa de no mínimo 3 palavras para armazenar o indicador de dados, tamanho e capacidade. Portanto, restringi a forma abreviada às mesmas 3 palavras. Foi sugerido que um tamanho de 4 palavras pode ter melhor desempenho. Eu não testei essa escolha de design.
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
Há um sinalizador de configuração chamado
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
que reorganiza os membros de dados de forma que o "layout longo" mude de:para:
A motivação para essa mudança é a crença de que colocar
__data_
primeiro lugar terá algumas vantagens de desempenho devido ao melhor alinhamento. Foi feita uma tentativa de medir as vantagens de desempenho e era difícil medir. Não vai piorar o desempenho e pode torná-lo um pouco melhor.A bandeira deve ser usada com cuidado. É uma ABI diferente e, se acidentalmente misturada com uma libc ++
std::string
compilada com uma configuração diferente de_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
, criará erros de tempo de execução.Eu recomendo que este sinalizador seja alterado apenas por um fornecedor de libc ++.
fonte
string
é todo 0 bits. Isso torna a construção padrão supereficiente. E se você estiver disposto a quebrar as regras, às vezes até de graça. Por exemplo, você podecalloc
memorizar e apenas declarar que está cheio de strings padrão construídas.int
s para que a classe possa ser compactada para apenas 16 bytes em arquiteturas de 64 bits?sizeof
. Mas, ao mesmo tempo, o buffer interno parachar
vai de 14 para 22, o que é um benefício muito bom.A implementação da libc ++ é um pouco complicada, vou ignorar seu design alternativo e supor um pequeno computador endian:
Nota:
__compressed_pair
é essencialmente um par otimizado para a Otimização de Base Vazia , também conhecida comotemplate <T1, T2> struct __compressed_pair: T1, T2 {};
; para todos os efeitos, você pode considerá-lo um par normal. Sua importância só surge porque nãostd::allocator
tem estado e, portanto, está vazio.Ok, isso é bastante cru, então vamos verificar a mecânica! Internamente, muitas funções chamarão as
__get_pointer()
próprias chamadas__is_long
para determinar se a string está usando a representação__long
ou__short
:Para ser honesto, não tenho certeza se isso é C ++ padrão (eu conheço a provisão de subsequência inicial,
union
mas não sei como ela se mescla com uma união anônima e aliasing lançados juntos), mas uma biblioteca padrão pode tirar vantagem da implementação definida comportamento de qualquer maneira.fonte
__min_cap
seria avaliado para diferentes arquiteturas, não tenho certeza do quesizeof()
retornará e como isso é influenciado pelo aliasing.3 * the size of one pointer
neste caso, que seriam 12 octetos em um arco de 32 bits e 24 em um arco de 64 bits.