Мне нужно (для некоторой не относящейся к делу цели) создать большой
файл (от 100 мегабайт до неск. гигабайт), напр. для простоты забитый
одними двоичными нулями.
Т.е. итеративно пишем в файл двоичный ноль или строку, содержащую
'\x00' нужное количество раз.
Изначально я сделал это на Perl и на C. Потом стал использовать данную
задачу как некий тест, написал аналогичные программки на Lua и на
Haskell.
Получились следующие тайминги для генерации 100 мегабайтного файла:
1. C 22 сек
2. Perl 53 сек
3. Lua 83.766 сек
4. Haskell (компилир) 100.484375 сек
5. Haskell (runghc) 110.765625 сек
Я в ужасе. Как я понимаю, в моем случае -- проблема в неумении
работать с организацией циклов в монаде IO. Намеренно говорю о циклах,
хотя написан код как рекурсия. Видимо, при организации императивного
алгоритма уместно думать в терминах все-таки циклов.
Вопрос, что я сделал плохо, как сделать хорошо?
Мало того, что работает медленно, так еще и громоздко!
(я скажу, что первые версии без буферизации и т.п. вообще работали по
800 сек!)
Или так и должно быть? У меня опыта маловато...
Привожу листинг на Haskell:
import System.IO
import System.CPUTime
main = do
t1 <- getCPUTimeDouble
print t1
let n = 1024*1024*100+1
let f = "white100"
h <- openFile f WriteMode
hSetBuffering h (BlockBuffering (Just (1024)))
b <- hGetBuffering h
-- print b
loop n h
hClose h
t2 <- getCPUTimeDouble
print t2
print (t2 - t1)
loop :: Integer -> Handle -> IO()
loop 0 h = return ()
loop i h = do
hPutChar h '\x00'
loop (i-1) h
getCPUTimeDouble :: IO Double
getCPUTimeDouble = do t <- System.CPUTime.getCPUTime; return
((fromInteger t) * 1e-12)
Ну и для сравнения листинг на Perl
#!/usr/bin/perl -w
$n = 100*1024*1024; # число байт
open (WRITEFILE, '>white100') or die "Can't open writefile: $!";
binmode(WRITEFILE);
$t1 = time();
print "\n".$t1;
for($i=0;$i<$n;$i++) { print WRITEFILE "\x00"; };
$t2 = time();
print "\n".$t2." ".($t2-$t1);
close;
P.S. подскажите, как отвечать в обсуждениях? Я отправил ответ
("Автору", больше вариантов кроме "Переслать", не нашел) -- в теме "о
как узнать сколько времени выполняется код?" Но так и не увидел своего
ответа, в чем дело?
>
> P.S. подскажите, как отвечать в обсуждениях? Я отправил ответ
> ("Автору", больше вариантов кроме "Переслать", не нашел) -- в теме "о
> как узнать сколько времени выполняется код?" Но так и не увидел своего
> ответа, в чем дело?
У нас стоит премодерация для новых участников из-за обилия спама. Ваше
сообщение ее успешно прошло - теперь можете участвовать в обсуждении
:)
Исходный вариант на моей машинке работает 22 секунды.
А вот такой всего три с половиной:
main = do
t1 <- getCPUTimeDouble
print t1
let n = 1024*1024*100+1
let f = "white100"
writeFile f $ replicate n '\x00'
t2 <- getCPUTimeDouble
print t2
print (t2 - t1)
Собирать лучше с оптимизацией: ghc --make -O2 ...
26 июня 2009 г. 18:35 пользователь nsk.coder (nsk....@gmail.com) написал:
1. Действительно, при указанных флагах оптимизации на моей машине
исходная программа прошла за 56 сек, а Ваш вариант программы -- за 11
(быстрее неоптимизированного вариант на С !!)
2. Идея использования replicate мне в голову не пришла вообще -- очень
удивился эффективности!
3. Однако, решив одну проблему (с большой строкой одинаковых
символов), я бы хотел перепостановить вопрос так:
Хорошо, пусть мне надо не повторять один и тот же символ, а, скажем,
считывать побайтно огромный файл (и даже не весь, а фиксированное
число сотен мегабайт или гигабайт), делать обработку каждого байта
какой-то функцией и записывать байт-результат последовательно в другой
файл.
Я клоню вот к чему -- как эффективно организовать цикл в монаде IO --
мне кажется, что рекурсия при миллиардах вызовов становится очень
неэффективной и нужно как-то делать простой цикл.
В данном случае, Вы очень красиво учли специфику первой задачи и
возможность оптимизации.
А в общем случае? Если монада IO -- это "островок императивного мира",
то должно быть эффективное средство делать цикл внутри нее??
main = readFile "in.txt" >>= writeFile "out.txt" . map f
f = chr . (+1) . ord -- обработка байта
Идея заключается в использовании результатов ленивых функций как итераторов.
Т.е. readFile "in.txt" возвращает не содержимое файла, а итератор по нему.
А как быть с циклами в IO я и сам не знаю. Не часто доводилось такое делать.
Как я понял из предложенных советов -- нужды в циклах, даже внутри
монадных вычислений вроде как нет, вполне можно обойтись обычными
ленивыми вычислениями с рекурсиями...
Если однако, не всплывет задача, требующая строгих вычислений и
больших объемов..
On 30 июн, 18:03, Maxim Taldykin <jor...@gmail.com> wrote:
> 30 июня 2009 г. 14:48 пользователь nsk.coder (nsk.co...@gmail.com) написал:
А можно уточнить, что понимается под "глубиной рекурсии равной 2" ??
Судя по оперделению в Prelude -- map определяется рекурсивно, и тогда
конечно же должен прямо зависеть от длины списка.
В примере Maxim'a Taldykin'a, как я понимаю, эффективность достигается
за счет ленивости -- и то, это вроде касается скорости и использования
памяти для хранения списка (т.е. один файл лениво читается, а в другой
-- лениво сбрасывается, весь список в памяти держать не надо)
Интересно, а если гигантский список придется держать в памяти?
Ну и кроме того, при рекурсии ведь нужно держать в памяти предыдущие
незаконченные вычисления -- а если таковых на гигабайты?
Все равно соменения остаются и есть ощущение в необходимости
огранизации циклов...