Alguém pode bater o desempenho do meu número inteiro para o código std :: string, vinculado abaixo?
Já existem várias perguntas que explicam como converter um número inteiro em um std::string
em C ++, como este , mas nenhuma das soluções fornecidas é eficiente.
Aqui está o código pronto para compilação para alguns métodos comuns para competir:
- A "maneira C ++", usando stringstream: http://ideone.com/jh3Sa
- sprintf, que os SO-ers geralmente recomendam para o desempenho consciente: http://ideone.com/82kwR
Ao contrário do que se pensa , boost::lexical_cast
tem sua própria implementação ( white paper ) e não usa stringstream
operadores de inserção numérica. Eu realmente gostaria de ver seu desempenho comparado, porque essa outra pergunta sugere que é infeliz .
E minha própria contribuição, que é competitiva em computadores desktop, e demonstra uma abordagem que é executada a toda velocidade também em sistemas embarcados, diferentemente dos algoritmos dependentes do módulo inteiro:
- Algoritmos de Ben: http://ideone.com/SsEUW
Se você deseja usar esse código, eu o disponibilizo sob uma licença BSD simplificada (uso comercial permitido, atribuição necessária). Basta perguntar.
Finalmente, a função ltoa
não é padrão, mas está amplamente disponível.
- versão ltoa, para quem tem um compilador que o fornece (o ideone não): http://ideone.com/T5Wim
Postarei minhas medidas de desempenho como resposta em breve.
Regras para algoritmos
- Forneça código para uma conversão de pelo menos números inteiros assinados e não assinados de 32 bits em decimal.
- Produzir saída como a
std::string
. - Não há truques incompatíveis com encadeamento e sinais (por exemplo, buffers estáticos).
- Você pode assumir um conjunto de caracteres ASCII.
- Teste seu código em
INT_MIN
uma máquina de complemento de dois onde o valor absoluto não seja representável. - Idealmente, a saída deve ser caractere por caractere idêntico à versão canônica C ++ usando
stringstream
, http://ideone.com/jh3Sa , mas qualquer coisa que é claramente compreensível que o número correto é ok também. - NOVO : Embora você possa usar as opções de compilador e otimizador (exceto completamente desativadas) que deseja para a comparação, o código também precisa compilar e fornecer resultados corretos em pelo menos VC ++ 2010 e g ++.
Discussão esperada
Além de algoritmos melhores, eu também gostaria de obter alguns benchmarks em várias plataformas e compiladores diferentes (vamos usar a taxa de transferência de MB / s como nossa unidade de medida padrão). Acredito que o código do meu algoritmo (eu sei que o sprintf
benchmark usa alguns atalhos - agora corrigidos) é um comportamento bem definido pelo padrão, pelo menos sob a premissa do ASCII, mas se você vir algum comportamento ou entradas indefinidos para os quais a saída é inválido, indique-o.
Conclusões:
Diferentes algoritmos são executados para g ++ e VC2010, provavelmente devido às diferentes implementações de std::string
cada um. O VC2010 claramente faz um trabalho melhor com o NRVO, livrar-se do retorno por valor ajudou apenas no gcc.
Foi encontrado um código que supera sprintf
por uma ordem de magnitude. ostringstream
fica para trás por um fator de 50 e mais.
O vencedor do desafio é o usuário434507, que produz código que executa 350% da velocidade da minha própria no gcc. Entradas adicionais são fechadas devido aos caprichos da comunidade SO.
Os atuais campeões de velocidade (final?) São:
- Para gcc: user434507, 8 vezes mais rápido que
sprintf
: http://ideone.com/0uhhX - Para Visual C ++: Timo, 15 vezes mais rápido que
sprintf
: http://ideone.com/VpKO3
fonte
Respostas:
Isso explodirá em sistemas que não permitem acessos desalinhados à memória (nesse caso, a primeira atribuição desalinhada via
*(short*)
causaria um segfault), mas deve funcionar muito bem.Uma coisa importante a fazer é minimizar o uso de
std::string
. (Irônico, eu sei.) No Visual Studio, por exemplo, a maioria das chamadas para métodos de std :: string não está embutida, mesmo se você especificar / Ob2 nas opções do compilador. Assim, mesmo algo tão trivial quanto uma chamadastd::string::clear()
, que você pode esperar ser muito rápido, pode levar 100 clockticks ao vincular o CRT como uma biblioteca estática e até 300 clockticks ao vincular como uma DLL.Pelo mesmo motivo, retornar por referência é melhor porque evita uma atribuição, um construtor e um destruidor.
fonte
sprintf
. E com o VC ++ 2010, ele recebe cerca de 50 MB / s, aproximadamente o dobro da velocidade do sprintf.sprintf
implementação, já mencionei isso na minha pergunta, mas acredito que o code-to-beat fornece exatamente o mesmo resultado que o stringstream.sprintf
invólucro também, para evitar confusão.Ah, desafio incrível por sinal ... Eu me diverti muito com isso.
Eu tenho dois algoritmos para enviar (o código está na parte inferior, se você quiser pular para ele). Nas minhas comparações, exijo que a função retorne uma string e que ela possa manipular int e unsigned int. Comparar coisas que não constroem uma string com aquelas que não fazem realmente sentido.
A primeira é uma implementação divertida que não usa nenhuma tabela de pesquisa pré-computada ou divisão / módulo explícito. Este é competitivo com os outros com gcc e com todos, exceto o Timo no msvc (por um bom motivo que explico abaixo). O segundo algoritmo é minha submissão real para obter o melhor desempenho. Nos meus testes, ele vence todos os outros no gcc e no msvc.
Acho que sei por que alguns dos resultados no MSVC são muito bons. std :: string possui dois construtores relevantes
std::string(char* str, size_t n)
e o
std::string(ForwardIterator b, ForwardIterator e)
gcc faz a mesma coisa para os dois ... ou seja, usa o segundo para implementar o primeiro. O primeiro construtor pode ser implementado significativamente mais eficientemente que isso e o MSVC o faz. O benefício disso é que, em alguns casos (como meu código rápido e o código do Timo), o construtor de strings pode ser incorporado. De fato, apenas alternar entre esses construtores no MSVC é quase uma diferença de 2x para o meu código.
Meus resultados dos testes de desempenho:
Fontes de código:
- Voigt
- Timo
- ergosys
- user434507
- user-voigt-timo
- hopman-fun
- hopman-fast
gcc 4.4.5 -O2 no Ubuntu 10.10 de 64 bits, Core i5
MSVC 2010 de 64 bits / Ox no Windows 7 de 64 bits, Core i5
Aqui estão alguns resultados e uma estrutura de teste / tempo na ideone
http://ideone.com/XZRqp
Observe que a ideone é um ambiente de 32 bits. Ambos os meus algoritmos sofrem com isso, mas hopman_fast ainda é pelo menos competitivo.
Observe que, para aqueles que não constroem uma string, adicionei o seguinte modelo de função:
Agora, meu código ... primeiro o mais divertido:
E então o mais rápido:
fonte
Dados de referência para o código fornecido na pergunta:
No ideone (gcc 4.3.4):
Core i7, Windows 7 de 64 bits, 8 GB de RAM, Visual C ++ 2010 de 32 bits:
cl /Ox /EHsc
Core i7, Windows 7 de 64 bits, 8 GB de RAM, Visual C ++ 2010 de 64 bits:
cl /Ox /EHsc
Core i7, Windows 7 de 64 bits, 8 GB de RAM, cygwin gcc 4.3.4:
g++ -O3
edit : eu adicionaria minha própria resposta, mas a pergunta foi encerrada, por isso estou adicionando aqui. :) Escrevi meu próprio algoritmo e consegui uma melhoria decente em relação ao código de Ben, embora só o tenha testado no MSVC 2010. Também fiz uma referência de todas as implementações apresentadas até agora, usando a mesma configuração de teste que estava no original de Ben código. - Timo
Intel Q9450, Windows XP de 32 bits, MSVC 2010
cl /O2 /EHsc
-
fonte
std::string
ou a otimização ruim do código aritmético. Vou fazer outra versão que não seja convertidastd::string
no final e ver se o gcc se sai melhor.Enquanto as informações que chegamos aqui para os algoritmos são bastante boas, acho que a pergunta está "quebrada" e vou explicar por que penso isso:
A pergunta solicita o desempenho de
int
->std::string
conversion, e isso pode ser interessante ao comparar um método comumente disponível, como diferentes implementações de strings ou boost :: lexical_cast. Entretanto, não faz sentido pedir um novo código , um algoritmo especializado, para fazer isso. O motivo é que o int2string sempre envolverá a alocação de heap de std :: string e se estamos tentando extrair o último do nosso algoritmo de conversão, não acho que faça sentido misturar essas medidas com as alocações de heap feitas pelo std: :corda. Se eu quiser conversão de desempenho, eu irei sempre usarei um buffer de tamanho fixo e certamente nunca alocarei nada no heap!Para resumir, acho que os horários devem ser divididos:
Esses aspectos não devem ser confundidos em um momento, IMHO.
fonte
std::string
requisito "output as " foi colocado lá apenas para tornar as coisas justas e consistentes para todos os envios. Os algoritmos mais rápidos para obterstd::string
resultados também serão mais rápidos para preencher um buffer pré-alocado.Não posso testar no VS, mas isso parece ser mais rápido que o seu código para g ++, cerca de 10%. Provavelmente poderia ser ajustado, os valores de decisão escolhidos são suposições. int apenas, desculpe.
fonte
char
paraunsigned
produz uma melhoria de velocidade semelhante no meu código, pelo menos em gcc / ideone ideone.com/uthKK . Vou testar no VS amanhã.Resposta atualizada do usuário2985907 ... modp_ufast ...
fonte
Mismatch found: Generated: -99 Reference: -9099999 Mismatch found: Generated: -99 Reference: -9099998 Mismatch found: Generated: -99 Reference: -9099997
Aqui está minha pequena tentativa deste divertido quebra-cabeça.
Em vez de usar tabelas de pesquisa, eu queria que o compilador descobrisse tudo. Nesse caso em particular - se você ler o Hackers 'Delight, verá como a divisão e o módulo funcionam - o que torna muito possível otimizar isso usando as instruções SSE / AVX.
Referência de desempenho
Quanto à velocidade, minha referência aqui me diz que é 1,5 vezes mais rápido que o trabalho do Timo (no meu Intel Haswell ele roda em aproximadamente 1 GB / s).
Coisas que você poderia considerar uma trapaça
Quanto ao truque de não fazer uma string padrão que eu uso - é claro que levei isso em consideração também para a minha referência do método de Timo.
Eu uso um intrínseco: BSR. Se você preferir, também pode usar as tabelas DeBruijn - que é uma das coisas sobre as quais escrevi bastante no meu post 'fastlog 2log'. Claro, isso tem uma penalidade de desempenho (* bem ... se você estiver realizando muitas operações itoa, poderá fazer um BSR mais rápido, mas acho que isso não é justo ...).
Como funciona
A primeira coisa a fazer é descobrir quanta memória precisamos. Este é basicamente um 10log, que pode ser implementado de várias maneiras inteligentes. Veja os " Bit Twiddling Hacks " frequentemente citados para obter detalhes.
A próxima coisa a fazer é executar a saída numérica. Eu uso recursão de modelo para isso, para que o compilador descubra.
Eu uso 'modulo' e 'div' um ao lado do outro. Se você ler o Hacker's Delight, notará que os dois estão intimamente relacionados; portanto, se você tiver uma resposta, provavelmente também a outra. Eu achei que o compilador pode descobrir os detalhes ... :-)
O código
Obtendo o número de dígitos usando um log (modificado) 10:
Preparando a string:
fonte
Eu tive isso por um tempo e finalmente cheguei a publicá-lo.
Mais alguns métodos em comparação com a palavra dupla por vez hopman_fast . Os resultados são para o std :: string otimizado por cadeia de caracteres curta do GCC, pois, caso contrário, as diferenças de desempenho ficam obscurecidas pela sobrecarga do código de gerenciamento de cadeia de cópia na gravação. A taxa de transferência é medida da mesma maneira que em outras partes deste tópico, as contagens de ciclo são para as partes brutas de serialização do código antes de copiar o buffer de saída em uma sequência.
Opções de tempo de compilação:
-DVSTRING - habilita sequências de SSO para configurações mais antigas do GCC
-DBSR1 - habilita o log
rápido10 -DRDTSC - habilita contadores de ciclo
fonte
Acredito que criei o algoritmo inteiro para string mais rápido. É uma variação do algoritmo do Módulo 100 que é cerca de 33% mais rápida e, o mais importante, é mais rápida para números menores e maiores. É chamado de algoritmo Script ItoS. Para ler o artigo que explica como desenvolvi o algoritmo, consulte https://github.com/kabuki-starship/kabuki-toolkit/wiki/Engineering-a-Faster-Integer-to-String-Algorithm . Você pode usar o algoritmo, mas pense em contribuir com a VM Kabuki e confira o Script ; especialmente se você estiver interessado em protocolos de rede AMIL-NLP e / ou definidos por software.
Autor
fonte
std::string
para que a comparação com outros métodos listados aqui seja válida. No começo, não conseguia descobrir o uso do operador shift na árvore de pesquisa binária, porque uma comparação já é excepcionalmente rápida, mas agora percebo que seria útil pré-computar esse valor alterado, se necessário. Você não usa, no entanto. Por outro lado, você não acaba com literais grandes codificados em instruções, então talvez isso seja motivo suficiente por si só.Modificação na solução do user434507. Modificado para usar a matriz de caracteres em vez da string C ++. Corre um pouco mais rápido. Também movi a verificação para 0 inferior no código ... pois isso nunca acontece no meu caso particular. Mova-o para trás se for mais comum para o seu caso.
fonte
Mismatch found: Generated: -9999999990 Reference: -999999999 Mismatch found: Generated: -9999999980 Reference: -999999998 Mismatch found: Generated: -9999999970 Reference: -999999997
Usamos o seguinte código (para MSVC):
Modelo tBitScanReverse:
auxiliares char / wchar_t:
Poderes de 10 mesas:
Impressão real:
O último loop pode ser desenrolado:
A ideia principal é a mesma que a @atlaste sugerida anteriormente: https://stackoverflow.com/a/29039967/2204001
fonte
Acabei de descobrir isso por causa de atividades recentes; Eu realmente não tenho tempo para adicionar benchmarks, mas queria adicionar o que escrevi no passado para quando eu precisar de uma conversão rápida de número inteiro para string ...
https://github.com/CarloWood/ai-utils/blob/master/itoa.h
https://github.com/CarloWood/ai-utils/blob/master/itoa.cxx
O truque usado aqui é que o usuário deve fornecer um std :: array grande o suficiente (em sua pilha) e que esse código grave a string para trás, iniciando nas unidades e retornando um ponteiro para o array com um deslocamento para onde o resultado realmente começa.
Portanto, isso não aloca ou move a memória, mas ainda exige uma divisão e um módulo por dígito de resultado (que eu acredito que seja rápido o suficiente, pois isso é apenas o código executado internamente na CPU; o acesso à memória geralmente é o problema).
fonte
Por que ninguém está usando a função div do stdlib quando ambos, quociente e restante são necessários?
Usando o código fonte do Timo, acabei com algo assim:
Ok, para int sem sinal, a função div não pode ser usada, mas os sem sinal podem ser manipulados separadamente.
Eu defini a macro COPYPAIR da seguinte maneira para testar variações como copiar os 2 caracteres do digit_pairs (não encontrei nenhuma vantagem óbvia de nenhum desses métodos):
fonte