web-dev-qa-db-ja.com

Project Eulerとの速度比較:C vs Python vs Erlang vs Haskell

私はプログラミング演習として Project Euler から 問題#12 を取り入れ、C、Pythonでの(確かに最適ではない)実装を比較しました。アーランとハスケル。いくつかのより高い実行時間を得るために、私は最初の問題で述べたように500の代わりに1000以上の約数を持つ最初の三角形の数を探します。

結果は次のとおりです。

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

Python:

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

ハスケル:

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

要約:

  • 子:100%
  • Python:692%(PyPyでは118%)
  • アーラン:436%(RichardCのおかげで135%)
  • ハスケル:1421%

Cは計算に長い時間を使用し、他の3つのような任意の長さの整数ではないため、Cには大きな利点があると思います。また、それは最初にランタイムをロードする必要はありません(他の人たちですか?).

質問1:任意の長さの整数を使用したためにErlang、Python、およびHaskellが速度を落とすことがありますか。それとも、値がMAXINT未満でない限り、それらは無効になりませんか。

質問2:Haskellはなぜそんなに遅いのですか?ブレーキをオフにするコンパイラフラグはありますか、それとも私の実装ですか? (Haskellは私に7つの印鑑がある本なので後者はかなりありそうです。)

質問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
631
Hyperboreus

X86_64 Core2 Duo(2.5GHz)マシンでGHC 7.0.3gcc 4.4.6Linux 2.6.29を使用し、Haskellにghc -O2 -fllvm -fforce-recompを、Cにgcc -O3 -lmを使用してコンパイルします。

  • Cルーチンは8.4秒で実行されます(おそらく-O3のため、実行よりも速くなります)。
  • Haskellソリューションは36秒で実行されます(-O2フラグのため)
  • あなたのfactorCount'コードは明示的にタイプされておらず、Integerにデフォルト設定されていません(私の誤診を訂正してくれたDanielに感謝します)。 Intを使用して明示的な型シグネチャ(とにかく標準的な方法)を与えると、時間は11.1秒に変わります
  • factorCount'では、不必要にfromIntegralと呼ばれています。ただし、修正しても変更はありません(コンパイラは賢く、幸運です)。
  • modが速くて十分な場合はremを使用しました。これは時間を8.5秒に変更します。
  • factorCount'は、決して変わらない2つの追加の引数(numbersqrt)を常に適用しています。ワーカー/ラッパー変換により、次のようになります。
 $ 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:なぜhaskellはとても遅いのですか?ブレーキをオフにするコンパイラフラグはありますか、それとも私の実装ですか? (ハスケルは私に7つの印鑑がある本なので後者はかなりありそうです。)

質問3:要因の決定方法を変更せずにこれらの実装を最適化する方法についていくつかヒントを教えてください。どのような方法でも最適化:言語に対してより良く、より速く、より "ネイティブ"に。

それが私が上で答えたものでした。答えは

  • 0)-O2による最適化の使用
  • 1)可能であれば高速(特にボックス化不可能)なタイプを使用する
  • 2)remmodではない(よく忘れられている最適化)
  • 3)ワーカー/ラッパー変換(おそらく最も一般的な最適化)。

質問4:私の機能的な実装はLCOを許可し、それ故コールスタックに不要なフレームを追加することを避けますか?

はい、それは問題ではありませんでした。良い仕事とあなたがこれを考えてくれてうれしい。

766

Erlangの実装にはいくつか問題があります。以下の基準として、私の修正されていないErlangプログラムの実行時間は、Cコードの12.7秒に対して47.6秒でした。

計算集約型の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コードはこれをしません。一般に、同じコードの異なる実装間で公平な比較を行うのは難しい場合があります。特にアルゴリズムが数値の場合は、実際に同じことをしていることを確認する必要があるためです。どちらかの型キャストによる、ある実装でのわずかな丸め誤差により、両方が最終的に同じ結果になっても、他のものよりもはるかに多くの反復が行われる可能性があります。

この考えられるエラーの原因を排除するために(そして各反復で余分なテストを取り除くために)、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ビット算術演算を使う」と言うことはできません。したがって、コンパイラがあなたの整数の限界を証明できない限り(そして通常は不可能です)、すべての計算をチェックしなければなりませんタグ付けされた1つのWordに収まる場合、またはそれらをヒープに割り当てられたbignumに変換する必要がある場合。実行時に実際にbignumが使用されたことがない場合でも、これらのチェックを実行する必要があります。一方、それはあなたがknowにすると、突然大きな入力を突然与えたとしても、予期しない整数の折り返しのためにアルゴリズムが失敗することは決してないということです。

質問4:私の機能実装はLCOを許可しているので、不要なフレームをコールスタックに追加することを避けますか?

はい、あなたのErlangコードは最後の呼び出しの最適化に関して正しいです。

216
RichardC

Pythonの最適化に関しては、PyPyを使うことに加えて(あなたのコードに変更を加えることなくかなりスピードアップするために)、PyPyの 翻訳ツールチェーンを使うことができます RPythonに準拠したバージョンをコンパイルするため、または Cython で拡張モジュールを構築する。どちらも私のテストではCバージョンよりも高速です。 moduleはほぼ2倍の速さです。参考のために、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

Cython 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、% translate.py euler12-rpython.pyを使って翻訳することができます。これはCに翻訳され、あなたのためにコンパイルされます。

# 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 __== '__main__':
    main(sys.argv)

def target(*args):
    return main, None

Cython版は拡張モジュール_euler12.pyxとして書き直されました。私はこれを通常のpythonファイルからインポートして呼び出します。 _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に負荷のかかるコードを記述することは、プログラムを最適化するための本当に簡単な方法のように思えます。

154
zeekay

質問3:要因の決め方を変えずにこれらの実装を最適化するためのヒントを教えてください。どのような方法でも最適化:言語に対してより良く、より速く、より "ネイティブ"に。

Cの実装は(Thomas M. DuBuissonによって示唆されているように)次善であり、バージョンは64ビット整数(すなわちlongデータ型)を使用します。後でアセンブリの一覧を調べますが、十分な知識があると思いますが、コンパイルされたコードでメモリアクセスがいくつか行われているため、64ビット整数の使用はかなり遅くなります。それは、それとも生成コードです(SSEレジスタに収めることができる64ビット整数が少なくても、64ビット整数にdoubleを丸めることもできます)。

これは修正されたコードです(単にlongintに置き換えて、factorCountを明示的にインライン化しましたが、これは必要ではないと思います) gcc -O3と一緒に:

#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は通常より速いコードを生成します。

70
Raedwulf

このブログを見てください。ここ1年ほどで、彼はHaskellとPythonでProject Eulerの問題のいくつかをやりました、そして、彼は一般的にHaskellがずっと速いとわかりました。私はそれらの言語の間でそれがあなたの流暢さとコーディングスタイルともっと関係があると思う。

Pythonの速度に関しては、間違った実装を使っています。 PyPy を試してみてください。このような場合は、はるかに速くなることがわかります。

55
agf

あなたのHaskellの実装はHaskellパッケージからいくつかの関数を使うことによって大いにスピードアップすることができます。この場合は、 'cabal install 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
31

ただ楽しみのために。以下は、より「ネイティブな」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;
}

gcc -O3 -lmを使用して、longを主にintに変更すると、これは一貫して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秒かかります。私は同様のhaskellスピードアップを興味のある読者に任せます。

28
thaumkid
質問1:任意の長さの整数を使用しているため、erlang、python、およびhaskellの速度が低下していませんか。

これはありそうもないです。私はErlangとHaskellについてはあまり言えませんが(おそらくHaskellについて少し後で説明しますが)、Pythonの他の多くのボトルネックを指摘することができます。プログラムは、Pythonでいくつかの値を使って操作を実行しようとするたびに、その値が適切な型からのものであるかどうかを検証する必要があり、少し時間がかかります。あなたのfactorCount関数はrange (1, isquare + 1)でリストを何度も割り当てるだけであり、実行時のmalloc__スタイルのメモリ割り当てはCで行うようにカウンタで範囲を繰り返すよりもずっと遅いです。特に、factorCount()は複数回呼び出されます。リストまた、Pythonが解釈され、CPythonインタプリタが最適化されることに大きな焦点を当てていないことを忘れないでください。

EDIT:ああ、私はあなたがPython 3を使っているのでrange()はリストを返さないがジェネレータを返すことに注意してください。この場合、リストを割り当てることについての私の考えは半分間違っています。関数はrangeオブジェクトを割り当てるだけですが、それでも非効率的ですが、多くの項目を含むリストを割り当てるほど非効率的ではありません。

質問2:なぜhaskellはとても遅いのですか?ブレーキをオフにするコンパイラフラグはありますか、それとも私の実装ですか? (ハスケルは私に7つの印鑑がある本なので後者はかなりありそうです。)

あなたは 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

末尾再帰を行うかどうかを決定するのは、コンパイラ/インタプリタ次第です。例えば、私がよく覚えているのであれば、Pythonインタプリタはそれを行いません(私の例では、流暢な構文のためだけにPythonを使いました)。とにかく、2つのパラメータを持つ階乗関数(そしてパラメータの1つにaccaccumulatorなどの名前がある)のような奇妙なものを見つけたなら、なぜ人々がそれをするのかわかります:)

13
brandizzi

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の回答の明示的な再帰を一般的なリスト演算に置き換えました。末尾再帰について心配することなく、コードはまだまったく同じことをします。 @Thomasの回答のバージョンよりも 6%ほど遅く(〜7.49s)実行されます(〜7.04 GHC 7.6.2を使っている私のマシンではs )、@ RaedwulfのCバージョンでは〜3.15sを実行します。 GHCは1年で改善したようです。

PS。私はそれが古い質問であることを知っています、そして私はグーグル検索からそれにつまずきます(私は今探していたものを忘れました...)。 LCOについての質問にコメントして、Haskell全般についての私の気持ちを表現したいだけでした。私はトップの答えにコメントしたいのですが、コメントはコードブロックを許可しません。

12
jxy

Cバージョンのいくつかのより多くの数字と説明。どうやら誰もその年の間それをしなかった。誰もが見て学ぶことができるように、この回答を支持することを忘れないでください。

ステップ1:著者のプログラムのベンチマーク

ノートパソコンの仕様:

  • CPU i3 M380(931 MHz - 最大バッテリー節約モード)
  • 4GBメモリ
  • Win7 64ビット
  • Microsoft Visual Studio 2012 Ultimate
  • Cygwin with gcc 4.9.3
  • Python 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%高速です。 2つのコンパイラは同じようなスピードを出すべきです。明らかに、コードやコンパイラオプションに何か問題があります。調べよう!

最初の注目点は整数型です。変換はコストがかかる可能性があり、コード生成と最適化を改善するには一貫性が重要です。すべての整数は同じ型であるべきです。

今のところintlongは混乱しています。それを改善するつもりです。使用するタイプ最速。彼らのベンチマークを得た!

----------
$ 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

整数型はintlongint32_tuint32_tint64_tおよびuint64_tからの#include <stdint.h>です。

Cには整数型のLOTSがいくつかあり、それに加えて、符号付き/符号なしの組み合わせ、x86またはx64としてコンパイルするという選択肢があります(実際の整数サイズと混同しないでください)。コンパイルして実行するにはたくさんのバージョンがあります^^

ステップ3:数字を理解する

決定的な結論:

  • 32ビット整数は64ビット相当よりも〜200%高速です
  • 符号なし64ビット整数は符号付き64ビットより25%高速です(残念ながら、そのことについての説明はありません)。

トリックな質問:「Cのintとlongのサイズは?」
正しい答えは次のとおりです。Cのintおよびlongのサイズは明確に定義されていません!

Cの仕様から:

intは32ビット以上
longは少なくともintです

gccのマニュアルページ(-m32および-m64のフラグ)から:

32ビット環境では、int、long、およびポインタを32ビットに設定し、任意のi386システムで動作するコードを生成します。
64ビット環境では、intが32ビット、long、および64ビットへのポインタが設定され、AMDのx86-64アーキテクチャ用のコードが生成されます。

MSDNのドキュメント(データ型の範囲)から https://msdn.Microsoft.com/ja-jp/library/s3f49ktz%28v=vs.110%29。 aspx

int、4バイト、符号付きとしても認識
long、4バイト、long intおよびsigned long intとしても認識

これを結論づけるために:学んだ教訓

  • 32ビット整数は64ビット整数より速いです。

  • 標準の整数型はCでもC++でもよく定義されているわけではなく、コンパイラやアーキテクチャによって異なります。一貫性と予測可能性が必要な場合は、uint32_t#include <stdint.h>整数ファミリを使用してください。

  • スピードの問題は解決しました。他のすべての言語は数百パーセント遅れています、CとC++は再び勝ちます!彼らはいつもします。次の改良はOpenMPを使ったマルチスレッド化です。

10
user5994461

Erlangの実装を見てください。タイミングには、仮想マシン全体の起動、プログラムの実行、仮想マシンの停止が含まれています。 erlang vmの設定と停止には多少時間がかかることを確信しています。

タイミングがアーランの仮想マシン自体の中で行われた場合、その場合のように、問題のプログラムだけの実際の時間があるため、結果は異なります。そうでなければ、Erlang Vmの起動とロードのプロセスにかかる時間とそれを停止するプロセスにかかる時間の合計が、あなたが使っているメソッドが時間を計るために使った合計時間に含まれていると思います。プログラムが出力しています。仮想マシン自体の中でプログラムのタイミングを計りたいときに使用するアーラン・タイミング自体を使用することを検討してください timer:tc/1 or timer:tc/2 or timer:tc/3 このようにして、erlangからの結果は、仮想マシンの起動と停止/強制終了/停止にかかる時間を除外します。それがそこでの私の推論です、それについて考えて、それからあなたのベンチマークをもう一度試してください。

正確な値を取得するために、これらの言語のランタイム内で(ランタイムを持つ言語のために)プログラムのタイミングをとることを実際にお勧めします。たとえばCは、Erlang、Python、およびHaskellのようにランタイムシステムを起動および停止するためのオーバーヘッドがありません(これを98%保証しています - 私は正解です)。だから(この推論に基づいて)私はこのベンチマークはランタイムシステムの上で動く言語のために十分に正確/公平ではなかったと言って結論を出す。これらの変更を加えてもう一度やりましょう。

編集:すべての言語がランタイムシステムを持っていたとしても、それぞれを始めてそれを止めることのオーバーヘッドは異なるでしょう。だから私たちは(これが適用される言語のために)ランタイムシステムの中から時間をとることを勧めます。 Erlang VMは起動時にかなりのオーバーヘッドがあることが知られています。

8
Muzaaya Joshua

質問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, [email protected], 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
7
Mark Washeim

C++ 11、<20ミリ秒 - ここで実行する

私はあなたがあなたの言語特有の知識を向上させるのに役立つヒントがほしいと思うが、それはここでよくカバーされているので、私はあなたの質問などでmathematicaのコメントを見た人のために何らかの文脈を追加すると思いました。コードはとても遅くなりました。

この回答は、主に質問や他の回答に含まれるコードを他の人がより簡単に評価できるようにするためのコンテキストを提供することです。

このコードでは、使用される言語とは無関係に、次のような2つの(醜い)最適化のみが使用されます。

  1. すべてのトレーリング番号はn(n + 1)/ 2の形式です。
  2. nとn + 1は互いに素である
  3. 除数の数は乗法関数です
#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;
}

これは私のデスクトップでは平均19ミリ秒、ラップトップでは80ミリ秒かかる、これは私がここで見た他のコードの大部分とはかけ離れている。そして、利用可能な多くの最適化がまだあります。

5
user3125280

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.1690100%
go: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.1690100%
thaumkidのCバージョン:0.10608650%
最初のバージョン:8.2520111%
2番目のバージョン:0.023039865%

私はまたPython3.6とpypy3.3-5.5-alphaを試しました:

オリジナルのCバージョン:8.629100%
thaumkidのCバージョン:0.1097916%
Python3.6:54.79516%
pypy3.3-5.5-alpha:13.29165%

そして、次のコードで私は得た:

オリジナルのCバージョン:8.629100%
thaumkidのCバージョン:0.1098650%
Python3.6:1.489580%
pypy3.5-5.5-alpha:0.5821483%

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)
5
thanos

関係する数に小さな要素が多数ある場合にのみ、要素の数が多いと仮定しました。そこで私はthaumkidの優れたアルゴリズムを使いましたが、最初は決して小さすぎないファクターカウントの近似値を使いました。それは非常に簡単です:29までの素因数をチェックして、それから残りの数をチェックして、要素の数の上限を計算しなさい。これを使用して、因子数の上限を計算し、その数が十分に大きい場合は、正確な因子数を計算します。

以下のコードは正確さのためにこの仮定を必要としませんが、速くなるために。うまくいくようです。完全チェックを必要とするのに十分高い推定値を与えるのは、10万件に1件程度です。

これがコードです:

// 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より大きいので、

1
gnasher729

変更します。case (divisor(T,round(math:sqrt(T))) > 500) of

case (divisor(T,round(math:sqrt(T))) > 1000) of

これにより、Erlangのマルチプロセスの例に対する正しい答えが得られます。

1
user3051214

"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$
0
Witeman
#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

時間

2.79秒ユーザー0.00秒システム99%cpu 2.794合計

0
user3250089