Во-первых, кажется, что с вашей машиной происходит что-то нехорошее. Когда я запускаю эту программу для файла 1G, кэшированного в памяти или в файловой системе tmpfs (неважно, какая), системное время существенно меньше:
1.44user 0.14system 0:01.60elapsed 99%CPU (0avgtext+0avgdata 50256maxresident)
Если у вас есть какая-либо другая загрузка или Нагрузка памяти, которая может составлять эти дополнительные 300 мсек, думаю, вам нужно сначала решить эту проблему, прежде чем что-то, что я скажу ниже, поможет, но ...
В любом случае, для своих тестов я использовал больший 5G testfile для упрощения количественного определения системного времени. Исходя из этого, программа C:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFLEN (1024*1024)
char buffer[BUFLEN];
int
main()
{
int nulls = 0;
int fd = open("/dev/shm/testfile5G.dat", O_RDONLY);
while (read(fd, buffer, BUFLEN) > 0) {
for (int i = 0; i < BUFLEN; ++i) {
if (!buffer[i]) ++nulls;
}
}
printf("%d\n", nulls);
}
, скомпилированная с gcc -O2
, запускается в моем тестовом файле со временем:
real 0m2.035s
user 0m1.619s
sys 0m0.416s
Для сравнения, программа Haskell, скомпилированная с ghc -O2
:
import Data.Word
import qualified Data.ByteString.Lazy as BSL
main :: IO ()
main = do
contents <- BSL.readFile "/scratch/buhr/testfile5G.dat"
print $ BSL.foldl' go 0 contents
where go :: Int -> Word8 -> Int
go n 0 = n + 1
go n _ = n
немного медленнее в подсчете, но имеет почти эквивалентное системное время:
real 0m8.411s
user 0m7.966s
sys 0m0.444s
Все более простые тесты, такие как cat testfile5G.dat >/dev/null
, дают согласованные результаты по системному времени, поэтому можно с уверенностью заключить, что накладные расходы на read
вызовы, скорее всего, специфический c процесс копирования данных из ядра в адресное пространство пользователя, составляют значительную долю в 410 мсек или около того системного времени.
Вопреки вашему опыту выше, переключение на mmap
должно уменьшить эти издержки. Программа Haskell:
import System.Posix.IO
import Foreign.Ptr
import Foreign.ForeignPtr
import MMAP
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
-- exact length of file
len :: Integral a => a
len = 5368709120
main :: IO ()
main = do
fd <- openFd "/scratch/buhr/testfile5G.dat" ReadOnly Nothing defaultFileFlags
ptr <- newForeignPtr_ =<< castPtr <$>
mmap nullPtr len protRead (mkMmapFlags mapPrivate mempty) fd 0
let contents = BS.fromForeignPtr ptr 0 len
print $ BS.foldl' (+) 0 contents
работает примерно с тем же временем пользователя, но существенно сокращает системное время:
real 0m7.972s
user 0m7.791s
sys 0m0.181s
Обратите внимание, что абсолютно необходимо использовать метод нулевой копии превращая отображаемую область в строгую ByteString
здесь.
На данный момент, я думаю, мы, вероятно, дошли до издержек на управление таблицами страниц процесса и на версию C с использованием mmap:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
size_t len = 5368709120;
int
main()
{
int nulls = 0;
int fd = open("/scratch/buhr/testfile5G.dat", O_RDONLY);
char *p = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
for (int i = 0; i < len; ++i) {
if (!p[i]) ++nulls;
}
printf("%d\n", nulls);
}
имеет похожее системное время:
real 0m1.888s
user 0m1.708s
sys 0m0.180s