Медленный Хаскель (... и быстрый Питон!)
Компилировал с каким -O?
(a, b) <- zip as bsА нельзя a <- as, b <- bs?
А нельзя a <- as, b <- bs?Это даст всевозможные сочетания a и b, zip работает не так.
balanceTastes = zipWith (\a b -> (a - b * k, a as bs
Map.fromList $ map (\(b, t) -> (balance + b, taste + t $ Map.toList
без -O, я когда пост написал, скомпилировал с -O2, лучший вариант стал ~1.5 секунды, что, конечно получше, но тоже полный фейл по сравнению с питоном.
addFruit (balance, taste) balanceToMaxTaste = Map.unionWith max balanceToMaxTaste $
Map.map +) taste) $ Map.mapKeysMonotonic +) balance) balanceToMaxTaste
но это работает только на упорядоченных мапах и там (с -O2) дало улучшение с 1.6 с до 1.5 с
в общем, копейки...
upd: хотя, позволило пройти TLE на codeforces
так красивее, да, но на производительность никак не влияет, bottleneck тут addFruit — шаг динамического программирования.
import Data.List
import Data.Maybe
readInts :: String -> [Int]
readInts = map read . words
merge xk,xv):xs) yk,yv):ys)
| xk < yk = (xk,xv) : merge xs y
| xk == yk = (xk, max xv yv) : merge xs ys
| xk > yk = (yk,yv) : merge x ys
merge xs ys = xs ++ ys
addFruit (balance, taste) acc = merge acc acc'
where acc' = map (\(b, t) -> (balance + b, taste + t acc
solve [n,k] as bs = if snd (fromMaybe (0,0) maxTaste) > 0 then snd $ fromJust maxTaste else -1
where maxTaste = find (\(b,t) -> b == 0) maxTastes
maxTastes = foldr addFruit [(0, 0)] balanceTastes
balanceTastes = [(a - b*k, a) | (a, b) <- zip as bs]
main = do
nk <- getLine
as <- getLine
bs <- getLine
print $ solve (readInts nk) (readInts as) (readInts bs)
Учитывая, сколько твой addFruit всего аллоцирует, неудивительно, что тормозит.
Для сравнения, то же решение на Python:я не понял, почему решение "то же" ? в питоновском решении не вижу функцию addFruit ! что сравниваем и чему удивляемся?
что сравниваем и чему удивляемся?сравниваем решения в том стиле, который характерен для языка. Программист на фортране может писать на фортране на любом языке, но тогда сравнивать языки будет не интересно, потому что вместо свойств языка будет измеряться качество реализации фортрана.
Вот ты берёшь книжку "learning haskell for greater good" и методично следуешь советам оттуда при составлении решения. Потом берёшь книжку "программировать как питономатик" и используешь рекомендации относительно питоновского кода и составляешь решение. Потом сравниваешь эффективность. Это и будет практическая эффективность языков.
А так, конечно, можно написать быструю либу на си, и через нативный интерфейс подгрузить её и в cpython, и в ghc, только смысл замера будет нулевой. Почти нулевые отличия по времени исполнения покажут почти нулевые отличия питона от хаскеля, когда ты используешь си для программирования обоих.
сравниваем решения в том стиле, который характерен для языка.в данном случае имеем другое:
на новом для меня языке под названием Haskellчтобы составить решение, характерное для Хаскеля, нужно очень сильно перевернуть сознание (если оно повернуто в другую сторону )
Вот ты берёшь книжку "learning haskell for greater good" и методично следуешь советам оттуда при составлении решения. Потом берёшь книжку "программировать как питономатик" и используешь рекомендации относительно питоновского кода и составляешь решение. Потом сравниваешь эффективность. Это и будет практическая эффективность языков.ничего общего с практической эффективностью здесь не будет
код, написанный на языке после прочтения книги "язык Х для чайников", не имеет никакой пользы и его нельзя использовать для набросов типа "Х быстрее Y"
$ time ./3a < 20
1750
real 0m1.958s
user 0m1.864s
sys 0m0.084s
Вообще, по сути это то же самое, что решение с деревянной мапой: там тоже за линейное время "сдвигаются" ключи и значения у дерева, а потом за линейное же время мержатся два дерева.
from sys import stdin as cin
from collections import defaultdict
def addFruit(b2t, bal, t):
b2t2 = b2t.copy
for ba, ta in b2t.iteritems:
b2t2[ba + bal] = max(b2t2[ba + bal], ta + t)
return b2t2
def main:
n,k = map(int, next(cin).split
a = map(int, next(cin).split
b = map(int, next(cin).split
b = [ x*k for x in b ]
b2t = defaultdict(lambda:0, { 0 : 0 })
for t,c in zip(a, b):
b2t = addFruit(b2t, t - c, t)
return b2t[0] or -1
print main
странно. Си для чайников мне вполне хватало на олимпиадах.
сравниваем решения в том стиле, который характерен для языка. Вот ты берёшь книжку "learning haskell for greater good" и методично следуешь советам оттуда при составлении решения.
код, написанный на языке после прочтения книги "язык Х для чайников"я её ещё не дочитал
не имеет никакой пользы и его нельзя использовать для набросов типа "Х быстрее Y"я не хочу набросить, наоборот хочу разобраться.
в моём понимании, ghc должен был там всё очень хорошо соптимизировать, так что чуть ли не на Сях написано получится.
ну уж точно не проигрывать питону, про который я привык думать, что он очень тормозной.
и решение у меня алгоритмически такое же как на питоне, и скомпилировано всё уже, и соптмизировано, и про типы всё знает, но вот почему получается тормознее — это я и пытаюсь понять.
в моём понимании, ghc должен был там всё очень хорошо соптимизировать, так что чуть ли не на Сях написано получится.Как в твоем понимании PyPy может быть быстрее CPython?
ну уж точно не проигрывать питону, про который я привык думать, что он очень тормозной.
и решение у меня алгоритмически такое же как на питонев этом и был мой изначальный вопрос. где тут одинаковые решения? я не распознал
вынес её отдельнотеперь смотрим пристально в addFruit на питоне и на хаскеле и пытаемся найти десять отличий:
в питоне делаем поиски логарифмические в мапе, а в хаскеле превращает дерево в список, мурыжим его и снова упаковываем в дерево
мне кажется это кардинально разными алгоритмами и последнее с точки зрения сложности должно жрать больше времени
зззз, и вообще, это разные addFruit, интерфейс разный, делают они немного разные вещи
Как в твоем понимании PyPy может быть быстрее CPython?ну как, JIT и всё такое... да ты на время исполнения посмотри.
psyco для CPython'а не поддерживается, так что всё понятно.
еперь смотрим пристально в addFruit на питоне и на хаскеле и пытаемся найти десять отличий:в питоне поиски O(1 вроде, там же хэши везде.
в питоне делаем поиски логарифмические в мапе, а в хаскеле превращает дерево в список, мурыжим его и снова упаковываем в дерево
хаскель же ленивый, я надеялся, что он всё это соптимизирует.
и в хаскеле я попробовал два варианта:
1. с хэшмапой, хэш один остаётся на месте, а второй строится всё равно с нуля, потом они мержатся, по идее оверхед не должен быть очень большой.
тут, конечно, было бы быстрее иметь функцию, unionWith, которая вторым параметром list принимает, но кто виноват, что её нет?
2. с деревянной мапой, смотри второй вариант, там дерево фиксится без распаковки, а потом два дерева мержатся в одно. всё это, как я себе представляю довольно быстро. или вариант от — примерно то же самое.
зззз, и вообще, это разные addFruit, интерфейс разный, делают они немного разные вещину извиняйте, в хаскеле иммутабл структуры, там в принципе нельзя сделать того что можно в питоне.
ну так это ограничение не с неба спустилось, предполагалось (я полагаю что язык будет эффективным при выбранном дизайне.
вот я переписал питоно-код в ужасном стиле:
from sys import stdin as cin
from itertools import chain
def addFruit(b2t, bal, tst):
b2t2 = ba + bal, ta + tst) for (ba,ta) in b2t.iteritems
res = {}
for b,t in chain(b2t.iteritems b2t2):
try:
res[b] = max(res[b], t)
except:
res[b] = t
return res
def main:
n,k = map(int, next(cin).split
a = map(int, next(cin).split
b = map(int, next(cin).split
b = [ x*k for x in b ]
b2t = { 0 : 0 }
for t,c in zip(a, b):
b2t = addFruit(b2t, t - c, t)
return b2t[0] or -1
print main
Да, на питоне он стал жутко тормозной, а вот с PyPy до сих пор работает на недосягаемой для Хаскеля скорости:
$ time pypy 3b.py < 20
1750
real 0m0.641s
user 0m0.560s
sys 0m0.064s
Кстати, Data.HashMap очень неудачно назван, потому что он тоже дерево (IntMap (Map k v
У меня получились такие результаты(a - исходный вариант, b - Data.IntMap.Strict, c - Data.HashTable, IO, d - BasicHashTable из пакета hashtables)
a
1750
real 0m1.692s
user 0m1.622s
sys 0m0.064s
---
b
1750
real 0m0.589s
user 0m0.577s
sys 0m0.011s
---
c
1750
real 0m0.496s
user 0m0.484s
sys 0m0.008s
---
d
1750
real 0m0.379s
user 0m0.370s
sys 0m0.009s
Код:
=====
b.hs
=====
import qualified Data.IntMap.Strict as Map
readInts :: String -> [Int]
readInts = map read . words
addFruit (balance, taste) balanceToMaxTaste = Map.unionWith max balanceToMaxTaste $
Map.mapKeysMonotonic (+balance) $ Map.map (+taste) balanceToMaxTaste
solve [n,k] as bs = if maxTaste > 0 then maxTaste else -1
where maxTaste = (Map.!) maxTastes 0
maxTastes = foldr addFruit (Map.singleton 0 0) balanceTastes
balanceTastes = [(a - b*k, a) | (a, b) <- zip as bs]
main = do
nk <- getLine
as <- getLine
bs <- getLine
print $ solve (readInts nk) (readInts as) (readInts bs)
=====
c.hs
=====
import qualified Data.HashTable as Hash
import Control.Monad
import Data.Maybe
readInts :: String -> [Int]
readInts = map read . words
updateMax dict (k, v) = Hash.lookup dict k >>= maybe (Hash.insert dict k v)
(\old -> if v > old then void $ Hash.update dict k v else return
addFruit dict (balance, taste) = do
list <- Hash.toList dict
mapM_ (\(b, t) -> updateMax dict (b + balance, t + taste list
return dict;
solve [n,k] as bs = do
dict <- Hash.new (==) Hash.hashInt
Hash.insert dict 0 0
dict <- foldM addFruit dict [(a - b * k, a) | (a, b) <- zip as bs]
Hash.lookup dict 0 >>= (return . fromMaybe (-1
main = do
nk <- getLine
as <- getLine
bs <- getLine
print =<< solve (readInts nk) (readInts as) (readInts bs)
=====
d.hs
=====
import qualified Data.HashTable.IO as Hash
import Control.Monad
import Data.Maybe
readInts :: String -> [Int]
readInts = map read . words
updateMax dict (k, v) = Hash.lookup dict k >>= maybe (Hash.insert dict k v)
(\old -> if v > old then Hash.insert dict k v else return
addFruit dict (balance, taste) = do
list <- Hash.toList dict
mapM_ (\(b, t) -> updateMax dict (b + balance, t + taste list
return dict;
solve [n,k] as bs = do
dict <- Hash.new :: IO (Hash.BasicHashTable Int Int)
Hash.insert dict 0 0
dict <- foldM addFruit dict [(a - b * k, a) | (a, b) <- zip as bs]
Hash.lookup dict 0 >>= (return . fromMaybe (-1
main = do
nk <- getLine
as <- getLine
bs <- getLine
print =<< solve (readInts nk) (readInts as) (readInts bs)
Ну вообще-то IO - это часть Haskell,Ну если бы я хотел IO, я бы просто не брал хаскель. Не на IO меня заманивали мануалы. Найди хоть один, где IO рекомендуется для statefull вычислений, а не для исключительно вввода-вывода. Если бы все мануалы честно говорили, без IO хаскель не юзабелен, то он растерял бы значительную часть своей популярности.
Нативные интерфейсы - это тоже часть и хаскеля, и cpython, но это же не значит, что мы должны написать модуль на сях, подключить нативно и радоваться производительности. Тогда проще сразу на сях писать.
Если бы все мануалы честно говорили, без IO хаскель не юзабелен, то он растерял бы значительную часть своей популярности.Отсутствие хэш-таблиц - это не неюзабельность. Лично мне всегда хватало Data.Map/Data.IntMap, а там, где хэш-таблицы становятся эффективнее, можно уже брать базу данных или специализированную сишную либу.
Нативные интерфейсы - это тоже часть и хаскеля, и cpython, но это же не значит, что мы должны написать модуль на сях, подключить нативно и радоваться производительности. Тогда проще сразу на сях писать.Не знаю, я пробовал - мне кажется, получается удобнее, чем на сях.
Ну вообще-то IO - это часть Haskell, и если структура данных эффективно реализуется только с помощью IO, то почему бы это не использовать.до IO я ещё не дочитал =)
но код выглядит не так круто как оригинальное решение.
как-то императивненько получается
Кстати, Data.HashMap очень неудачно назван, потому что он тоже дерево (IntMap (Map k vЯ брал unordered-containers, написано, что честный хэш-мэп.
У меня получились такие результаты(a - исходный вариант, b - Data.IntMap.Strict, c - Data.HashTable, IO, d - BasicHashTable из пакета hashtables)Спасибо! А можешь, плз, ещё для масштаба померять питоновское решение, желательно с PyPy.
хаскель же ленивый, я надеялся, что он всё это соптимизирует.Лень не про это. :]
Лень дает ускорение в других смыслах.
Если тебя удовлетворяет голова списка, тебе незачем шерстить его хвост. :]
Принцип Ходжи Насреддина. :]
чтобы составить решение, характерное для Хаскеля, нужно очень сильно перевернуть сознаниеИ каково подобное решение для данной задачи?
итак (компилировал -O2):
$ time ./3d < 20
1750
real 0m1.105s
user 0m1.064s
sys 0m0.016s
$ time ./3c < 20
1750
real 0m1.350s
user 0m1.316s
sys 0m0.020s
$ time pypy 3.py < 20
1750
real 0m0.579s
user 0m0.512s
sys 0m0.056s
Так что, увы. Я, вот честно скажу, расстроен.
Так что, увы. Я, вот честно скажу, расстроен.Ну, не надо было покупаться на рекламу.
Я так понял, что для эффективного динамического программирования хранение промежуточных результатов надо делать либо с помощью ленивых структур/мемоизации (см. фибоначчи либо используя монаду State, которая внутри юзает мутабельность.
итак (компилировал -O2):вот я себе поставил pypy + haskell и получил другие результаты
D:\cygwin\bin\time.exe ./d.exe 0<20убогий time в цигвине =\
1750
system 0:00.40
D:\cygwin\bin\time.exe D:\pypy-2.2.1\pypy.exe 3b.py 0<20
1750
system 0:00.68
сделал замер другой прогой:
>ptime.exe d.exe 0<20есть идеи что я делаю не так?
=== d.exe ===
1750
Execution time: 0.436 s
-----------------------------
ptime.exe D:\pypy-2.2.1\pypy.exe 3b.py 0<20
=== D:\pypy-2.2.1\pypy.exe 3b.py ===
1750
Execution time: 0.699 s
из оригинального поста, или тот который я потом специально "ухудшил", чтобы он походил на мой же хаскелевский код?
$ time pypy 3.py < 20
1750
real 0m0.709s
user 0m0.669s
sys 0m0.039s
$ time ./d < 20
1750
real 0m0.826s
user 0m0.809s
sys 0m0.016s
тут они почти сравнялись, но всё равно пипи чуть быстрее.
ну, в общем, скорее всего, тут различие в оптимизациях под разные ОСи/процессоры.
а ты питоновский код какой брал?как ты выше заметил, разницы для pypy нет
ptime.exe D:\pypy-2.2.1\pypy.exe 3b_orig.py 0<20
1750
Execution time: 0.709 s
------------------------------------------
ptime.exe D:\pypy-2.2.1\pypy.exe 3b.py 0<20
1750
Execution time: 0.699 s
тут различие в оптимизацияха дай-ка версию ghc
моя:
>ghc -v
Glasgow Haskell Compiler, Version 7.6.3, stage 2 booted by GHC version 7.4.1
$ ghc -v
Glasgow Haskell Compiler, Version 7.4.1, stage 2 booted by GHC version 7.4.1
FreeBSD сервер:
$ ghc -v
Glasgow Haskell Compiler, Version 7.6.3, stage 2 booted by GHC version 7.4.1
Оставить комментарий
psihodog
Я тут решил написать решение вот этой задачи на новом для меня языке под названием Haskell и вот что у меня получилось:Для сравнения, то же решение на Python:
Взял последний тест:
Скормил его программе на хаскеле и неприятно удивился:
ибо решение на питоне работает в 3 раза быстрее:
ну ладно... меняем Data.Map на Data.Map.Strict:
на Data.HashMap.Strict:
получше, конечно, но всё равно хуже стандартного питона в два раза!
а если уж PyPy взять:
А я, если честно ожидал приличного прироста в скорости при переходе на хаскель.
В общем, прошу показать, где я не правильно этот хаскель готовлю.