私は現在、ProjetEulerで問題14の解決策を最適化しようとしています。私はHaskellを本当に楽しんでおり、この種の問題に非常に適していると思います。これが私が試した3つの異なる解決策です。
import Data.List (unfoldr, maximumBy)
import Data.Maybe (fromJust, isNothing)
import Data.Ord (comparing)
import Control.Parallel
next :: Integer -> Maybe (Integer)
next 1 = Nothing
next n
| even n = Just (div n 2)
| odd n = Just (3 * n + 1)
get_sequence :: Integer -> [Integer]
get_sequence n = n : unfoldr (pack . next) n
where pack n = if isNothing n then Nothing else Just (fromJust n, fromJust n)
get_sequence_length :: Integer -> Integer
get_sequence_length n
| isNothing (next n) = 1
| otherwise = 1 + (get_sequence_length $ fromJust (next n))
-- 8 seconds
main1 = print $ maximumBy (comparing length) $ map get_sequence [1..1000000]
-- 5 seconds
main2 = print $ maximum $ map (\n -> (get_sequence_length n, n)) [1..1000000]
-- Never finishes
main3 = print solution
where
s1 = maximumBy (comparing length) $ map get_sequence [1..500000]
s2 = maximumBy (comparing length) $ map get_sequence [500001..10000000]
solution = (s1 `par` s2) `pseq` max s1 s2
ここで、実際の問題を見ると、ほとんどの新しいシーケンスには以前に計算されたサブシーケンスが含まれるため、キャッシュの可能性がたくさんあります。
比較のために、私もCでバージョンを作成しました。キャッシュありの
実行時間:0.03秒
キャッシュなしの実行時間:0.3秒
それは正気じゃない!確かに、キャッシュは時間を10分の1に短縮しましたが、キャッシュしなくても、私のHaskellコードよりも少なくとも17倍高速です。
私のコードの何が問題になっていますか?Haskellが関数呼び出しをキャッシュしないのはなぜですか?関数は純粋なキャッシングであるため、キャッシングは些細なことではなく、使用可能なメモリの問題だけですか?
私の3番目の並列バージョンの問題は何ですか?どうして終わらないの?
Haskellを言語として、コンパイラーはいくつかのコード(フォールド、マップなど)を自動的に並列化しますか、それともControl.Parallelを使用して常に明示的に実行する必要がありますか?
編集:私はこの同様の質問に出くわしました。彼らは、彼の機能は末尾再帰ではないと述べました。get_sequence_lengthの末尾再帰は再帰的ですか?そうでない場合、どうすればそうすることができますか?
Edit2:
ダニエルへ:
返信ありがとうございます。本当に素晴らしいです。私はあなたの改善をいじってみました、そして私はいくつかの本当に悪い落とし穴を見つけました。
Windws 7(64ビット)、8GBRAMを搭載した3.3GHZクアッドコアでテストを実行しています。
私が最初にしたことは、あなたが言うように、すべてのIntegerをIntに置き換えることでしたが、メインのいずれかを実行するたびに、+ RTS kSize -RTSが途方もなく高く設定されていても、メモリが不足しました。
最終的に私はこれを見つけました(stackoverflowは素晴らしいです...)、これはWindows上のすべてのHaskellプログラムが32ビットとして実行されているため、Intがオーバーフローして無限の再帰を引き起こしていたことを意味します...
代わりに、Linux仮想マシン(64ビットghcを使用)でテストを実行したところ、同様の結果が得られました。