Tomé el Problema # 12 del Proyecto Euler como un ejercicio de programación y para comparar mis implementaciones (seguramente no óptimas) en C, Python, Erlang y Haskell. Para obtener tiempos de ejecución más altos, busco el primer número de triángulo con más de 1000 divisores en lugar de 500 como se indica en el problema original.
El resultado es el siguiente:
C:
lorenzo@enzo:~/erlang$ gcc -lm -o euler12.bin euler12.c
lorenzo@enzo:~/erlang$ time ./euler12.bin
842161320
real 0m11.074s
user 0m11.070s
sys 0m0.000s
Pitón:
lorenzo@enzo:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
Python con PyPy:
lorenzo@enzo:~/Downloads/pypy-c-jit-43780-b590cf6de419-linux64/bin$ time ./pypy /home/lorenzo/erlang/euler12.py
842161320
real 0m13.082s
user 0m13.050s
sys 0m0.020s
Erlang:
lorenzo@enzo:~/erlang$ erlc euler12.erl
lorenzo@enzo:~/erlang$ time erl -s euler12 solve
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.4 (abort with ^G)
1> 842161320
real 0m48.259s
user 0m48.070s
sys 0m0.020s
Haskell
lorenzo@enzo:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main ( euler12.hs, euler12.o )
Linking euler12.hsx ...
lorenzo@enzo:~/erlang$ time ./euler12.hsx
842161320
real 2m37.326s
user 2m37.240s
sys 0m0.080s
Resumen:
- C: 100%
- Python: 692% (118% con PyPy)
- Erlang: 436% (135% gracias a RichardC)
- Haskell: 1421%
Supongo que C tiene una gran ventaja ya que usa largos para los cálculos y no enteros de longitud arbitraria como los otros tres. Además, no necesita cargar un tiempo de ejecución primero (¿los otros?).
Pregunta 1:
¿Erlang, Python y Haskell pierden velocidad debido al uso de enteros de longitud arbitraria o no, siempre que los valores sean menores MAXINT
?
Pregunta 2: ¿Por qué Haskell es tan lento? ¿Hay una bandera del compilador que apaga los frenos o es mi implementación? (Esto último es bastante probable ya que Haskell es un libro con siete sellos para mí).
Pregunta 3: ¿Puede ofrecerme algunas sugerencias sobre cómo optimizar estas implementaciones sin cambiar la forma en que determino los factores? Optimización de cualquier forma: más agradable, más rápida, más "nativa" del idioma.
EDITAR:
Pregunta 4: ¿Mis implementaciones funcionales permiten LCO (optimización de última llamada, también conocida como eliminación de recursión de cola) y, por lo tanto, evitan agregar marcos innecesarios en la pila de llamadas?
Realmente intenté implementar el mismo algoritmo lo más similar posible en los cuatro idiomas, aunque tengo que admitir que mi conocimiento de Haskell y Erlang es muy limitado.
Códigos fuente utilizados:
#include <stdio.h>
#include <math.h>
int factorCount (long n)
{
double square = sqrt (n);
int isquare = (int) square;
int count = isquare == square ? -1 : 0;
long candidate;
for (candidate = 1; candidate <= isquare; candidate ++)
if (0 == n % candidate) count += 2;
return count;
}
int main ()
{
long triangle = 1;
int index = 1;
while (factorCount (triangle) < 1001)
{
index ++;
triangle += index;
}
printf ("%ld\n", triangle);
}
#! /usr/bin/env python3.2
import math
def factorCount (n):
square = math.sqrt (n)
isquare = int (square)
count = -1 if isquare == square else 0
for candidate in range (1, isquare + 1):
if not n % candidate: count += 2
return count
triangle = 1
index = 1
while factorCount (triangle) < 1001:
index += 1
triangle += index
print (triangle)
-module (euler12).
-compile (export_all).
factorCount (Number) -> factorCount (Number, math:sqrt (Number), 1, 0).
factorCount (_, Sqrt, Candidate, Count) when Candidate > Sqrt -> Count;
factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;
factorCount (Number, Sqrt, Candidate, Count) ->
case Number rem Candidate of
0 -> factorCount (Number, Sqrt, Candidate + 1, Count + 2);
_ -> factorCount (Number, Sqrt, Candidate + 1, Count)
end.
nextTriangle (Index, Triangle) ->
Count = factorCount (Triangle),
if
Count > 1000 -> Triangle;
true -> nextTriangle (Index + 1, Triangle + Index + 1)
end.
solve () ->
io:format ("~p~n", [nextTriangle (1, 1) ] ),
halt (0).
factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
where square = sqrt $ fromIntegral number
isquare = floor square
factorCount' number sqrt candidate count
| fromIntegral candidate > sqrt = count
| number `mod` candidate == 0 = factorCount' number sqrt (candidate + 1) (count + 2)
| otherwise = factorCount' number sqrt (candidate + 1) count
nextTriangle index triangle
| factorCount triangle > 1000 = triangle
| otherwise = nextTriangle (index + 1) (triangle + index + 1)
main = print $ nextTriangle 1 1
Euler12[x_Integer] := Module[{s = 1}, For[i = 2, DivisorSigma[0, s] < x, i++, s += i]; s]
. ¡Hurra!Respuestas:
Usando
GHC 7.0.3
,gcc 4.4.6
,Linux 2.6.29
en una Core2 Duo máquina x86_64 (2,5 GHz), la compilación usandoghc -O2 -fllvm -fforce-recomp
para Haskell ygcc -O3 -lm
para C.-O3
)-O2
bandera)factorCount'
código no está escrito de manera explícita y por defectoInteger
(¡gracias a Daniel por corregir mi diagnóstico erróneo aquí!). Dar una firma de tipo explícito (que es la práctica estándar de todos modos) usandoInt
y el tiempo cambia a 11.1 segundosfactorCount'
ti has llamado innecesariamentefromIntegral
. Sin embargo, una solución no produce cambios (el compilador es inteligente, por suerte para usted)mod
donderem
es más rápido y suficiente. Esto cambia el tiempo a 8,5 segundos .factorCount'
aplica constantemente dos argumentos adicionales que nunca cambian (number
,sqrt
). Una transformación trabajador / envoltorio nos da:Así es, 7.95 segundos . Consistentemente medio segundo más rápido que la solución C . Sin la
-fllvm
bandera que sigo recibiendo8.182 seconds
, por lo que el backend de NCG también funciona bien en este caso.Conclusión: Haskell es asombroso.
Código resultante
EDITAR: ahora que hemos explorado eso, abordemos las preguntas
En Haskell, el uso
Integer
es más lento que,Int
pero cuánto más lento depende de los cálculos realizados. Afortunadamente (para máquinas de 64 bits)Int
es suficiente. Por razones de portabilidad, probablemente debería volver a escribir mi código para usarInt64
oWord64
(C no es el único lenguaje con along
).Eso fue lo que respondí arriba. La respuesta fue
-O2
rem
nomod
(una optimización frecuentemente olvidada) ySí, ese no era el problema. Buen trabajo y me alegro de que hayas considerado esto.
fuente
rem
es en realidad un subcomponente de lamod
operación (no son lo mismo). Si busca en la biblioteca Base de GHC, verámod
pruebas para varias condiciones y ajusta el signo en consecuencia. (vermodInt#
enBase.lhs
)mod
conrem
después de leer esta respuesta (je, vaya). Vea el enlace para mis horarios, pero la versión corta es "casi idéntica a la C".factorCount'
era recursivo de cola, habría pensado que el compilador podría detectar los parámetros adicionales que no se cambian y optimizar la recursión de cola solo para los parámetros cambiantes (después de todo, Haskell es un lenguaje puro, esto debería ser fácil). ¿Alguien piensa que el compilador podría hacer eso o debería volver a leer más documentos de teoría?Hay algunos problemas con la implementación de Erlang. Como referencia para lo siguiente, mi tiempo de ejecución medido para su programa Erlang no modificado fue de 47.6 segundos, en comparación con 12.7 segundos para el código C.
Lo primero que debe hacer si desea ejecutar código Erlang computacionalmente intensivo es usar código nativo. Compilar con
erlc +native euler12
el tiempo se redujo a 41,3 segundos. Sin embargo, esta es una aceleración mucho menor (solo 15%) de lo esperado de la compilación nativa en este tipo de código, y el problema es su uso-compile(export_all)
. Esto es útil para la experimentación, pero el hecho de que todas las funciones sean potencialmente accesibles desde el exterior hace que el compilador nativo sea muy conservador. (El emulador BEAM normal no se ve tan afectado). Reemplazar esta declaración por-export([solve/0]).
una aceleración mucho mejor: 31.5 segundos (casi 35% desde la línea de base).Pero el código en sí tiene un problema: para cada iteración en el bucle factorCount, realiza esta prueba:
El código C no hace esto. En general, puede ser complicado hacer una comparación equitativa entre diferentes implementaciones del mismo código, y en particular si el algoritmo es numérico, porque debe asegurarse de que realmente estén haciendo lo mismo. Un ligero error de redondeo en una implementación debido a algún tipo de conversión en alguna parte puede hacer que realice muchas más iteraciones que la otra, aunque ambas finalmente alcanzan el mismo resultado.
Para eliminar esta posible fuente de error (y deshacerme de la prueba adicional en cada iteración), reescribí la función factorCount de la siguiente manera, estrechamente modelada en el código C:
Esta
export_all
compilación de reescritura, no y nativa, me dio el siguiente tiempo de ejecución:que no es tan malo en comparación con el código C:
teniendo en cuenta que Erlang no está orientado en absoluto a escribir código numérico, es solo un 50% más lento que C en un programa como este es bastante bueno.
Finalmente, con respecto a sus preguntas:
Pregunta 1: ¿Erlang, Python y Haskell pierden velocidad debido al uso de enteros de longitud arbitraria o no, siempre que los valores sean inferiores a MAXINT?
Si un poco. En Erlang, no hay forma de decir "usar aritmética de 32/64 bits con ajuste", así que a menos que el compilador pueda probar algunos límites en sus enteros (y generalmente no puede), debe verificar todos los cálculos para ver si pueden caber en una sola palabra etiquetada o si tiene que convertirlos en bignums asignados en el montón. Incluso si nunca se usan bignums en la práctica en tiempo de ejecución, estas comprobaciones deberán realizarse. Por otro lado, eso significa que sabe que el algoritmo nunca fallará debido a una envoltura entera inesperada si de repente le da entradas más grandes que antes.
Pregunta 4: ¿Mis implementaciones funcionales permiten LCO y, por lo tanto, evitan agregar marcos innecesarios a la pila de llamadas?
Sí, su código Erlang es correcto con respecto a la optimización de la última llamada.
fuente
En lo que respecta a la optimización de Python, además de usar PyPy (para aceleraciones bastante impresionantes con cero cambios en su código), puede usar la cadena de herramientas de traducción de PyPy para compilar una versión compatible con RPython, o Cython para construir un módulo de extensión, ambos que son más rápidos que la versión C en mis pruebas, con el módulo Cython casi el doble de rápido . Como referencia también incluyo los resultados de referencia de C y PyPy:
C (compilado con
gcc -O3 -lm
)PyPy 1.5
RPython (usando la última revisión de PyPy
c2f583445aee
)Cython 0.15
La versión RPython tiene un par de cambios clave. Para traducir a un programa independiente, necesita definir su
target
, que en este caso es lamain
función. Se espera que aceptesys.argv
como único argumento, y se requiere que devuelva un int. Puede traducirlo utilizando translate.py,% translate.py euler12-rpython.py
que se traduce a C y lo compila por usted.La versión de Cython fue reescrita como un módulo de extensión
_euler12.pyx
, que importo y llamo desde un archivo normal de Python. El_euler12.pyx
es esencialmente la misma que la versión, con algunas declaraciones de tipo estáticas adicionales. El setup.py tiene la plantilla normal para construir la extensión, usandopython setup.py build_ext --inplace
.Sinceramente, tengo muy poca experiencia con RPython o Cython, y me sorprendieron gratamente los resultados. Si está utilizando CPython, escribir sus bits de código intensivos en CPU en un módulo de extensión de Cython parece una forma realmente fácil de optimizar su programa.
fuente
La implementación de C es subóptima (como lo insinuó Thomas M. DuBuisson), la versión usa enteros de 64 bits (es decir, tipo de datos largo ). Investigaré la lista de ensamblaje más tarde, pero con una suposición educada, hay algunos accesos a la memoria en el código compilado, lo que hace que el uso de enteros de 64 bits sea significativamente más lento. Es eso o el código generado (ya sea el hecho de que puede colocar menos entradas de 64 bits en un registro SSE o redondear un número entero de doble a 64 bits es más lento).
Aquí está el código modificado (simplemente reemplace long con int y explícitamente incluí factorCount, aunque no creo que sea necesario con gcc -O3):
Correr + sincronización da:
Como referencia, la implementación de haskell por Thomas en la respuesta anterior da:
Conclusión: sin quitarle nada a ghc, es un gran compilador, pero gcc normalmente genera un código más rápido.
fuente
2.5 seconds
mientras que una modificación similar al código Haskell (pasar a Word32, agregar pragma INLINE) da como resultado un tiempo de ejecución de4.8 seconds
. Tal vez se pueda hacer algo (al parecer, no de manera trivial): el resultado de gcc es ciertamente impresionante.Echa un vistazo a este blog . Durante el año pasado, más o menos, ha solucionado algunos de los problemas del Proyecto Euler en Haskell y Python, y generalmente ha descubierto que Haskell es mucho más rápido. Creo que entre esos idiomas tiene más que ver con tu fluidez y estilo de codificación.
Cuando se trata de la velocidad de Python, ¡estás usando una implementación incorrecta! Pruebe PyPy , y para cosas como esta, encontrará que es mucho, mucho más rápido.
fuente
Su implementación de Haskell podría acelerarse enormemente utilizando algunas funciones de los paquetes de Haskell. En este caso usé primos, que se acaba de instalar con 'cabal install primes';)
Tiempos:
Tu programa original:
Implementación mejorada
Como puede ver, este se ejecuta en 38 milisegundos en la misma máquina donde la suya se ejecutó en 16 segundos :)
Comandos de compilación:
fuente
Solo por diversión. La siguiente es una implementación de Haskell más 'nativa':
Utilizando
ghc -O3
, esto se ejecuta constantemente en 0.55-0.58 segundos en mi máquina (1.73GHz Core i7).Una función factorCount más eficiente para la versión C:
Cambiando longs a ints en main, usando
gcc -O3 -lm
, esto se ejecuta consistentemente en 0.31-0.35 segundos.Se puede hacer que ambos funcionen aún más rápido si aprovecha el hecho de que el enésimo número de triángulo = n * (n + 1) / 2, yn y (n + 1) tienen factorizaciones primas completamente dispares, por lo que el número de factores de cada mitad se puede multiplicar para encontrar el número de factores del todo. El seguimiento:
reducirá el tiempo de ejecución del código c a 0.17-0.19 segundos, y puede manejar búsquedas mucho más grandes: más de 10000 factores toman alrededor de 43 segundos en mi máquina. Dejo una aceleración de haskell similar para el lector interesado.
fuente
Esto es poco probable No puedo decir mucho sobre Erlang y Haskell (bueno, tal vez un poco sobre Haskell a continuación) pero puedo señalar muchos otros cuellos de botella en Python. Cada vez que el programa intenta ejecutar una operación con algunos valores en Python, debe verificar si los valores son del tipo adecuado y cuesta un poco de tiempo. Su
factorCount
función solo asigna una lista conrange (1, isquare + 1)
varias veces, ymalloc
la asignación de memoria con estilo de tiempo de ejecución es mucho más lenta que iterar en un rango con un contador como lo hace en C. Notablemente,factorCount()
se llama varias veces y, por lo tanto, asigna muchas listas. Además, no olvidemos que Python se interpreta y que el intérprete de CPython no tiene un gran enfoque en la optimización.EDITAR : oh, bueno, noto que está utilizando Python 3, por lo
range()
que no devuelve una lista, sino un generador. En este caso, mi punto sobre la asignación de listas es medio erróneo: la función solo asignarange
objetos, que no obstante son ineficientes pero no tan ineficientes como asignar una lista con muchos elementos.¿Estás usando abrazos ? Hugs es un intérprete considerablemente lento. Si lo está utilizando, tal vez pueda obtener un mejor momento con GHC , pero solo estoy reflexionando sobre la hipotesis, el tipo de cosas que un buen compilador de Haskell hace bajo el capó es bastante fascinante y está más allá de mi comprensión :)
Yo diría que estás jugando un juego divertido. La mejor parte de conocer varios idiomas es usarlos de la manera más diferente posible :) Pero estoy divagando, simplemente no tengo ninguna recomendación para este punto. Lo siento, espero que alguien pueda ayudarte en este caso :)
Hasta donde recuerdo, solo necesita asegurarse de que su llamada recursiva sea el último comando antes de devolver un valor. En otras palabras, una función como la siguiente podría usar dicha optimización:
Sin embargo, no tendría tal optimización si su función fuera como la siguiente, porque hay una operación (multiplicación) después de la llamada recursiva:
Separé las operaciones en algunas variables locales para dejar en claro qué operaciones se ejecutan. Sin embargo, lo más habitual es ver estas funciones a continuación, pero son equivalentes para el punto que estoy haciendo:
Tenga en cuenta que depende del compilador / intérprete decidir si hará una recursión de cola. Por ejemplo, el intérprete de Python no lo hace si recuerdo bien (usé Python en mi ejemplo solo por su sintaxis fluida). De todos modos, si encuentra cosas extrañas como funciones factoriales con dos parámetros (y uno de los parámetros tiene nombres como
acc
,accumulator
etc.) ahora sabe por qué la gente lo hace :)fuente
Con Haskell, realmente no necesita pensar explícitamente en las recursiones.
En el código anterior, he reemplazado las recurrencias explícitas en la respuesta de @Thomas con operaciones de lista comunes. El código sigue haciendo exactamente lo mismo sin que nos preocupemos por la recursividad de la cola. Se ejecuta (~ 7.49s ) aproximadamente un 6% más lento que la versión en la respuesta de @Thomas (~ 7.04s ) en mi máquina con GHC 7.6.2, mientras que la versión C de @Raedwulf funciona con ~ 3.15s . Parece que GHC ha mejorado durante el año.
PD. Sé que es una vieja pregunta, y me topé con ella en las búsquedas de Google (olvidé lo que estaba buscando, ahora ...). Solo quería comentar la pregunta sobre LCO y expresar mis sentimientos sobre Haskell en general. Quería comentar sobre la respuesta principal, pero los comentarios no permiten bloques de código.
fuente
Algunos números más y explicaciones para la versión C. Al parecer, nadie lo hizo durante todos esos años. Recuerde votar esta respuesta para que todos puedan ver y aprender.
Paso uno: Benchmark de los programas del autor.
Especificaciones de la computadora portátil:
Comandos:
.
Los nombres de archivo son:
integertype_architecture_compiler.exe
Paso dos: investigue, mejore y vuelva a comparar
VS es 250% más rápido que gcc. Los dos compiladores deberían dar una velocidad similar. Obviamente, algo está mal con el código o las opciones del compilador. ¡A investigar!
El primer punto de interés son los tipos enteros. Las conversiones pueden ser costosas y la consistencia es importante para una mejor generación de código y optimizaciones. Todos los enteros deben ser del mismo tipo.
Es un desastre mixto
int
y enlong
este momento. Vamos a mejorar eso. ¿Qué tipo usar? El más rápido. ¡Tengo que compararlos a todos!Los tipos enteros son
int
long
int32_t
uint32_t
int64_t
yuint64_t
de#include <stdint.h>
Hay MUCHOS tipos de enteros en C, más algunos con y sin signo para jugar, más la opción de compilar como x86 o x64 (no confundir con el tamaño entero real). Esas son muchas versiones para compilar y ejecutar ^^
Paso tres: comprender los números
Conclusiones definitivas:
Pregunta capciosa: "¿Cuáles son los tamaños de int y long en C?"
La respuesta correcta es: ¡ El tamaño de int y long en C no está bien definido!
De la especificación C:
Desde la página de manual de gcc (banderas -m32 y -m64):
De la documentación de MSDN (rangos de tipo de datos) https://msdn.microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx :
Para concluir esto: lecciones aprendidas
Los enteros de 32 bits son más rápidos que los enteros de 64 bits.
Los tipos enteros estándar no están bien definidos en C ni C ++, varían según los compiladores y las arquitecturas. Cuando necesite consistencia y previsibilidad, use la
uint32_t
familia entera de#include <stdint.h>
.Problemas de velocidad resueltos. Todos los demás lenguajes han retrocedido cientos por ciento, ¡C & C ++ vuelve a ganar! Ellos siempre lo hacen. La próxima mejora será multihilo con OpenMP: D
fuente
INT_MIN
yINT_MAX
(-32767 y 32767, que prácticamente imponen un requisito deint
al menos 16 bits).long
se requiere que sea al menos tan grande como anint
, y el promedio de requisitos de rangolong
es de al menos 32 bits.Mirando su implementación de Erlang. El tiempo ha incluido el inicio de toda la máquina virtual, ejecutar su programa y detener la máquina virtual. Estoy bastante seguro de que configurar y detener el erlang vm lleva algún tiempo.
Si el tiempo se realizó dentro de la máquina virtual de erlang, los resultados serían diferentes ya que en ese caso tendríamos el tiempo real solo para el programa en cuestión. De lo contrario, creo que el tiempo total que lleva el proceso de inicio y carga de Erlang Vm más el de detenerlo (como lo incluyó en su programa) se incluyen en el tiempo total que utiliza el método para cronometrar El programa está saliendo. Considere usar el tiempo de erlang mismo que usamos cuando queremos cronometrar nuestros programas dentro de la máquina virtual
timer:tc/1 or timer:tc/2 or timer:tc/3
. De esta manera, los resultados de erlang excluirán el tiempo necesario para iniciar y detener / matar / detener la máquina virtual. Ese es mi razonamiento allí, piénselo y luego intente su punto de referencia nuevamente.De hecho, sugiero que intentemos cronometrar el programa (para los idiomas que tienen un tiempo de ejecución), dentro del tiempo de ejecución de esos idiomas para obtener un valor preciso. C, por ejemplo, no tiene sobrecarga de iniciar y apagar un sistema de tiempo de ejecución al igual que Erlang, Python y Haskell (98% seguro de esto, estoy de acuerdo con la corrección). Entonces (basado en este razonamiento) concluyo diciendo que este punto de referencia no era lo suficientemente preciso / justo para los idiomas que se ejecutan sobre un sistema de tiempo de ejecución. Hagámoslo nuevamente con estos cambios.
EDITAR: además, incluso si todos los idiomas tuvieran sistemas de tiempo de ejecución, la sobrecarga de iniciar cada uno y detenerlo sería diferente. así que sugiero que tomemos el tiempo desde los sistemas de tiempo de ejecución (para los idiomas para los que esto se aplica). ¡Se sabe que Erlang VM tiene una sobrecarga considerable en el arranque!
fuente
La pregunta uno se puede responder negativamente para Erlang. La última pregunta se responde usando Erlang adecuadamente, como en:
http://bredsaal.dk/learning-erlang-using-projecteuler-net
Como es más rápido que su ejemplo inicial de C, supongo que hay numerosos problemas, ya que otros ya lo han cubierto en detalle.
Este módulo Erlang se ejecuta en una netbook barata en aproximadamente 5 segundos ... Utiliza el modelo de hilos de red en erlang y, como tal, muestra cómo aprovechar el modelo de eventos. Se podría distribuir en muchos nodos. Y es rapido. No es mi codigo.
La prueba a continuación se realizó en una: Intel (R) Atom (TM) CPU N270 @ 1.60GHz
fuente
C ++ 11, <20 ms para mí : ejecútelo aquí
Entiendo que desea consejos para ayudar a mejorar su conocimiento específico del idioma, pero dado que eso se ha cubierto bien aquí, pensé que agregaría un contexto para las personas que pudieron haber visto el comentario matemático sobre su pregunta, etc., y me pregunté por qué esto El código fue mucho más lento.
Esta respuesta es principalmente para proporcionar contexto para ayudar a las personas a evaluar el código en su pregunta / otras respuestas más fácilmente.
Este código usa solo un par de optimizaciones (feas), no relacionadas con el lenguaje utilizado, basado en:
Eso toma alrededor de 19 ms en promedio para mi escritorio y 80 ms para mi computadora portátil, muy lejos de la mayoría del otro código que he visto aquí. Y hay, sin duda, muchas optimizaciones aún disponibles.
fuente
Intentando IR:
Yo obtengo:
versión original c: 9.1690 100%
ir: 8.2520 111%
Pero usando:
Yo obtengo:
versión c original: 9.1690 100%
versión c de thaumkid: 0.1060 8650%
primera versión de versión: 8.2520 111%
segunda versión de versión: 0.0230 39865%
También probé Python3.6 y pypy3.3-5.5-alpha:
versión c original: 8.629 100%
versión c de thaumkid: 0.109 7916%
Python3.6: 54.795 16%
pypy3.3-5.5-alpha: 13.291 65%
y luego con el siguiente código obtuve:
versión c original: 8.629 100%
versión c de thaumkid: 0.109 8650%
Python3.6: 1.489 580%
pypy3.3-5.5-alpha: 0.582 1483%
fuente
Cambio:
case (divisor(T,round(math:sqrt(T))) > 500) of
A:
case (divisor(T,round(math:sqrt(T))) > 1000) of
Esto producirá la respuesta correcta para el ejemplo multiproceso de Erlang.
fuente
Supuse que el número de factores solo es grande si los números involucrados tienen muchos factores pequeños. Así que utilicé el excelente algoritmo de thaumkid, pero primero usé una aproximación al conteo de factores que nunca es demasiado pequeño. Es bastante simple: verifique los factores primos hasta 29, luego verifique el número restante y calcule un límite superior para el número de factores. Use esto para calcular un límite superior para el número de factores, y si ese número es lo suficientemente alto, calcule el número exacto de factores.
El siguiente código no necesita esta suposición para la corrección, sino para ser rápido. Parece funcionar; solo alrededor de uno de cada 100,000 números da una estimación lo suficientemente alta como para requerir una verificación completa.
Aquí está el código:
Esto encuentra el número triangular 14,753,024 con 13824 factores en aproximadamente 0,7 segundos, el número triangular 879,207,615 con 61,440 factores en 34 segundos, el número triangular 12,524,486,975 con factores 138,240 en 10 minutos y 5 segundos, y el número triangular 26,467,792,064 con 172,032 factores en 21 minutos 25 segundos (Core2 Duo a 2,4 GHz), por lo que este código solo toma 116 ciclos de procesador por número en promedio. El último número triangular en sí es mayor que 2 ^ 68, por lo que
fuente
Modifiqué la versión de "Jannich Brendle" a 1000 en lugar de 500. Y enumere el resultado de euler12.bin, euler12.erl, p12dist.erl. Ambos códigos erl usan '+ native' para compilar.
fuente
gcc -lm -Ofast euler.c
tiempo ./a.out
2.79s usuario 0.00s sistema 99% cpu 2.794 total
fuente