[Haskell] Жуткие тормоза на простой задаче
let range start stop =
let rec range_ acc s = function
| f when s = f -> s :: acc
| x -> range_ (x :: acc) s (x-1) in
if start < stop then range_ [] start stop else List.rev (range_ []
stop start);;
let divisorSum x =
let rec divisorSumAcc acc = function
| 1 -> acc + 1
| y -> divisorSumAcc (acc + if x mod y == 0 then y else 0) (y - 1) in
divisorSumAcc 0 x;;
let isPerfect x = x == (divisorSum x) - x;;
List.filter isPerfect (range 1 5000);;
Ушло 0.515 секунды. Это без компиляции в нативный код. С ней — 0.157 секунды.
Для 100000 получается 3m16.936s и 1m1.231s соответственно.
Память не кушает от слова «совсем».
Выводы оставим нашим маленьким радиослушателям.
Ну этот алгоритм не такой короткий и прямолинейный, как у меня (13 строчек вместо трех). И, кажется, используется рекурсия. Можно попробовать реализовать подобное на Haskell-е и сравнить с Вашими результатами!
let (-->) start stop =
let rec range_ acc s = function
| f when s = f -> s :: acc
| x -> range_ (x :: acc) s (x-1) in
if start < stop then range_ [] start stop else List.rev (range_ [] stop start);;
let divisorSum x =
let ssum y = if x mod y == 0 then y else 0 in
List.fold_left (+) 0 (List.map ssum (1-->x;;
let isPerfect x = x == (divisorSum x) - x;;
List.filter isPerfect (1-->5000);;
Первая длинная функция нужна, потому что в окамле нет встроенного генератора списка.
Для 5000 стало:
- в байткоде 1.510с
- нативный код 0.311с
Хотя я не очень понимаю, чем хвостовая рекурсия не Ъ у реальных пацанов.
Для 5000 стало:Спасибо за ответ!
- в байткоде 1.510с
- нативный код 0.311с
Свой код на Haskell я тоже переписал, в виде рекурсии (как у Вас но результаты стали еще хуже
Пока разбираюсь, в чем дело. Может быть я неправильно использую компилятор (GHC) или интерпретатор ...
или интерпретатор ...или ошибка в генетическом коде?
Для 5000 стало:Разобрался! Все тормоза были не из-за Haskell-а, а из-за его интерпретатора (ghci)!
- в байткоде 1.510с
- нативный код 0.311с
Скомпилированный exe-файл для случая 5000 чисел выполняется меньше 10 секунд!
******
Весь Haskell-код состоит из двух файлов:
Main.hs
module Main where
import PerfectNumbers
main = print ( perfList 5000 )
PerfectNumbers.hs
perfList n = [x | x <- [1..n], isPerfect x]
isPerfect x = x == (divisorSum x) - x
divisorSum x = sum [d | d <- [1..x], x `mod` d == 0]
А компиляция в PerfectNumbers.exe осуществляется так:
>ghc -o PerfectNumbers Main.hs
Вот такой файл:
isPerfect x = x == (divisorSum x) - x
divisorSum x = sum [d | d <- [1..x], x `mod` d == 0]
perfList n = [x | x <- [1..n], isPerfect x]
main = print ( perfList 5000 )
Собранный командой
ghc -O2 pn.hs -o pn.exe
у меня отрабатывает за 0.55 секунды на ноуте с Core 2 Duo 2.0 GHz. GHC старый, 6.12.
module test
import StdEnv
isPerfect x = x == (divisorSum x) - x
divisorSum x = sum [d \\ d <- [1..x] | x rem d == 0]
perfList n = [x \\ x <- [1..n] | isPerfect x]
Start = perfList 5000
отрабатывает за 0.13 секунды. И exe-шник всего 59 КБ против 907 КБ хаскельного.
open ExtLib
let nums x = List.init x +) 1)
let divisorSum x = List.fold_left (+) 0 (List.filter (fun d -> x mod d = 0) (nums x
let isPerfect x = x == (divisorSum x) - x
let perfList n = List.filter isPerfect (nums n);;
List.iter (Printf.printf "%d ") (perfList 5000);;
0.57 секунды, 393 КБ.
То же самое на Enum'aх (без создания списков в памяти):
let nums x = Enum.init x +) 1)
let divisorSum x = Enum.fold (+) 0 (Enum.filter (fun d -> x mod d = 0) (nums x
let isPerfect x = x == (divisorSum x) - x
let perfList n = Enum.filter isPerfect (nums n);;
Enum.iter (Printf.printf "%d ") (perfList 5000);;
0.25 секунды.
И exe-шник всего 59 КБ против 907 КБ хаскельного.Тут сравнивать не совсем корректно, т.к. хаскель туда похоже много чего по умолчанию запихивает. Т.е. если код будет не 3 строчки, а 300, то хаскельный exe-шник в 100 раз не увеличится. Может местны тру-хаскел-хакеры знают как это уменьшить?
ghc -O2 pn.hs -o pn.exeПопробовал так откомпилировать два файла (Main.hs и PerfectNumbers.hs). Получил те же 10 секунд для n = 5000. Но когда соединил два файла в один, тогда полученный при компиляции exe-шник отработал за 4 секунды на моем AMD Athlon 64 3000+. При этом 4 секунды получается для обоих способов компиляции (Вашего и моего).
Довольно странный результат. Может быть дело в отложенных вычислениях, которые воспринимаются компилятором в контексте только одного Haskell-модуля? Т.е. если в самом модуле написано: perfList 5000, то отложенные вычисления иначе влияют на работу программы?
Попробуй явно указать типы (Int'ы и их списки). Есть подозрение, что так он слишком обобщенный код генерит.
Тут сравнивать не совсем корректно, т.к. хаскель туда похоже много чего по умолчанию запихивает. Т.е. если код будет не 3 строчки, а 300, то хаскельный exe-шник в 100 раз не увеличится.Совершенно верно, у хаскеля рантайм намного сложнее и оттого толще. У клина простой и большей частью написанный на асме (в частности, там два разных сборщика мусора, и каждый написан на нескольких разновидностях асма - под разные платформы). В 100 раз не увеличится, но в среднем GHC все равно намного жирнее генерит код. Бороться частично можно strip'ом, частично хитрой сборкой используемых библиотек.
Попробуй явно указать типы (Int'ы и их списки).Здорово, спасибо!
Теперь для n = 5000 программа выполняется за 0,8 секунды !
Т.е. если код будет не 3 строчки, а 300, то хаскельный exe-шник в 100 раз не увеличится. Может местны тру-хаскел-хакеры знают как это уменьшить?
ghc -no-implicit-prelude
?
прогайте на фортране, компильте с O3 и будет вам счастие.
Панч-карты — выбор мужей!
Панч-карты — выбор мужей!не юродствуй, в отличие от панч-карт фортран вполне ок, да и код ifort с оптимизациями генерит реально быстрый.
Кто бы сомневался.
Вся история фортрана наталкивает на мысль о его скорости.
И о том, что программы сложнее интегрирования двумерной разностной схемы на нем писать отдает самоубийством.
Убедил, можешь запостить тут свое решение на фортране, только чур не больше 8 строк, а то уже не будет счастие.
PROGRAM SAMPLE
INTEGER, DIMENSION (5000) :: a = (/(k,k=1,5000)/)
DO 6, I=1,5000
IF (SUM (a, MASK=MOD(I,a) == 0) - I == I) THEN
WRITE (*,'(I5)') I
END IF
6 CONTINUE
END
Компилировал gfortran, без оптимизаций
-rwxrwxr-x 1 oks oks 28810 июля 15 21:34 a.out
Вот время:
perseus:~/work/Fortran$ /usr/bin/time -v ./a.out
6
28
496
Command being timed: "./a.out"
User time (seconds): 0.15
System time (seconds): 0.00
Percent of CPU this job got: 98%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.15
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 3488
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 274
Voluntary context switches: 1
Involuntary context switches: 21
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
Update: кажется, это фортран 90 получился, на 77 так не получится написать.
А с оптимизацией? (выше ключик -O3 предлагали) Без нее выигрыша не видно пока.
Думаю, чудодейственность фортрана сильно преувеличена. Либо есть какой-то хак, про который я не в курсе.
Настоящие сварщики, может быть, скажут, что ifort надо использовать и OpenMP добавить.
БТВ, можно ничего не изобретать и не писать для этой задачи самому, ибо гугл выдает вполне себе уже готовый код.
Оставить комментарий
nikola1956
Вчера начал изучать Haskell для общего развития, так как понравился красивый синтаксис языка по сравнению с другими функциональными языками программирования (Scala, Clojure, OCaml).Решил написать тестовый модуль:
Загружаю этот модуль в командной строке Windows (предварительно установил на комп Haskell Platform):
Запускаю:
Выдает:
Но работает больше минуты !
А в случае, когда вместо 5000 стоит 100000, использует больше гигабайта оперативной памяти и зависает более, чем на полчаса.
Не понятно, то ли я некорректно использую Haskell, то ли Haskell сам по себе тормозной, то ли сама идея функционального программирования (использовать неизменяемые области памяти и вообще свободнее тратить оперативную память) — ущербна для подобного рода "объемных" задач?