Co dzieje się za zasłonami podczas I / O dysku?

Kiedy szukam jakiejś pozycji w pliku i zapisuję małą ilość danych (20 bajtów), co dzieje się za kulisami?

Moje zrozumienie

Według mojej wiedzy najmniejszą jednostką danych, którą można zapisać lub odczytać z dysku, jest jeden sektor (tradycyjnie 512 bajtów, ale ten standard się zmienia). Oznacza to zapisanie 20 bajtów. Muszę odczytać cały sektor, zmodyfikować jego część w pamięci i zapisać na dysku.

Tego właśnie oczekuję w niebuforowanych we / wy. Spodziewam się również, że buforowane I / O będą robić mniej więcej to samo, ale bądź mądry co do jego pamięci podręcznej. Pomyślałbym więc, że jeśli wybiję lokację przez okno, wykonując losowe wyszukiwania i zapisy, zarówno buforowane, jak i niebuforowane I / O powinny mieć podobną wydajność ... być może z niebuforowanym wyjściem nieco lepszym.

Z drugiej strony wiem, że dla buforowanego I / O jest szalone, aby buforować tylko jeden sektor, więc mogę również oczekiwać, że zadziała strasznie.

Moja aplikacja

Przechowuję wartości zebrane przez sterownik urządzenia SCADA, który odbiera telemetrię zdalną za ponad sto tysięcy punktów. W pliku znajdują się dodatkowe dane, tak że każdy rekord ma 40 bajtów, ale tylko 20 bajtów tego wymaga zapisania podczas aktualizacji.

Benchmark przedwdrożeniowy

Aby sprawdzić, czy nie muszę wymyślać genialnie przemyślanego rozwiązania, przeprowadziłem test z użyciem kilku milionów losowych rekordów zapisanych w pliku, który może zawierać łącznie 200 000 rekordów. Każdy test zawiera generator liczb losowych o takiej samej wartości, aby był sprawiedliwy. Najpierw wymazuję plik i wpisuję go do całkowitej długości (około 7,6 meg), a następnie pętlę kilka milionów razy, przekazując losowy offset pliku i niektóre dane do jednej z dwóch funkcji testowych:

void WriteOldSchool( void *context, long offset, Data *data )
{
    int fd = (int)context;
    lseek( fd, offset, SEEK_SET );
    write( fd, (void*)data, sizeof(Data) );
}

void WriteStandard( void *context, long offset, Data *data )
{
    FILE *fp = (FILE*)context;
    fseek( fp, offset, SEEK_SET );
    fwrite( (void*)data, sizeof(Data), 1, fp );
    fflush(fp);
}

Może nie ma niespodzianek?

TheOldSchool metoda wyszła na szczyt - dużo. Był ponad 6 razy szybszy (1,48 mln w porównaniu z 232000 rekordów na sekundę). Aby upewnić się, że nie wpadłem na buforowanie sprzętowe, rozszerzyłem rozmiar mojej bazy danych do 20 milionów rekordów (rozmiar pliku 763 megabajty) i uzyskałem takie same wyniki.

Zanim wskażesz oczywiste wezwanie dofflush, pozwól mi powiedzieć, że usunięcie go nie przyniosło skutku. Wyobrażam sobie, że to dlatego, że skrytka musi być popełniona, gdy szukam wystarczająco daleko, co robię przez większość czasu.

Więc co się dzieje?

Wydaje mi się, że buforowane I / O muszą odczytywać (i być może zapisywać wszystkie) dużą część pliku, gdy próbuję pisać. Ponieważ rzadko korzystam z jego pamięci podręcznej, jest to bardzo marnotrawne.

Ponadto (i nie znam szczegółów buforowania sprzętowego na dysku), jeśli buforowane we / wy próbuje zapisać kilka sektorów, gdy zmieniam tylko jeden, zmniejszyłoby to skuteczność pamięci podręcznej sprzętu.

Czy są tam eksperci od dysków, którzy mogą komentować i wyjaśniać to lepiej niż moje eksperymentalne wyniki? =)

questionAnswers(2)

yourAnswerToTheQuestion