Ответ 1
Я написал об этом здесь.
Насколько хороши производительность двоичных библиотек ввода/вывода на этих двух языках > Я размышляю о повторной записи уродливого (но очень быстрого) кода на С++, который обрабатывает двоичные файлы размером около 5-10 ГБ, используя стандартные функции fread и fwrite, Какой фактор замедления следует ожидать для оптимизированной реализации в F # и Haskell?
EDIT: здесь выполняется реализация C для подсчета нулевых байтов (буфер выделен на кучу).
#include <stdio.h>
#include <stdlib.h>
#define SIZE 32*1024
int main(int argc, char* argv[])
{
FILE *fp;
char *buf;
long i = 0, s = 0, l = 0;
fp = fopen(argv[1], "rb");
if (!fp) {
printf("Openning %s failed\n", argv[1]);
return -1;
}
buf = (char *) malloc(SIZE);
while (!feof(fp)) {
l = fread(buf, 1, SIZE, fp);
for (i = 0; i < l; ++i) {
if (buf[i] == 0) {
++s;
}
}
}
printf("%d\n", s);
fclose(fp);
free(buf);
return 0;
}
Результаты:
$ gcc -O3 -o ioc io.c
$ ghc --make -O3 -o iohs io.hs
Linking iohs ...
$ time ./ioc 2.bin
462741044
real 0m16.171s
user 0m11.755s
sys 0m4.413s
$ time ./iohs 2.bin
4757708340
real 0m16.879s
user 0m14.093s
sys 0m2.783s
$ ls -lh 2.bin
-rw-r--r-- 1 14G Jan 4 10:05 2.bin
Я написал об этом здесь.
Haskell, использующий ленивый IO на основе ByteString, с "двоичным" парсером должен быть примерно такой же, как и код C, выполняющий одно и то же задание, в тех же типах данных.
Ключевые пакеты, о которых нужно знать:
Учитывая, что это сообщение влечет за собой:
... безопасно сказать, что я нахожусь над моей головой. Тем не менее, я всегда чему-то научился, когда попадаю себе в голову, так что здесь.
Я пошел по модулю Data.ByteString.Lazy.*
Haskell через Hoogle и нашел length для измерения длины ленивой байтовой строки. Он реализуется таким образом:
length :: ByteString -> Int64
length cs = foldlChunks (\n c -> n + fromIntegral (S.length c)) 0 cs
Хм. Джон сказал, что "... Складывание более фрагментов файла в F # является важной частью того, почему это быстро..." (мой акцент). И эта функция length
, как представляется, реализована с использованием короткой складки. Таким образом, кажется, что эта функция намного больше похожа на "яблоки на яблоки" на код Jon F #.
Есть ли разница в практике? Я сравнил пример Джона со следующим:
import System
import Data.List
import Data.ByteString.Lazy as B
main =
getArgs
>>= B.readFile . Data.List.head
>>= print . B.length
Пример Jon Haskell на моем компьютере для файла с 1,2 ГБ: 10,5 с
Версия 'chunky': 1.1s
"Короткая" версия кода Haskell быстрее десять раз. Это говорит о том, что он, вероятно, в несколько раз быстрее, чем Джон оптимизировал код F #.
ИЗМЕНИТЬ
Хотя я не совсем полностью согласен с критикой Джона на моем примере, я хотел бы сделать его максимально возможным. Таким образом, я профилировал следующий код:
import System
import Data.List
import Data.ByteString.Lazy as B
main =
getArgs
>>= B.readFile . Data.List.head
>>= print . B.count 0
Этот код загружает содержимое целевого файла в ByteString, а затем "подсчитывает" каждое появление байта с 0 значениями. Если я что-то не хватает, эта программа должна загружать и оценивать каждый байт целевого файла.
Вышеупомянутая программа работает примерно в 4 раза быстрее, чем самая быстрая программа Haskell, представленная Джоном, скопированная здесь для справки (в случае ее обновления):
import System
import Data.Int
import Data.List
import Data.ByteString.Lazy as B
main =
getArgs
>>= B.readFile . Data.List.head
>>= print . B.foldl (\n c -> n + 1) (0 :: Data.Int.Int64)