프로젝트 오일러와 속도 비교 : C vs Python vs Erlang vs Haskell
저는 Project Euler 에서 문제 # 12 를 프로그래밍 실습으로 가져와 C, Python, Erlang 및 Haskell의 구현을 비교했습니다. 더 높은 실행 시간을 얻기 위해 원래 문제에서 설명한 것처럼 500 대신 1000 이상의 제수가있는 첫 번째 삼각형 숫자를 검색합니다.
결과는 다음과 같습니다.
씨:
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
파이썬 :
lorenzo@enzo:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
PyPy를 사용한 Python :
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
얼랭 :
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
요약:
- C : 100 %
- Python : 692 % (PyPy 사용시 118 %)
- Erlang : 436 % (RichardC 덕분에 135 %)
- 하스켈 : 1421 %
나는 C가 다른 세 가지와 같이 임의의 길이 정수가 아닌 계산에 long을 사용하므로 큰 이점이 있다고 가정합니다. 또한 런타임을 먼저로드 할 필요가 없습니다 (다른 작업은 수행합니까?).
질문 1 : Erlang, Python 및 Haskell은 임의의 길이 정수를 사용하여 속도를 잃거나 값이보다 작 으면 속도를 잃지 MAXINT
않습니까?
질문 2 : Haskell이 왜 그렇게 느린가요? 브레이크를 끄는 컴파일러 플래그가 있습니까 아니면 내 구현입니까? (하스켈이 나에게 일곱 개의 봉인이있는 책이기 때문에 후자는 꽤 가능성이있다.)
질문 3 : 요인을 결정하는 방식을 변경하지 않고 이러한 구현을 최적화하는 방법에 대한 힌트를 제공 할 수 있습니까? 어떤 방식 으로든 최적화 : 더 좋고, 더 빠르고, 더 "기본"언어.
편집하다:
질문 4 : 내 기능 구현에서 LCO (마지막 호출 최적화, 꼬리 재귀 제거라고도 함)를 허용하므로 불필요한 프레임을 호출 스택에 추가하지 않습니까?
나는 Haskell과 Erlang에 대한 지식이 매우 제한적이라는 것을 인정해야하지만 4 개 언어로 가능한 한 비슷한 동일한 알고리즘을 구현하려고했습니다.
사용 된 소스 코드 :
#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
사용하여 GHC 7.0.3
, gcc 4.4.6
, Linux 2.6.29
, x86_64의 코어 2 듀오 (2.5GHz의) 시스템에서 사용 컴파일 ghc -O2 -fllvm -fforce-recomp
하스켈과 gcc -O3 -lm
C.에 대한
- C 루틴은 8.4 초 안에 실행됩니다 (아마도으로 인해 실행하는 것보다 빠름
-O3
). - Haskell 솔루션은 36 초만에 실행됩니다 (
-O2
플래그 로 인해 ). - 귀하의
factorCount'
코드는 명시 적으로 입력되지 않았고 기본값으로 설정되어 있지 않습니다Integer
(여기서 내 오진을 수정 한 Daniel에게 감사드립니다!). 사용Int
하고 시간이 11.1 초로 변경 되는 명시 적 유형 서명 (어쨌든 표준 방식) 제공 - 에
factorCount'
당신 불필요하게 불렀다fromIntegral
. 그러나 수정 사항은 변경되지 않습니다 (컴파일러는 똑똑하고 운이 좋습니다). - 더 빠르고 충분한
mod
곳을 사용했습니다rem
. 이렇게하면 시간이 8.5 초로 변경됩니다 . factorCount'
변경되지 않는 두 개의 추가 인수 (number
,sqrt
)를 지속적으로 적용 합니다. 작업자 / 래퍼 변환은 다음을 제공합니다.
$ time ./so
842161320
real 0m7.954s
user 0m7.944s
sys 0m0.004s
맞습니다. 7.95 초 . C 솔루션보다 지속적으로 0.5 초 더 빠릅니다 . -fllvm
플래그 없이 나는 여전히을 얻고 8.182 seconds
있으므로 NCG 백엔드는이 경우에도 잘 작동합니다.
결론 : Haskell은 굉장합니다.
결과 코드
factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
where square = sqrt $ fromIntegral number
isquare = floor square
factorCount' :: Int -> Int -> Int -> Int -> Int
factorCount' number sqrt candidate0 count0 = go candidate0 count0
where
go candidate count
| candidate > sqrt = count
| number `rem` candidate == 0 = go (candidate + 1) (count + 2)
| otherwise = go (candidate + 1) count
nextTriangle index triangle
| factorCount triangle > 1000 = triangle
| otherwise = nextTriangle (index + 1) (triangle + index + 1)
main = print $ nextTriangle 1 1
편집 : 이제 우리가 그것을 탐구 했으므로 질문에 답해 보겠습니다.
질문 1 : erlang, python 및 haskell은 임의의 길이 정수를 사용하여 속도를 잃거나 값이 MAXINT보다 작 으면 속도를 잃지 않습니까?
Haskell에서 사용 Integer
은 Int
수행하는 계산에 따라 속도 가 더 느리지 만 얼마나 느려질 지에 따라 다릅니다. 다행히 (64 비트 머신의 경우) Int
충분합니다. 이식성을 위해 아마도 내 코드를 다시 작성 Int64
하거나 Word64
(C는를 가진 유일한 언어가 아닙니다 long
).
질문 2 : 하스켈이 왜 그렇게 느린가요? 브레이크를 끄는 컴파일러 플래그가 있습니까 아니면 내 구현입니까? (하스켈은 나에게 일곱 개의 봉인이있는 책이기 때문에 후자는 꽤 가능성이 있습니다.)
질문 3 : 요인을 결정하는 방식을 변경하지 않고 이러한 구현을 최적화하는 방법에 대한 힌트를 제공 할 수 있습니까? 어떤 방식 으로든 최적화 : 더 좋고, 더 빠르고, 더 "기본"언어.
그것이 내가 위에서 대답 한 것입니다. 대답은
- 0) 다음을 통해 최적화 사용
-O2
- 1) 가능하면 빠른 (특히 : unbox-able) 유형 사용
- 2)
rem
notmod
(자주 잊혀진 최적화) - 3) 작업자 / 래퍼 변환 (아마도 가장 일반적인 최적화).
질문 4 : 기능 구현이 LCO를 허용하므로 불필요한 프레임을 호출 스택에 추가하지 않습니까?
네, 그게 문제가 아닙니다. 수고하셨습니다.
Erlang 구현에 몇 가지 문제가 있습니다. 다음에 대한 기준으로 수정되지 않은 Erlang 프로그램의 실행 시간은 47.6 초였으며 C 코드의 경우 12.7 초였습니다.
계산 집약적 인 Erlang 코드를 실행하려면 가장 먼저해야 할 일은 네이티브 코드를 사용하는 것입니다. 로 컴파일 erlc +native euler12
하면 시간이 41.3 초로 단축되었습니다. 그러나 이것은 이러한 종류의 코드에 대한 네이티브 컴파일에서 예상 한 것보다 훨씬 느린 속도 (15 %)이며 문제는 -compile(export_all)
. 이것은 실험에 유용하지만 모든 함수가 외부에서 잠재적으로 접근 할 수 있다는 사실로 인해 네이티브 컴파일러는 매우 보수적입니다. (일반적인 BEAM 에뮬레이터는 그다지 영향을받지 않습니다.)이 선언을로 -export([solve/0]).
바꾸면 속도가 훨씬 빨라집니다 : 31.5 초 (기준선에서 거의 35 %).
그러나 코드 자체에는 문제가 있습니다. factorCount 루프의 각 반복 에 대해 다음 테스트를 수행합니다.
factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;
C 코드는 이것을하지 않습니다. 일반적으로 동일한 코드의 다른 구현을 공정하게 비교하는 것은 까다로울 수 있습니다. 특히 알고리즘이 숫자 인 경우 실제로 동일한 작업을 수행하는지 확인해야하기 때문입니다. 어딘가에서 어떤 typecast로 인해 한 구현에서 약간의 반올림 오류가 발생하면 둘 다 결국 동일한 결과에 도달하더라도 다른 것보다 더 많은 반복을 수행 할 수 있습니다.
이 가능한 오류 소스를 제거하고 각 반복에서 추가 테스트를 제거하기 위해 C 코드에서 밀접하게 모델링 된 다음과 같이 factorCount 함수를 다시 작성했습니다.
factorCount (N) ->
Sqrt = math:sqrt (N),
ISqrt = trunc(Sqrt),
if ISqrt == Sqrt -> factorCount (N, ISqrt, 1, -1);
true -> factorCount (N, ISqrt, 1, 0)
end.
factorCount (_N, ISqrt, Candidate, Count) when Candidate > ISqrt -> Count;
factorCount ( N, ISqrt, Candidate, Count) ->
case N rem Candidate of
0 -> factorCount (N, ISqrt, Candidate + 1, Count + 2);
_ -> factorCount (N, ISqrt, Candidate + 1, Count)
end.
이 재 작성, no export_all
및 네이티브 컴파일은 다음과 같은 런타임을 제공했습니다.
$ erlc +native euler12.erl
$ time erl -noshell -s euler12 solve
842161320
real 0m19.468s
user 0m19.450s
sys 0m0.010s
C 코드에 비해 나쁘지 않습니다.
$ time ./a.out
842161320
real 0m12.755s
user 0m12.730s
sys 0m0.020s
Erlang이 숫자 코드 작성에 전혀 적합하지 않다는 점을 고려할 때 이와 같은 프로그램에서 C보다 50 % 만 느리다는 것은 꽤 좋습니다.
마지막으로 귀하의 질문과 관련하여 :
질문 1 : 임의의 길이 정수를 사용하기 때문에 erlang, python 및 haskell 속도가 느슨합니까? 아니면 값이 MAXINT 미만인 한 그렇지 않습니까?
예, 다소. Erlang에서는 "둘러보기가있는 32/64 비트 산술 사용"이라고 말할 방법이 없습니다. 따라서 컴파일러가 정수에 대한 경계를 증명할 수없는 경우 (일반적으로 불가능) 모든 계산을 확인하여 확인해야합니다. 태그가 지정된 단일 단어에 들어갈 수 있거나 힙 할당 큰 숫자로 바꿔야하는 경우. 런타임에 실제로 사용되는 bignum이 없더라도 이러한 검사를 수행해야합니다. 반면에, 그 의미는 당신이 알고 갑자기 이전보다 그에게 더 큰 입력을 주면 알고리즘 때문에 예기치 않은 정수 랩 어라운드 실패하지 않을 것입니다.
질문 4 : 기능 구현이 LCO를 허용하므로 불필요한 프레임을 호출 스택에 추가하지 않습니까?
예, 마지막 호출 최적화와 관련하여 Erlang 코드가 정확합니다.
Python 최적화와 관련하여 PyPy (코드 변경없이 상당히 인상적인 속도 향상을 위해)를 사용하는 것 외에도 PyPy의 번역 툴체인 을 사용하여 RPython 호환 버전을 컴파일하거나 Cython 을 사용하여 확장 모듈을 빌드 할 수 있습니다. 내 테스트에서 C 버전보다 빠르며 Cython 모듈은 거의 두 배 빠릅니다 . 참고로 C 및 PyPy 벤치 마크 결과도 포함합니다.
C (로 컴파일 됨 gcc -O3 -lm
)
% time ./euler12-c
842161320
./euler12-c 11.95s
user 0.00s
system 99%
cpu 11.959 total
PyPy 1.5
% time pypy euler12.py
842161320
pypy euler12.py
16.44s user
0.01s system
99% cpu 16.449 total
RPython (최신 PyPy 개정 사용 c2f583445aee
)
% time ./euler12-rpython-c
842161320
./euler12-rpy-c
10.54s user 0.00s
system 99%
cpu 10.540 total
사이 톤 0.15
% time python euler12-cython.py
842161320
python euler12-cython.py
6.27s user 0.00s
system 99%
cpu 6.274 total
RPython 버전에는 몇 가지 주요 변경 사항이 있습니다. 독립 실행 형 프로그램으로 변환하려면 target
이 경우 main
함수 인 을 정의해야 합니다. sys.argv
유일한 인수 로 받아 들일 것으로 예상되며 int를 반환하는 데 필요합니다. % translate.py euler12-rpython.py
C로 번역하고 컴파일하는 translate.py를 사용하여 번역 할 수 있습니다 .
# euler12-rpython.py
import math, sys
def factorCount(n):
square = math.sqrt(n)
isquare = int(square)
count = -1 if isquare == square else 0
for candidate in xrange(1, isquare + 1):
if not n % candidate: count += 2
return count
def main(argv):
triangle = 1
index = 1
while factorCount(triangle) < 1001:
index += 1
triangle += index
print triangle
return 0
if __name__ == '__main__':
main(sys.argv)
def target(*args):
return main, None
Cython 버전은 확장 모듈로 다시 작성되었으며 _euler12.pyx
일반 파이썬 파일에서 가져오고 호출합니다. 는 _euler12.pyx
기본적으로 몇 가지 추가 정적 유형 선언으로, 버전과 동일합니다. setup.py에는 python setup.py build_ext --inplace
.
# _euler12.pyx
from libc.math cimport sqrt
cdef int factorCount(int n):
cdef int candidate, isquare, count
cdef double square
square = 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
cpdef main():
cdef int triangle = 1, index = 1
while factorCount(triangle) < 1001:
index += 1
triangle += index
print triangle
# euler12-cython.py
import _euler12
_euler12.main()
# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("_euler12", ["_euler12.pyx"])]
setup(
name = 'Euler12-Cython',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
솔직히 RPython이나 Cython에 대한 경험이 거의 없었고 결과에 놀랐습니다. CPython을 사용하는 경우 Cython 확장 모듈에 CPU 집약적 인 코드를 작성하는 것이 프로그램을 최적화하는 정말 쉬운 방법처럼 보입니다.
질문 3 : 요인을 결정하는 방식을 변경하지 않고 이러한 구현을 최적화하는 방법에 대한 힌트를 제공 할 수 있습니까? 어떤 방식 으로든 최적화 : 더 좋고, 더 빠르고, 더 "기본"언어.
C 구현은 차선책이며 (Thomas M. DuBuisson이 암시 한대로) 버전은 64 비트 정수 (예 : long 데이터 유형)를 사용합니다. 나중에 어셈블리 목록을 조사 할 것이지만, 추측을 통해 컴파일 된 코드에서 일부 메모리 액세스가 진행되어 64 비트 정수 사용이 상당히 느려집니다. 그것은 또는 생성 된 코드입니다 (SSE 레지스터에 64 비트 정수를 더 적게 넣거나 double을 64 비트 정수로 반올림 할 수 있다는 사실이 더 느립니다).
수정 된 코드는 다음과 같습니다 ( gcc -O3에 필요하다고 생각하지는 않지만 long 을 int로 바꾸고 명시 적으로 factorCount를 인라인 처리했습니다).
#include <stdio.h>
#include <math.h>
static inline int factorCount(int n)
{
double square = sqrt (n);
int isquare = (int)square;
int count = isquare == square ? -1 : 0;
int candidate;
for (candidate = 1; candidate <= isquare; candidate ++)
if (0 == n % candidate) count += 2;
return count;
}
int main ()
{
int triangle = 1;
int index = 1;
while (factorCount (triangle) < 1001)
{
index++;
triangle += index;
}
printf ("%d\n", triangle);
}
달리기 + 타이밍 :
$ gcc -O3 -lm -o euler12 euler12.c; time ./euler12
842161320
./euler12 2.95s user 0.00s system 99% cpu 2.956 total
참고로, 이전 답변에서 Thomas의 haskell 구현은 다음과 같습니다.
$ ghc -O2 -fllvm -fforce-recomp euler12.hs; time ./euler12 [9:40]
[1 of 1] Compiling Main ( euler12.hs, euler12.o )
Linking euler12 ...
842161320
./euler12 9.43s user 0.13s system 99% cpu 9.602 total
결론 : 훌륭한 컴파일러 인 ghc에서 아무것도 빼앗기지 않지만 gcc는 일반적으로 더 빠른 코드를 생성합니다.
이 블로그를 살펴보십시오 . 지난 1 년 동안 그는 Haskell과 Python에서 몇 가지 프로젝트 오일러 문제를 해결했으며 일반적으로 Haskell 이 훨씬 빠르다는 것을 알게되었습니다 . 나는 그 언어들 사이에서 당신의 유창함과 코딩 스타일과 더 관련이 있다고 생각합니다.
Python 속도에 관해서는 잘못된 구현을 사용하고 있습니다! PyPy를 사용 해보세요. 이와 같은 경우 훨씬 더 빠릅니다.
Haskell 패키지의 일부 기능을 사용하면 Haskell 구현 속도를 크게 높일 수 있습니다. 이 경우에는 'cabal install primes'로 방금 설치된 primes를 사용했습니다.)
import Data.Numbers.Primes
import Data.List
triangleNumbers = scanl1 (+) [1..]
nDivisors n = product $ map ((+1) . length) (group (primeFactors n))
answer = head $ filter ((> 500) . nDivisors) triangleNumbers
main :: IO ()
main = putStrLn $ "First triangle number to have over 500 divisors: " ++ (show answer)
타이밍 :
원래 프로그램 :
PS> measure-command { bin\012_slow.exe }
TotalSeconds : 16.3807409
TotalMilliseconds : 16380.7409
향상된 구현
PS> measure-command { bin\012.exe }
TotalSeconds : 0.0383436
TotalMilliseconds : 38.3436
보시다시피, 이것은 16 초 만에 실행 된 동일한 시스템에서 38 밀리 초 만에 실행됩니다. :)
컴파일 명령 :
ghc -O2 012.hs -o bin\012.exe
ghc -O2 012_slow.hs -o bin\012_slow.exe
재미로. 다음은보다 '네이티브'Haskell 구현입니다.
import Control.Applicative
import Control.Monad
import Data.Either
import Math.NumberTheory.Powers.Squares
isInt :: RealFrac c => c -> Bool
isInt = (==) <$> id <*> fromInteger . round
intSqrt :: (Integral a) => a -> Int
--intSqrt = fromIntegral . floor . sqrt . fromIntegral
intSqrt = fromIntegral . integerSquareRoot'
factorize :: Int -> [Int]
factorize 1 = []
factorize n = first : factorize (quot n first)
where first = (!! 0) $ [a | a <- [2..intSqrt n], rem n a == 0] ++ [n]
factorize2 :: Int -> [(Int,Int)]
factorize2 = foldl (\ls@((val,freq):xs) y -> if val == y then (val,freq+1):xs else (y,1):ls) [(0,0)] . factorize
numDivisors :: Int -> Int
numDivisors = foldl (\acc (_,y) -> acc * (y+1)) 1 <$> factorize2
nextTriangleNumber :: (Int,Int) -> (Int,Int)
nextTriangleNumber (n,acc) = (n+1,acc+n+1)
forward :: Int -> (Int, Int) -> Either (Int, Int) (Int, Int)
forward k val@(n,acc) = if numDivisors acc > k then Left val else Right (nextTriangleNumber val)
problem12 :: Int -> (Int, Int)
problem12 n = (!!0) . lefts . scanl (>>=) (forward n (1,1)) . repeat . forward $ n
main = do
let (n,val) = problem12 1000
print val
를 사용하면 ghc -O3
내 컴퓨터 (1.73GHz Core i7)에서 0.55-0.58 초 안에 일관되게 실행됩니다.
C 버전을위한보다 효율적인 factorCount 함수 :
int factorCount (int n)
{
int count = 1;
int candidate,tmpCount;
while (n % 2 == 0) {
count++;
n /= 2;
}
for (candidate = 3; candidate < n && candidate * candidate < n; candidate += 2)
if (n % candidate == 0) {
tmpCount = 1;
do {
tmpCount++;
n /= candidate;
} while (n % candidate == 0);
count*=tmpCount;
}
if (n > 1)
count *= 2;
return count;
}
를 사용하여 main에서 long을 int로 변경하면 gcc -O3 -lm
0.31-0.35 초 안에 지속적으로 실행됩니다.
n 번째 삼각형 수 = n * (n + 1) / 2이고 n과 (n + 1)이 완전히 다른 소인수 분해를 가지고 있다는 사실을 이용하면 둘 다 더 빠르게 실행되도록 만들 수 있습니다. 전체의 요소 수를 찾기 위해 각각의 절반을 곱할 수 있습니다. 다음과 같은:
int main ()
{
int triangle = 0,count1,count2 = 1;
do {
count1 = count2;
count2 = ++triangle % 2 == 0 ? factorCount(triangle+1) : factorCount((triangle+1)/2);
} while (count1*count2 < 1001);
printf ("%lld\n", ((long long)triangle)*(triangle+1)/2);
}
c 코드 실행 시간을 0.17-0.19 초로 줄이고 훨씬 더 큰 검색을 처리 할 수 있습니다. 10000 개 이상의 요소는 내 컴퓨터에서 약 43 초가 걸립니다. 나는 관심있는 독자에게 비슷한 하스켈 속도 향상을 남깁니다.
질문 1 : 임의의 길이 정수를 사용하기 때문에 erlang, python 및 haskell 속도가 느슨합니까? 아니면 값이 MAXINT 미만인 한 그렇지 않습니까?
그럴 것 같지 않습니다. Erlang과 Haskell에 대해 많이 말할 수는 없지만 (아래에서 Haskell에 대해 조금 설명 할 수 있습니다) 파이썬에서 다른 많은 병목 현상을 지적 할 수 있습니다. 프로그램이 Python에서 일부 값으로 작업을 실행하려고 할 때마다 값이 적절한 유형인지 확인해야하며 시간이 많이 걸립니다. 귀하의 factorCount
기능이 단지와 목록 할당 range (1, isquare + 1)
다양한 시간 및 런타임을, malloc
당신은 특히 C.에서와 -styled 메모리 할당 방법은이 카운터와 느린 반복하는 것보다 범위가 켜져 factorCount()
여러 번 호출 때문에 목록을 많이 할당된다. 또한 파이썬이 해석되고 CPython 인터프리터가 최적화에 큰 초점을 맞추지 않는다는 것을 잊지 마십시오.
편집 : 오, 글쎄요, 파이썬 3을 사용하고 있으므로 range()
목록이 아니라 생성기를 반환합니다. 이 경우 목록 할당에 대한 내 요점은 절반이 잘못되었습니다. 함수 range
는 그럼에도 불구하고 비효율적이지만 많은 항목이있는 목록을 할당하는 것만 큼 비효율적이지 않은 객체를 할당합니다.
질문 2 : 하스켈이 왜 그렇게 느린가요? 브레이크를 끄는 컴파일러 플래그가 있습니까 아니면 내 구현입니까? (하스켈은 나에게 일곱 개의 봉인이있는 책이기 때문에 후자는 꽤 가능성이 있습니다.)
Hugs 를 사용하고 있습니까? Hugs는 상당히 느린 통역사입니다. 당신이 그것을 사용한다면, 아마도 당신은 GHC 로 더 나은 시간을 얻을 수있을 것입니다. 하지만 저는 가설을 짜고있을뿐입니다. 좋은 Haskell 컴파일러가 내부에서하는 일들은 꽤 매력적이고 제 이해를 넘어선 것입니다. :)
질문 3 : 요인을 결정하는 방식을 변경하지 않고 이러한 구현을 최적화하는 방법에 대한 힌트를 제공 할 수 있습니까? 어떤 방식 으로든 최적화 : 더 좋고, 더 빠르고, 더 "기본"언어.
나는 당신이 재미없는 게임을하고 있다고 말하고 싶습니다. 다양한 언어를 아는 가장 좋은 부분은 가능한 한 가장 다른 방법으로 사용하는 것입니다. :) 그러나 저는이 점에 대한 권장 사항이 없습니다. 죄송합니다.이 경우 누군가가 당신을 도울 수 있기를 바랍니다. :)
질문 4 : 기능 구현이 LCO를 허용하므로 불필요한 프레임을 호출 스택에 추가하지 않습니까?
내가 기억하는 한, 값을 반환하기 전에 재귀 호출이 마지막 명령인지 확인하면됩니다. 즉, 아래와 같은 함수가 이러한 최적화를 사용할 수 있습니다.
def factorial(n, acc=1):
if n > 1:
acc = acc * n
n = n - 1
return factorial(n, acc)
else:
return acc
그러나 함수가 다음과 같으면 재귀 호출 후에 연산 (곱하기)이 있기 때문에 이러한 최적화를 수행 할 수 없습니다.
def factorial2(n):
if n > 1:
f = factorial2(n-1)
return f*n
else:
return 1
어떤 작업이 실행되는지 명확히하기 위해 일부 지역 변수에서 작업을 분리했습니다. 그러나 가장 일반적인 것은 이러한 기능을 아래와 같이 보는 것이지만 내가 말하는 요점에 대해서는 동일합니다.
def factorial(n, acc=1):
if n > 1:
return factorial(n-1, acc*n)
else:
return acc
def factorial2(n):
if n > 1:
return n*factorial(n-1)
else:
return 1
꼬리 재귀를 만들지 여부를 결정하는 것은 컴파일러 / 인터프리터의 몫입니다. 예를 들어, 파이썬 인터프리터는 내가 잘 기억하면 그것을하지 않습니다 (나는 유창한 구문 때문에 내 예제에서 파이썬을 사용했습니다). 어쨌든 두 개의 매개 변수가있는 팩토리얼 함수와 같은 이상한 것을 발견하면 (그리고 매개 변수 중 하나에 acc
, accumulator
등의 이름이 있습니다 .) 이제 사람들이 왜 그렇게하는지 알 수 있습니다. :)
Haskell을 사용하면 재귀를 명시 적으로 생각할 필요가 없습니다.
factorCount number = foldr factorCount' 0 [1..isquare] -
(fromEnum $ square == fromIntegral isquare)
where
square = sqrt $ fromIntegral number
isquare = floor square
factorCount' candidate
| number `rem` candidate == 0 = (2 +)
| otherwise = id
triangles :: [Int]
triangles = scanl1 (+) [1,2..]
main = print . head $ dropWhile ((< 1001) . factorCount) triangles
위의 코드에서 @Thomas의 답변에서 명시 적 재귀를 일반적인 목록 작업으로 대체했습니다. 코드는 꼬리 재귀에 대해 걱정하지 않고 똑같은 일을합니다. GHC 7.6.2를 사용하는 내 컴퓨터 에서 @Thomas의 답변 (~ 7.04s )에 있는 버전보다 약 6 % 느리게 실행됩니다 (~ 7.49s ) , @Raedwulf 의 C 버전은 ~ 3.15s로 실행됩니다 . GHC는 1 년 동안 개선 된 것 같습니다.
추신. 나는 그것이 오래된 질문이라는 것을 알고 있으며 Google 검색에서 우연히 발견했습니다 (지금 검색하고 있던 것을 잊어 버렸습니다 ...). LCO에 대한 질문에 대해 언급하고 일반적으로 Haskell에 대한 내 감정을 표현하고 싶었습니다. 나는 최고의 답변에 대해 의견을 말하고 싶었지만 주석은 코드 블록을 허용하지 않습니다.
C 버전에 대한 더 많은 숫자와 설명. 그동안 아무도 그렇게하지 않았던 것 같습니다. 모든 사람이보고 배울 수 있도록이 답변을 찬성하는 것을 잊지 마십시오.
1 단계 : 저자 프로그램의 벤치 마크
노트북 사양 :
- CPU i3 M380 (931MHz-최대 배터리 절약 모드)
- 4GB 메모리
- Win7 64 비트
- Microsoft Visual Studio 2012 Ultimate
- gcc 4.9.3을 사용한 Cygwin
- 파이썬 2.7.10
명령어 :
compiling on VS x64 command prompt > `for /f %f in ('dir /b *.c') do cl /O2 /Ot /Ox %f -o %f_x64_vs2012.exe`
compiling on cygwin with gcc x64 > `for f in ./*.c; do gcc -m64 -O3 $f -o ${f}_x64_gcc.exe ; done`
time (unix tools) using cygwin > `for f in ./*.exe; do echo "----------"; echo $f ; time $f ; done`
.
----------
$ time python ./original.py
real 2m17.748s
user 2m15.783s
sys 0m0.093s
----------
$ time ./original_x86_vs2012.exe
real 0m8.377s
user 0m0.015s
sys 0m0.000s
----------
$ time ./original_x64_vs2012.exe
real 0m8.408s
user 0m0.000s
sys 0m0.015s
----------
$ time ./original_x64_gcc.exe
real 0m20.951s
user 0m20.732s
sys 0m0.030s
파일 이름은 다음과 같습니다. integertype_architecture_compiler.exe
- integertype 은 현재 원래 프로그램과 동일합니다 (나중에 자세히 설명).
- 아키텍처 는 컴파일러 설정에 따라 x86 또는 x64입니다.
- 컴파일러 는 gcc 또는 vs2012입니다.
2 단계 : 다시 조사, 개선 및 벤치마킹
VS는 gcc보다 250 % 빠릅니다. 두 컴파일러는 비슷한 속도를 제공해야합니다. 분명히 코드 나 컴파일러 옵션에 문제가 있습니다. 조사합시다!
첫 번째 관심 지점은 정수 유형입니다. 변환은 비용이 많이들 수 있으며 더 나은 코드 생성 및 최적화를 위해 일관성이 중요합니다. 모든 정수는 동일한 유형이어야합니다.
그것은 지금 int
과 혼합 된 엉망입니다 long
. 우리는 그것을 개선 할 것입니다. 어떤 유형을 사용할까요? 가장 빠른. 그들 모두를 벤치마킹해야합니다!
----------
$ time ./int_x86_vs2012.exe
real 0m8.440s
user 0m0.016s
sys 0m0.015s
----------
$ time ./int_x64_vs2012.exe
real 0m8.408s
user 0m0.016s
sys 0m0.015s
----------
$ time ./int32_x86_vs2012.exe
real 0m8.408s
user 0m0.000s
sys 0m0.015s
----------
$ time ./int32_x64_vs2012.exe
real 0m8.362s
user 0m0.000s
sys 0m0.015s
----------
$ time ./int64_x86_vs2012.exe
real 0m18.112s
user 0m0.000s
sys 0m0.015s
----------
$ time ./int64_x64_vs2012.exe
real 0m18.611s
user 0m0.000s
sys 0m0.015s
----------
$ time ./long_x86_vs2012.exe
real 0m8.393s
user 0m0.015s
sys 0m0.000s
----------
$ time ./long_x64_vs2012.exe
real 0m8.440s
user 0m0.000s
sys 0m0.015s
----------
$ time ./uint32_x86_vs2012.exe
real 0m8.362s
user 0m0.000s
sys 0m0.015s
----------
$ time ./uint32_x64_vs2012.exe
real 0m8.393s
user 0m0.015s
sys 0m0.015s
----------
$ time ./uint64_x86_vs2012.exe
real 0m15.428s
user 0m0.000s
sys 0m0.015s
----------
$ time ./uint64_x64_vs2012.exe
real 0m15.725s
user 0m0.015s
sys 0m0.015s
----------
$ time ./int_x64_gcc.exe
real 0m8.531s
user 0m8.329s
sys 0m0.015s
----------
$ time ./int32_x64_gcc.exe
real 0m8.471s
user 0m8.345s
sys 0m0.000s
----------
$ time ./int64_x64_gcc.exe
real 0m20.264s
user 0m20.186s
sys 0m0.015s
----------
$ time ./long_x64_gcc.exe
real 0m20.935s
user 0m20.809s
sys 0m0.015s
----------
$ time ./uint32_x64_gcc.exe
real 0m8.393s
user 0m8.346s
sys 0m0.015s
----------
$ time ./uint64_x64_gcc.exe
real 0m16.973s
user 0m16.879s
sys 0m0.030s
정수 유형은 다음 int
long
int32_t
uint32_t
int64_t
과 같습니다 uint64_t
.#include <stdint.h>
C에는 많은 정수 유형과 함께 사용할 부호 / 부호없는 일부가 있으며 x86 또는 x64로 컴파일 할 수있는 선택 사항 (실제 정수 크기와 혼동하지 말 것)이 있습니다. 컴파일하고 실행할 버전이 많네요 ^^
3 단계 : 숫자 이해
확실한 결론 :
- 32 비트 정수는 64 비트 정수보다 ~ 200 % 빠릅니다.
- 부호없는 64 비트 정수를 25 % 빠르게보다 서명 64 비트 (불행하게도, 나는 그것에 대해 아무런 설명이 없습니다)
트릭 질문 : "C에서 int와 long의 크기는 얼마입니까?"
정답은 : C에서 int와 long의 크기가 잘 정의되지 않았습니다!
C 사양에서 :
int는
길이가 32 비트 이상입니다.
gcc man 페이지에서 (-m32 및 -m64 플래그) :
32 비트 환경은 int, long 및 포인터를 32 비트로 설정하고 모든 i386 시스템에서 실행되는 코드를 생성합니다.
64 비트 환경은 int를 32 비트, long 및 포인터를 64 비트로 설정하고 AMD의 x86-64 아키텍처 용 코드를 생성합니다.
MSDN 설명서 (데이터 유형 범위) https://msdn.microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx에서 :
int, 4 바이트는 또한 부호있는
long, 4 바이트로 알고 있으며 long int 및 부호있는 long int로도 알고 있습니다.
결론을 내리기 위해 : 배운 교훈
32 비트 정수는 64 비트 정수보다 빠릅니다.
표준 정수 유형은 C 나 C ++에서 잘 정의되어 있지 않으며 컴파일러 및 아키텍처에 따라 다릅니다. 당신은 일관성과 예측 가능성을 필요로 할 때, 사용
uint32_t
의 정수의 가족#include <stdint.h>
.속도 문제가 해결되었습니다. 다른 모든 언어는 수백 퍼센트 뒤처지고 C & C ++가 다시 승리합니다! 그들은 항상 그렇습니다. 다음 개선은 OpenMP를 사용한 멀티 스레딩입니다. : D
Erlang 구현을 살펴보십시오. 타이밍에는 전체 가상 머신의 시작, 프로그램 실행 및 가상 머신 중지가 포함됩니다. erlang vm을 설정하고 중지하는 데 시간이 좀 걸립니다.
타이밍이 erlang 가상 머신 자체 내에서 수행 되었다면 해당 프로그램에만 실제 시간이있는 것처럼 결과가 달라집니다. 그렇지 않으면 Erlang Vm을 시작하고로드하는 데 걸린 총 시간과 프로그램에 넣은대로 중지하는 데 걸리는 총 시간이 시간 측정에 사용하는 총 시간에 모두 포함되어 있다고 생각합니다. 프로그램이 출력되고 있습니다. 가상 머신 자체 내에서 프로그램 시간을 측정 할 때 사용하는 erlang 타이밍 자체를 사용하는 것을 고려하십시오 timer:tc/1 or timer:tc/2 or timer:tc/3
. 이런 식으로 erlang의 결과는 가상 머신을 시작 및 중지 / 종료 / 중지하는 데 걸리는 시간을 제외합니다. 그것이 내 추론입니다. 그것에 대해 생각하고 다시 벤치 마크를 시도하십시오.
정확한 값을 얻기 위해 해당 언어의 런타임 내에서 프로그램 (런타임이있는 언어의 경우) 시간을 측정하는 것이 좋습니다. 예를 들어 C는 Erlang, Python 및 Haskell처럼 런타임 시스템을 시작하고 종료하는 오버 헤드가 없습니다 (98 % 확신-나는 정정을 의미합니다). 그래서 (이 추론을 바탕으로) 저는이 벤치 마크가 런타임 시스템 위에서 실행되는 언어에 대해 정확하고 공정하지 않다고 결론지었습니다. 이러한 변경 사항으로 다시 해보겠습니다.
편집 : 모든 언어에 런타임 시스템이 있더라도 각 언어를 시작하고 중지하는 오버 헤드가 다를 수 있습니다. 그래서 나는 우리가 런타임 시스템 내에서 시간을 보내는 것이 좋습니다 (적용되는 언어의 경우). Erlang VM은 시작시 상당한 오버 헤드가있는 것으로 알려져 있습니다!
질문 1 : Erlang, Python 및 Haskell은 임의의 길이 정수를 사용하여 속도를 잃거나 값이 MAXINT보다 작 으면 속도를 잃지 않습니까?
질문 1은 Erlang에 대해 부정적으로 답할 수 있습니다. 마지막 질문은 다음과 같이 Erlang을 적절하게 사용하여 답변됩니다.
http://bredsaal.dk/learning-erlang-using-projecteuler-net
초기 C 예제보다 빠르기 때문에 다른 사람들이 이미 자세히 다루었으므로 많은 문제가 있다고 생각합니다.
이 Erlang 모듈은 저렴한 넷북에서 약 5 초만에 실행됩니다. erlang의 네트워크 스레드 모델을 사용하므로 이벤트 모델을 활용하는 방법을 보여줍니다. 여러 노드에 분산 될 수 있습니다. 그리고 그것은 빠릅니다. 내 코드가 아닙니다.
-module(p12dist).
-author("Jannich Brendle, jannich@bredsaal.dk, http://blog.bredsaal.dk").
-compile(export_all).
server() ->
server(1).
server(Number) ->
receive {getwork, Worker_PID} -> Worker_PID ! {work,Number,Number+100},
server(Number+101);
{result,T} -> io:format("The result is: \~w.\~n", [T]);
_ -> server(Number)
end.
worker(Server_PID) ->
Server_PID ! {getwork, self()},
receive {work,Start,End} -> solve(Start,End,Server_PID)
end,
worker(Server_PID).
start() ->
Server_PID = spawn(p12dist, server, []),
spawn(p12dist, worker, [Server_PID]),
spawn(p12dist, worker, [Server_PID]),
spawn(p12dist, worker, [Server_PID]),
spawn(p12dist, worker, [Server_PID]).
solve(N,End,_) when N =:= End -> no_solution;
solve(N,End,Server_PID) ->
T=round(N*(N+1)/2),
case (divisor(T,round(math:sqrt(T))) > 500) of
true ->
Server_PID ! {result,T};
false ->
solve(N+1,End,Server_PID)
end.
divisors(N) ->
divisor(N,round(math:sqrt(N))).
divisor(_,0) -> 1;
divisor(N,I) ->
case (N rem I) =:= 0 of
true ->
2+divisor(N,I-1);
false ->
divisor(N,I-1)
end.
아래 테스트는 Intel (R) Atom (TM) CPU N270 @ 1.60GHz에서 수행되었습니다.
~$ time erl -noshell -s p12dist start
The result is: 76576500.
^C
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a
real 0m5.510s
user 0m5.836s
sys 0m0.152s
C ++ (11), <나를 위해이 20ms - 실행 여기
나는 당신이 당신의 언어 관련 지식을 향상시키는 데 도움이되는 팁을 원한다는 것을 이해합니다. 그러나 여기에서 잘 다루었 기 때문에 당신의 질문 등에 대한 수학적 코멘트를 보았을지도 모르는 사람들을 위해 몇 가지 맥락을 추가 할 것이라고 생각했고 이것이 왜 그런지 궁금했습니다. 코드가 훨씬 느 렸습니다.
이 답변은 주로 사람들이 질문 / 다른 답변의 코드를 더 쉽게 평가할 수 있도록 컨텍스트를 제공하기위한 것입니다.
이 코드는 사용 된 언어와 관련이없는 몇 가지 (추악한) 최적화 만 사용합니다.
- 모든 학습 번호는 n (n + 1) / 2 형식입니다.
- n과 n + 1은 코 프라임
- 제수는 곱셈 함수입니다.
#include <iostream>
#include <cmath>
#include <tuple>
#include <chrono>
using namespace std;
// Calculates the divisors of an integer by determining its prime factorisation.
int get_divisors(long long n)
{
int divisors_count = 1;
for(long long i = 2;
i <= sqrt(n);
/* empty */)
{
int divisions = 0;
while(n % i == 0)
{
n /= i;
divisions++;
}
divisors_count *= (divisions + 1);
//here, we try to iterate more efficiently by skipping
//obvious non-primes like 4, 6, etc
if(i == 2)
i++;
else
i += 2;
}
if(n != 1) //n is a prime
return divisors_count * 2;
else
return divisors_count;
}
long long euler12()
{
//n and n + 1
long long n, n_p_1;
n = 1; n_p_1 = 2;
// divisors_x will store either the divisors of x or x/2
// (the later iff x is divisible by two)
long long divisors_n = 1;
long long divisors_n_p_1 = 2;
for(;;)
{
/* This loop has been unwound, so two iterations are completed at a time
* n and n + 1 have no prime factors in common and therefore we can
* calculate their divisors separately
*/
long long total_divisors; //the divisors of the triangle number
// n(n+1)/2
//the first (unwound) iteration
divisors_n_p_1 = get_divisors(n_p_1 / 2); //here n+1 is even and we
total_divisors =
divisors_n
* divisors_n_p_1;
if(total_divisors > 1000)
break;
//move n and n+1 forward
n = n_p_1;
n_p_1 = n + 1;
//fix the divisors
divisors_n = divisors_n_p_1;
divisors_n_p_1 = get_divisors(n_p_1); //n_p_1 is now odd!
//now the second (unwound) iteration
total_divisors =
divisors_n
* divisors_n_p_1;
if(total_divisors > 1000)
break;
//move n and n+1 forward
n = n_p_1;
n_p_1 = n + 1;
//fix the divisors
divisors_n = divisors_n_p_1;
divisors_n_p_1 = get_divisors(n_p_1 / 2); //n_p_1 is now even!
}
return (n * n_p_1) / 2;
}
int main()
{
for(int i = 0; i < 1000; i++)
{
using namespace std::chrono;
auto start = high_resolution_clock::now();
auto result = euler12();
auto end = high_resolution_clock::now();
double time_elapsed = duration_cast<milliseconds>(end - start).count();
cout << result << " " << time_elapsed << '\n';
}
return 0;
}
내 데스크톱의 경우 평균 19ms, 랩톱의 경우 80ms가 소요되는데, 여기에서 본 대부분의 다른 코드와는 거리가 멀다. 그리고 의심 할 여지없이 많은 최적화가 여전히 가능합니다.
GO 시도 :
package main
import "fmt"
import "math"
func main() {
var n, m, c int
for i := 1; ; i++ {
n, m, c = i * (i + 1) / 2, int(math.Sqrt(float64(n))), 0
for f := 1; f < m; f++ {
if n % f == 0 { c++ }
}
c *= 2
if m * m == n { c ++ }
if c > 1001 {
fmt.Println(n)
break
}
}
}
나는 얻다:
원본 c 버전 : 9.1690 100 %
이동 : 8.2520111 %
그러나 사용 :
package main
import (
"math"
"fmt"
)
// Sieve of Eratosthenes
func PrimesBelow(limit int) []int {
switch {
case limit < 2:
return []int{}
case limit == 2:
return []int{2}
}
sievebound := (limit - 1) / 2
sieve := make([]bool, sievebound+1)
crosslimit := int(math.Sqrt(float64(limit))-1) / 2
for i := 1; i <= crosslimit; i++ {
if !sieve[i] {
for j := 2 * i * (i + 1); j <= sievebound; j += 2*i + 1 {
sieve[j] = true
}
}
}
plimit := int(1.3*float64(limit)) / int(math.Log(float64(limit)))
primes := make([]int, plimit)
p := 1
primes[0] = 2
for i := 1; i <= sievebound; i++ {
if !sieve[i] {
primes[p] = 2*i + 1
p++
if p >= plimit {
break
}
}
}
last := len(primes) - 1
for i := last; i > 0; i-- {
if primes[i] != 0 {
break
}
last = i
}
return primes[0:last]
}
func main() {
fmt.Println(p12())
}
// Requires PrimesBelow from utils.go
func p12() int {
n, dn, cnt := 3, 2, 0
primearray := PrimesBelow(1000000)
for cnt <= 1001 {
n++
n1 := n
if n1%2 == 0 {
n1 /= 2
}
dn1 := 1
for i := 0; i < len(primearray); i++ {
if primearray[i]*primearray[i] > n1 {
dn1 *= 2
break
}
exponent := 1
for n1%primearray[i] == 0 {
exponent++
n1 /= primearray[i]
}
if exponent > 1 {
dn1 *= exponent
}
if n1 == 1 {
break
}
}
cnt = dn * dn1
dn = dn1
}
return n * (n - 1) / 2
}
나는 얻다:
원본 c 버전 : 9.1690 100 %
thaumkid 's c 버전 : 0.1060 8650 %
첫 번째 버전 : 8.2520111 %
두 번째 버전 : 0.0230 39865 %
또한 Python3.6 및 pypy3.3-5.5-alpha를 시도했습니다.
원본 c 버전 : 8.629100 %
thaumkid 's c 버전 : 0.109 7916 %
Python3.6 : 54.795 16 %
pypy3.3-5.5-alpha : 13.291 65 %
그리고 다음 코드로 내가 얻었습니다.
원본 c 버전 : 8.629100 %
thaumkid 's c 버전 : 0.109 8650 %
Python3.6 : 1.489580 %
pypy3.3-5.5-alpha : 0.582 1483 %
def D(N):
if N == 1: return 1
sqrtN = int(N ** 0.5)
nf = 1
for d in range(2, sqrtN + 1):
if N % d == 0:
nf = nf + 1
return 2 * nf - (1 if sqrtN**2 == N else 0)
L = 1000
Dt, n = 0, 0
while Dt <= L:
t = n * (n + 1) // 2
Dt = D(n/2)*D(n+1) if n%2 == 0 else D(n)*D((n+1)/2)
n = n + 1
print (t)
변화: case (divisor(T,round(math:sqrt(T))) > 500) of
에: case (divisor(T,round(math:sqrt(T))) > 1000) of
이것은 Erlang 다중 프로세스 예제에 대한 정답을 생성합니다.
나는 관련된 숫자에 작은 요인이 많을 때만 요인의 수가 많다고 가정했습니다. 그래서 저는 thaumkid의 우수한 알고리즘을 사용했지만 처음에는 결코 너무 작지 않은 계수 수에 대한 근사치를 사용했습니다. 매우 간단합니다. 최대 29 개의 소인수를 확인한 다음 나머지 수를 확인하고 요소의 nmber에 대한 상한을 계산합니다. 이를 사용하여 요인 수의 상한을 계산하고 그 수가 충분히 높으면 정확한 요인 수를 계산합니다.
아래 코드는 정확성을 위해이 가정이 필요하지 않지만 빠릅니다. 작동하는 것 같습니다. 100,000 개의 숫자 중 하나만 전체 확인을 요구할만큼 높은 추정치를 제공합니다.
코드는 다음과 같습니다.
// Return at least the number of factors of n.
static uint64_t approxfactorcount (uint64_t n)
{
uint64_t count = 1, add;
#define CHECK(d) \
do { \
if (n % d == 0) { \
add = count; \
do { n /= d; count += add; } \
while (n % d == 0); \
} \
} while (0)
CHECK ( 2); CHECK ( 3); CHECK ( 5); CHECK ( 7); CHECK (11); CHECK (13);
CHECK (17); CHECK (19); CHECK (23); CHECK (29);
if (n == 1) return count;
if (n < 1ull * 31 * 31) return count * 2;
if (n < 1ull * 31 * 31 * 37) return count * 4;
if (n < 1ull * 31 * 31 * 37 * 37) return count * 8;
if (n < 1ull * 31 * 31 * 37 * 37 * 41) return count * 16;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43) return count * 32;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47) return count * 64;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53) return count * 128;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59) return count * 256;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61) return count * 512;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67) return count * 1024;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71) return count * 2048;
if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71 * 73) return count * 4096;
return count * 1000000;
}
// Return the number of factors of n.
static uint64_t factorcount (uint64_t n)
{
uint64_t count = 1, add;
CHECK (2); CHECK (3);
uint64_t d = 5, inc = 2;
for (; d*d <= n; d += inc, inc = (6 - inc))
CHECK (d);
if (n > 1) count *= 2; // n must be a prime number
return count;
}
// Prints triangular numbers with record numbers of factors.
static void printrecordnumbers (uint64_t limit)
{
uint64_t record = 30000;
uint64_t count1, factor1;
uint64_t count2 = 1, factor2 = 1;
for (uint64_t n = 1; n <= limit; ++n)
{
factor1 = factor2;
count1 = count2;
factor2 = n + 1; if (factor2 % 2 == 0) factor2 /= 2;
count2 = approxfactorcount (factor2);
if (count1 * count2 > record)
{
uint64_t factors = factorcount (factor1) * factorcount (factor2);
if (factors > record)
{
printf ("%lluth triangular number = %llu has %llu factors\n", n, factor1 * factor2, factors);
record = factors;
}
}
}
}
이것은 약 0.7 초에 13824 개의 요소를 가진 14,753,024 번째 삼각형, 34 초에 61,440 개의 요소를 가진 879,207,615 번째 삼각형, 10 분 5 초에 138,240 개의 요소를 가진 12,524,486,975 번째 삼각형 숫자, 그리고 172,032 개의 요소를 가진 26,467,792,064 번째 삼각형 숫자를 찾습니다. 21 분 25 초 (2.4GHz Core2 Duo), 따라서이 코드는 평균적으로 숫자 당 116 프로세서 사이클 만 걸립니다. 마지막 삼각형 숫자 자체가 2 ^ 68보다 크므로
"Jannich Brendle"버전을 500 대신 1000으로 수정했습니다. 그리고 euler12.bin, euler12.erl, p12dist.erl의 결과를 나열합니다. 두 erl 코드 모두 '+ native'를 사용하여 컴파일합니다.
zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s p12dist start
The result is: 842161320.
real 0m3.879s
user 0m14.553s
sys 0m0.314s
zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s euler12 solve
842161320
real 0m10.125s
user 0m10.078s
sys 0m0.046s
zhengs-MacBook-Pro:workspace zhengzhibin$ time ./euler12.bin
842161320
real 0m5.370s
user 0m5.328s
sys 0m0.004s
zhengs-MacBook-Pro:workspace zhengzhibin$
#include <stdio.h>
#include <math.h>
int factorCount (long n)
{
double square = sqrt (n);
int isquare = (int) square+1;
long candidate = 2;
int count = 1;
while(candidate <= isquare && candidate<=n){
int c = 1;
while (n % candidate == 0) {
c++;
n /= candidate;
}
count *= c;
candidate++;
}
return count;
}
int main ()
{
long triangle = 1;
int index = 1;
while (factorCount (triangle) < 1001)
{
index ++;
triangle += index;
}
printf ("%ld\n", triangle);
}
gcc -lm -Ofast euler.c
시간 ./a.out
2.79s 사용자 0.00s 시스템 99 % cpu 2.794 총
'developer tip' 카테고리의 다른 글
PHP에서 die ()와 exit ()의 차이점은 무엇입니까? (0) | 2020.10.02 |
---|---|
휘발성 vs. 연동 vs. 잠금 (0) | 2020.10.02 |
지도에 Go의 키가 포함되어 있는지 확인하는 방법은 무엇입니까? (0) | 2020.10.02 |
1MB RAM에서 1 백만 개의 8 자리 숫자 정렬 (0) | 2020.10.02 |
Interop 유형은 삽입 할 수 없습니다. (0) | 2020.10.02 |