Estava a tentar resolver este exercício em www.spoj.com: FCTRL - Factorial
Você realmente não precisa ler, apenas faça se estiver curioso :)
Primeiro eu implementei em C ++ (aqui está minha solução):
#include <iostream>
using namespace std;
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
std::ios_base::sync_with_stdio(false); // turn off synchronization with the C library’s stdio buffers (from https://stackoverflow.com/a/22225421/5218277)
cin >> num_of_inputs;
while (num_of_inputs--)
{
cin >> fact_num;
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
cout << num_of_trailing_zeros << "\n";
}
return 0;
}
Eu carreguei como a solução para g ++ 5.1
O resultado foi: Tempo 0,18 Mem 3,3M
Mas então eu vi alguns comentários que afirmavam que seu tempo de execução era inferior a 0,1. Como não conseguia pensar em um algoritmo mais rápido, tentei implementar o mesmo código em C :
#include <stdio.h>
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
scanf("%d", &num_of_inputs);
while (num_of_inputs--)
{
scanf("%d", &fact_num);
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
printf("%d", num_of_trailing_zeros);
printf("%s","\n");
}
return 0;
}
Eu carreguei como a solução para gcc 5.1
Desta vez, o resultado foi: Tempo 0,02 Mem 2,1M
Agora o código é quase o mesmo , adicionei std::ios_base::sync_with_stdio(false);
ao código C ++ como foi sugerido aqui para desligar a sincronização com os buffers stdio da biblioteca C. Também divido o printf("%d\n", num_of_trailing_zeros);
para printf("%d", num_of_trailing_zeros); printf("%s","\n");
para compensar a dupla chamada de operator<<
em cout << num_of_trailing_zeros << "\n";
.
Mas eu ainda vi x9 melhor desempenho e menor uso de memória no código C vs. C ++.
Por que é que?
EDITAR
I fixo unsigned long
para unsigned int
o código C. Deveria ter sido unsigned int
e os resultados mostrados acima estão relacionados à nova unsigned int
versão ( ).
std::ostringstream
para acumular a saída e enviá-la astd::cout
todas de uma vez no final reduz o tempo para 0,02. Usarstd::cout
em um loop é simplesmente mais lento em seu ambiente e não acho que haja uma maneira simples de melhorá-lo.Respostas:
Ambos os programas fazem exatamente a mesma coisa. Eles usam o mesmo algoritmo exato e, devido à sua baixa complexidade, seu desempenho está principalmente vinculado à eficiência do tratamento de entrada e saída.
digitalizar a entrada
scanf("%d", &fact_num);
de um lado ecin >> fact_num;
do outro não parece muito caro de qualquer maneira. Na verdade, deve ser menos caro em C ++, pois o tipo de conversão é conhecido no momento da compilação e o analisador correto pode ser chamado diretamente pelo compilador C ++. O mesmo vale para a saída. Você até faz questão de escrever uma chamada separada paraprintf("%s","\n");
, mas o compilador C é bom o suficiente para compilar isso como uma chamada paraputchar('\n');
.Portanto, olhando para a complexidade de E / S e computação, a versão C ++ deve ser mais rápida que a versão C.
Desativar completamente o buffer de
stdout
retarda a implementação C para algo ainda mais lento do que a versão C ++. Outro teste por AlexLop com umfflush(stdout);
após o últimoprintf
produz um desempenho semelhante ao da versão C ++. Não é tão lento quanto desabilitar completamente o buffer porque a saída é gravada no sistema em pequenos pedaços, em vez de um byte por vez.Isso parece apontar para um comportamento específico em sua biblioteca C ++: eu suspeito da implementação de seu sistema
cin
ecout
libera a saída paracout
quando a entrada é solicitada decin
. Algumas bibliotecas C também fazem isso, mas geralmente apenas ao ler / gravar de e para o terminal. O benchmarking feito pelo site www.spoj.com provavelmente redireciona a entrada e a saída de e para os arquivos.AlexLop fez outro teste: ler todas as entradas de uma vez em um vetor e, subsequentemente, computar e escrever todas as saídas ajuda a entender por que a versão C ++ é tão mais lenta. Ele aumenta o desempenho em relação à versão C, o que prova meu ponto e remove a suspeita sobre o código de formatação C ++.
Outro teste da Blastfurnace, armazenando todas as saídas em um
std::ostringstream
e liberando-as de uma vez no final, melhora o desempenho do C ++ em relação à versão C básica. QED.PS: seu algoritmo está incorreto
fact_num >= UINT_MAX / 5
porquefives *= 5
irá estourar e quebrar antes de se tornar> fact_num
. Você pode corrigir isso criandofives
umunsigned long
ou umunsigned long long
se um desses tipos for maior queunsigned int
. Use também%u
comoscanf
formato. Você tem sorte de que os caras do www.spoj.com não sejam muito rígidos em seus benchmarks.EDITAR: Como explicado posteriormente por vitaux, esse comportamento é realmente exigido pelo padrão C ++.
cin
está vinculadocout
por padrão. Uma operação de entradacin
para a qual o buffer de entrada precisa ser recarregado causará ocout
esvaziamento da saída pendente. Na implementação do OP,cin
parece fluircout
sistematicamente, o que é um pouco exagero e visivelmente ineficiente.Ilya Popov forneceu uma solução simples para isso:
cin
pode ser desamarradocout
lançando outro feitiço mágico além destd::ios_base::sync_with_stdio(false);
:Observe também que tal descarga forçada também ocorre ao usar em
std::endl
vez de'\n'
para produzir um fim de linhacout
. Alterar a linha de saída para uma aparência mais idiomática e inocente do C ++cout << num_of_trailing_zeros << endl;
degradaria o desempenho da mesma forma.fonte
std::ostringstream
e enviá-la toda uma vez no final reduz o tempo para paridade com a versão C.ostringstream
para a saída e deu tempo 0,02 QED :). Em relação aofact_num >= UINT_MAX / 5
, bom ponto!vector
e depois processar os cálculos (semostringstream
) dá o mesmo resultado! Tempo 0,02. Combinandovector
eostringstream
não melhorá-lo mais. Mesmo tempo 0,02sizeof(int) == sizeof(long long)
for esta: adicione um teste no corpo do loop depoisnum_of_trailing_zeros += fact_num/fives;
para verificar sefives
atingiu seu máximo:if (fives > UINT_MAX / 5) break;
Outro truque para tornar
iostream
s mais rápido quando você usa amboscin
ecout
é chamarcin.tie(nullptr);
Por padrão, quando você insere qualquer coisa de
cin
, ele esvaziacout
. Isso pode prejudicar significativamente o desempenho se você fizer entradas e saídas intercaladas. Isso é feito para os usos da interface de linha de comando, onde você mostra algum prompt e espera pelos dados:std::string name; cout << "Enter your name:"; cin >> name;
Neste caso, você deseja ter certeza de que o prompt é realmente mostrado antes de começar a esperar pela entrada. Com a linha acima, você quebra esse laço
cin
ecout
se torna independente.Desde C ++ 11, mais uma maneira de obter melhor desempenho com iostreams é usar
std::getline
junto comstd::stoi
, desta forma:std::string line; for (int i = 0; i < n && std::getline(std::cin, line); ++i) { int x = std::stoi(line); }
Dessa forma, pode chegar perto do estilo C em desempenho, ou até mesmo superar
scanf
. Usargetchar
e especialmentegetchar_unlocked
junto com a análise manuscrita ainda fornece um melhor desempenho.PS. Eu escrevi um post comparando várias maneiras de inserir números em C ++, útil para juízes online, mas é apenas em russo, desculpe. Os exemplos de código e a tabela final, no entanto, devem ser compreensíveis.
fonte
std::readline
estd::stoi
não é funcionalmente equivalente ao código OPs. Amboscin >> x;
escanf("%f", &x);
aceitam espaços em branco como separadores, pode haver vários números na mesma linha.O problema é que, citando cppreference :
Isso é fácil de testar: se você substituir
cin >> fact_num;
com
scanf("%d", &fact_num);
e o mesmo para,
cin >> num_of_inputs
mas manter,cout
você obterá praticamente o mesmo desempenho em sua versão C ++ (ou melhor, versão IOStream) como em C um:O mesmo acontece se você mantiver,
cin
mas substituircout << num_of_trailing_zeros << "\n";
com
printf("%d", num_of_trailing_zeros); printf("%s","\n");
Uma solução simples é desamarrar
cout
ecin
conforme mencionado por Ilya Popov:cin.tie(nullptr);
As implementações da biblioteca padrão podem omitir a chamada para flush em certos casos, mas nem sempre. Aqui está uma citação de C ++ 14 27.7.2.1.3 (graças a chqrlie):
fonte
is.tie()
não for um ponteiro nulo, a função chamais.tie()->flush()
para sincronizar a sequência de saída com qualquer fluxo C externo associado. Exceto que essa chamada pode ser suprimida se a área put deis.tie()
estiver vazia. Além disso, uma implementação é permitida para adiar a chamada para liberar até queis.rdbuf()->underflow()
ocorra uma chamada de . Se nenhuma chamada ocorrer antes que o objeto sentinela seja destruído, a chamada para flush pode ser totalmente eliminada.