Estaba intentando resolver este ejercicio de www.spoj.com: FCTRL - Factorial
Realmente no tienes que leerlo, solo hazlo si tienes curiosidad :)
Primero lo implementé en C ++ (aquí está mi solución):
#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;
}
Lo cargué como la solución para g ++ 5.1
El resultado fue: Tiempo 0,18 Mem 3,3 M
Pero luego vi algunos comentarios que afirmaban que su tiempo de ejecución era inferior a 0,1. Como no podía pensar en un algoritmo más rápido, intenté implementar el mismo código en 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;
}
Lo cargué como solución para gcc 5.1
Esta vez el resultado fue: Tiempo 0.02 Mem 2.1M
Ahora el código es casi el mismo , agregué std::ios_base::sync_with_stdio(false);
al código C ++ como se sugirió aquí para desactivar la sincronización con los búferes stdio de la biblioteca C. También he dividido la printf("%d\n", num_of_trailing_zeros);
que printf("%d", num_of_trailing_zeros); printf("%s","\n");
para compensar doble llamada de operator<<
en cout << num_of_trailing_zeros << "\n";
.
Pero todavía vi un mejor rendimiento x9 y un menor uso de memoria en el código C frente a C ++.
¿Porqué es eso?
EDITAR
Me fijo unsigned long
a unsigned int
en el código C. Debería haber sido así unsigned int
y los resultados que se muestran arriba están relacionados con la unsigned int
versión new ( ).
std::ostringstream
para acumular la salida y enviarlo astd::cout
todos a la vez al final reduce el tiempo a 0.02. El usostd::cout
en bucle es simplemente más lento en su entorno y no creo que haya una forma sencilla de mejorarlo.Respuestas:
Ambos programas hacen exactamente lo mismo. Utilizan exactamente el mismo algoritmo y, dada su baja complejidad, su rendimiento está vinculado principalmente a la eficiencia del manejo de entrada y salida.
escanear la entrada con
scanf("%d", &fact_num);
un lado ycin >> fact_num;
el otro no parece muy costoso de ninguna manera. De hecho, debería ser menos costoso en C ++, ya que el tipo de conversión se conoce en tiempo de compilación y el compilador de C ++ puede invocar directamente el analizador correcto. Lo mismo ocurre con la salida. Incluso se asegura de escribir una llamada separada paraprintf("%s","\n");
, pero el compilador de C es lo suficientemente bueno como para compilar esto como una llamada aputchar('\n');
.Entonces, considerando la complejidad tanto de la E / S como del cálculo, la versión C ++ debería ser más rápida que la versión C.
La desactivación completa del almacenamiento en búfer
stdout
ralentiza la implementación de C a algo incluso más lento que la versión de C ++. Otra prueba de AlexLop con unfflush(stdout);
después de la últimaprintf
arroja un rendimiento similar al de la versión C ++. No es tan lento como deshabilitar completamente el almacenamiento en búfer porque la salida se escribe en el sistema en pequeños fragmentos en lugar de un byte a la vez.Esto parece apuntar a un comportamiento específico en su biblioteca C ++: sospecho que la implementación de su sistema de
cin
ycout
descarga la salidacout
cuando se solicita la entradacin
. Algunas bibliotecas de C también hacen esto, pero generalmente solo cuando se lee / escribe desde y hacia la terminal. La evaluación comparativa realizada por el sitio www.spoj.com probablemente redirige la entrada y salida hacia y desde archivos.AlexLop hizo otra prueba: leer todas las entradas a la vez en un vector y luego calcular y escribir toda la salida ayuda a comprender por qué la versión C ++ es mucho más lenta. Aumenta el rendimiento al de la versión C, esto prueba mi punto y elimina las sospechas sobre el código de formato C ++.
Otra prueba de Blastfurnace, que almacena todas las salidas en una
std::ostringstream
y la descarga de una sola vez al final, mejora el rendimiento de C ++ al de la versión básica de C. QED.PD: su algoritmo es incorrecto
fact_num >= UINT_MAX / 5
porquefives *= 5
se desbordará y se ajustará antes de que se convierta> fact_num
. Puede corregir esto haciendofives
ununsigned long
o ununsigned long long
si uno de estos tipos es mayor queunsigned int
. También se usa%u
comoscanf
formato. Tienes suerte de que los chicos de www.spoj.com no sean demasiado estrictos en sus puntos de referencia.EDITAR: Como se explica más adelante por vitaux, este comportamiento es de hecho obligatorio por el estándar C ++.
cin
está vinculadocout
de forma predeterminada. Una operación de entradacin
para la que el búfer de entrada necesita rellenarse harácout
que se vacíe la salida pendiente. En la implementación del OP,cin
parece vaciarcout
sistemáticamente, lo cual es un poco excesivo y visiblemente ineficiente.Ilya Popov proporcionó una solución simple para esto:
cin
se puede desatarcout
lanzando otro hechizo mágico además destd::ios_base::sync_with_stdio(false);
:También tenga en cuenta que tal descarga forzada también ocurre cuando se usa en
std::endl
lugar de'\n'
producir un final de línea encendidocout
. Cambiar la línea de salida a una aparienciacout << num_of_trailing_zeros << endl;
más idiomática e inocente de C ++ degradaría el rendimiento de la misma manera.fuente
std::ostringstream
y enviarla toda una vez al final reduce el tiempo a la paridad con la versión C.ostringstream
para la salida y me dio Time 0.02 QED :). Respecto alfact_num >= UINT_MAX / 5
, ¡BUEN punto!vector
luego procesar los cálculos (sinostringstream
) da el mismo resultado! Tiempo 0.02. Combinar ambosvector
yostringstream
no lo mejora más. Mismo tiempo 0.02sizeof(int) == sizeof(long long)
es la siguiente: agregue una prueba en el cuerpo del bucle despuésnum_of_trailing_zeros += fact_num/fives;
para verificar sifives
ha alcanzado su máximo:if (fives > UINT_MAX / 5) break;
Otro truco para hacer
iostream
s más rápido cuando usas amboscin
ycout
es llamarcin.tie(nullptr);
De forma predeterminada, cuando ingresa algo de
cin
, se vacíacout
. Puede dañar significativamente el rendimiento si realiza entradas y salidas intercaladas. Esto se hace para los usos de la interfaz de línea de comandos, donde muestra un mensaje y luego espera los datos:std::string name; cout << "Enter your name:"; cin >> name;
En este caso, desea asegurarse de que el mensaje se muestre realmente antes de comenzar a esperar la entrada. Con la línea de arriba rompes ese lazo
cin
y te vuelvescout
independiente.Desde C ++ 11, una forma más de lograr un mejor rendimiento con iostreams es usar
std::getline
junto constd::stoi
, así:std::string line; for (int i = 0; i < n && std::getline(std::cin, line); ++i) { int x = std::stoi(line); }
De esta manera puede acercarse al estilo C en rendimiento, o incluso superarlo
scanf
. El uso degetchar
y especialmentegetchar_unlocked
junto con el análisis escrito a mano todavía proporciona un mejor rendimiento.PD. He escrito una publicación comparando varias formas de ingresar números en C ++, útil para jueces en línea, pero solo está en ruso, lo siento. Sin embargo, las muestras de código y la mesa final deberían ser comprensibles.
fuente
std::readline
ystd::stoi
no es funcionalmente equivalente al código de OP. Amboscin >> x;
yscanf("%f", &x);
aceptan el espacio en blanco de la hormiga como separador, puede haber varios números en la misma línea.El problema es que, citando cppreference :
Esto es fácil de probar: si reemplaza
cin >> fact_num;
con
scanf("%d", &fact_num);
y lo mismo para,
cin >> num_of_inputs
pero mantengacout
, obtendrá prácticamente el mismo rendimiento en su versión C ++ (o, mejor dicho, versión IOStream) que en C uno:Lo mismo sucede si mantiene
cin
pero reemplazacout << num_of_trailing_zeros << "\n";
con
printf("%d", num_of_trailing_zeros); printf("%s","\n");
Una solución simple es desatar
cout
ycin
como lo menciona Ilya Popov:cin.tie(nullptr);
Las implementaciones de bibliotecas estándar pueden omitir la llamada a flush en ciertos casos, pero no siempre. Aquí hay una cita de C ++ 14 27.7.2.1.3 (gracias a chqrlie):
fuente
is.tie()
no es un puntero nulo, la función llamais.tie()->flush()
para sincronizar la secuencia de salida con cualquier flujo C externo asociado. Excepto que esta llamada se puede suprimir si el área de venta deis.tie()
está vacía. Además, se permite una implementación para aplazar la llamada a flush hasta que seis.rdbuf()->underflow()
produzca una llamada de . Si no se produce dicha llamada antes de que se destruya el objeto centinela, la llamada a flush puede eliminarse por completo.