2009-09-01 14 views
20

Po dostarczeniu tego samego programu, który odczytuje losowo wygenerowany plik wejściowy i odtwarza ten sam ciąg, który odczytuje na wyjściu. Jedyna różnica polega na tym, że z jednej strony udostępniam metody odczytu i zapisu z linuksowych linków, az drugiej strony używam fread/fwrite.Dlaczego funkcja fwrite libc działa szybciej niż funkcja zapisu syscall?

Odmierzając czas na moją aplikację o wielkości wejściowej 10 MB i wyświetlając ją w/dev/null i upewniając się, że plik nie jest zapisany w pamięci podręcznej, odkryłem, że fwrite w libc jest szybsze w skali DUŻEJ przy użyciu bardzo małych buforów (1 bajt w przypadku).

Oto moje wyjście od czasu, przy użyciu fwrite:

real 0m0.948s 
user 0m0.780s 
sys  0m0.012s 

I stosując zapis syscall:

real 0m8.607s 
user 0m0.972s 
sys  0m7.624s 

Jedyna możliwość, że mogę myśleć o to, że wewnętrznie libc jest już buforowanie mój input ... Niestety nie mogłem znaleźć tak wielu informacji w Internecie, więc może ci guru mogliby mi pomóc.

+4

"Wewnętrznie libc już buforuje moje wejście". Dokładnie to robi. Prawdopodobnie możesz przeczytać kod źródłowy libc, jeśli chcesz, i zobaczyć dokładnie, jak to robi. – kquinn

Odpowiedz

29

Timing mój wniosek z wejściem 10 MB i powtarzając je /dev/null i upewniając się, że plik w nie buforowane, Znalazłem że LibC za frwite jest szybsza dużym skala przy przy użyciu bardzo małych buforów (1 bajt w przypadku ).

fwrite działa na strumieniach, które są buforowane. Dlatego wiele małych buforów będzie szybszych, ponieważ nie będzie kosztować systemu, dopóki bufor się nie zapełni (lub nie wyłączysz go lub nie zamkniesz strumienia). Z drugiej strony, małe bufory wysyłane do write będą uruchamiać kosztowne wywołanie systemowe dla dla każdego bufora - tutaj tracisz prędkość. Przy 1024 bajtowym buforze strumieniowym i pisaniu buforów 1-bajtowych, patrzysz na 1024 write wywołań dla każdego kilobajtów, zamiast 1024 fwrite połączeń zmienia się w jeden write - widzisz różnicę?

W przypadku dużych buforów różnica będzie niewielka, ponieważ będzie mniej buforów, a zatem bardziej spójna liczba wywołań systemowych między fwrite i write.

Innymi słowy, fwrite(3) to po prostu procedura biblioteczna, która zbiera dane wyjściowe na porcje, a następnie wywołuje write(2). Teraz, write(2), jest wywołanie systemowe , które zatrzymuje się w kernelu. Tam właśnie dzieje się I/O. Istnieje pewne obciążenie dla zwykłego wywoływania w jądro, a następnie jest czas, aby coś napisać. Jeśli używasz dużych buforów, okaże się, że write(2) jest szybszy, ponieważ ostatecznie musi zostać wywołany, a jeśli piszesz jeden lub więcej razy na fwrite, to narzut na buforowanie fwrite jest właśnie taki: więcej narzutów.

Jeśli chcesz przeczytać więcej na ten temat, możesz zapoznać się z this document, która wyjaśnia standardowe strumienie we/wy.

14

write (2) jest podstawowym operacja jądra.

fwrite (3) to funkcja biblioteczna, która dodaje buforowanie na wierzchu write (2).

Dla małych (np. Liniowych) bajtów liczba, fwrite (3) jest szybsza, ze względu na obciążenie związane z wykonywaniem wywołania jądra.

Dla dużych (bloków we/wy) bajtów liczba, write (2) jest szybsza, ponieważ nie przeszkadza w buforowaniu i musisz wywołać jądro w obu przypadkach.

Jeśli spojrzysz na źródło do cp (1), nie zobaczysz żadnego buforowania.

Wreszcie ostatnia uwaga: ISO C vs Posix. Funkcje biblioteki buforowanej, takie jak fwrite, są określone w ISO C, natomiast wywołania jądra takie jak write są Posix. Podczas gdy wiele systemów twierdzi, że zgodność ze standardem Posix, szczególnie podczas próby zakwalifikowania się do kontraktów rządowych, w praktyce jest to specyficzne dla systemów uniksowych. Więc buforowane operacje są bardziej przenośne. W rezultacie Linux cp z pewnością użyje write, ale program w języku C, który musi działać na wielu platformach, może wymagać użycia fwrite.

+0

Niedawno miałem wywiad i dałem to samo rozumowanie na temat różnic w zapisie b/w i fwrite, a odpowiedź, którą otrzymałem, brzmiała: "Twoja wiedza na temat tej różnicy jest całkowicie fałszywa" !!. Ankieter wydawał mi się bardzo arogancki. Mimo to, chciałem tylko potwierdzić, czy jest jakaś różnica między połączeniami wykonywanymi przez glibc a wywołaniami bezpośrednio do jądra? –

+0

@PK, zaktualizowałem swoją odpowiedź ... – DigitalRoss

Powiązane problemy