Pamięć podręczna stron buforuje ostatnio używane strony z dysku twardego, skracając w ten sposób czas wyszukiwania kolejnych dostępu do tych samych danych. Pamięć podręczna stron nie zwiększa wydajności przy pierwszym wywołaniu strony z dysku twardego. Jeśli więc aplikacja ma zamiar odczytać plik raz i tylko raz, lepszym rozwiązaniem jest pominięcie pamięci podręcznej strony. Jest to możliwe dzięki zastosowaniu flagi O_DIRECT. Oznacza to, że jądro nie bierze pod uwagę tych konkretnych danych w pamięci podręcznej stron. Zmniejszenie rywalizacji o pamięć podręczną oznacza, że inne strony (do których dostęp byłby wielokrotny) mają większą szansę na zachowanie w pamięci podręcznej stron. Poprawia to współczynnik trafień w pamięci podręcznej, zapewniając lepszą wydajność.
void ioReadOnceFile()
{
/* Użycie direct_fd i direct_f pomija pamięć podręczną strony jądra.
* - direct_fd jest niskopoziomowym deskryptorem
pliku* - direct_f jest strumieniem plików podobnym do strumienia zwracanego przez fopen()
* UWAGA: Użyj getpagesize() do określenia optymalnego rozmiaru.
*/
int direct_fd = open("nazwa_pliku", O_DIRECT | O_RDWR);
PLIK *direct_f = fdopen(direct_fd, "w+");
/* direct disk-I/O zrobione TUTAJ*/
fclose(f);
close(fd);
}
Rozważmy przypadek odczytu w dużym pliku (tj. bazie danych) składającym się z ogromnej liczby stron. Każda kolejna strona, do której uzyskano dostęp, trafia do pamięci podręcznej tylko po to, by później zostać usunięta, gdy czytanych jest coraz więcej stron. To znacznie zmniejsza współczynnik trafień w pamięci podręcznej. W takim przypadku pamięć podręczna stron nie zapewnia żadnego wzrostu wydajności. Dlatego lepiej byłoby ominąć pamięć podręczną stron podczas uzyskiwania dostępu do dużych plików.
void ioLargeFile()
{
/* Użycie direct_fd i direct_f pomija pamięć podręczną stron jądra.
* - direct_fd jest niskopoziomowym deskryptorem
pliku* - direct_f jest strumieniem plików podobnym do strumienia zwracanego przez fopen()
* UWAGA: Użyj getpagesize() do określenia optymalnego rozmiaru.
*/
int direct_fd = open("largefile.bin", O_DIRECT | O_RDWR | O_LARGEFILE);
PLIK *direct_f = fdopen(direct_fd, "w+");
/* direct disk-I/O zrobione TUTAJ*/
fclose(f);
close(fd);
}
Harmonogram we/wy optymalizuje kolejność operacji we/wy, które mają być umieszczone w kolejce na dysku twardym. Ponieważ czas wyszukiwania jest największą karą na dysku twardym, większość harmonogramów we/wy stara się zminimalizować czas wyszukiwania. Jest to zaimplementowane jako wariant algorytmu windy, tj. zmiana kolejności losowo uporządkowanych żądań z wielu procesów do kolejności, w jakiej dane są obecne na dysku twardym, wymaga znacznej ilości czasu procesora.
Niektóre zadania, które obejmują złożone operacje, są zwykle ograniczone szybkością przetwarzania ogromnych ilości danych przez procesor. Złożony harmonogram we/wy działający w tle może zużywać cenne cykle procesora, zmniejszając w ten sposób wydajność systemu. W takim przypadku przejście na prostszy algorytm, taki jak no-op, zmniejsza obciążenie procesora i może poprawić wydajność systemu.
echo noop > /sys/block//queue/scheduler
Chociaż w końcu wykona to zadanie, zdecydowanie nie jest to najbardziej optymalny sposób. Z punktu widzenia jądra, najbardziej optymalnym rozmiarem dla żądań I/O jest rozmiar bloku systemu plików (tj. rozmiar strony). Ponieważ wszystkie operacje we/wy w systemie plików (i pamięć podręczna stron jądra) dotyczą stron, sensowne jest, aby aplikacja wykonywała również transfery w wielokrotnościach rozmiaru strony. Ponadto, ponieważ wielosegmentowe pamięci podręczne trafiają teraz do dysków twardych, można by odnieść ogromne korzyści, wykonując operacje we/wy w wielokrotnościach rozmiaru bloku.
Następujące polecenie może być użyte do określenia optymalnej statystyki rozmiaru
bloku--printf="bs=%s optimal-bs=%S\n" --file-system /dev/
Gdy aplikacja inicjuje odczyt we/wy SYNC, jądro kolejkuje operację odczytu danych i zwraca ją dopiero po odczytaniu całego bloku żądanych danych. W tym okresie jądro oznaczy proces aplikacji jako zablokowany dla we/wy. Inne procesy mogą wykorzystywać procesor, co skutkuje ogólnie lepszą wydajnością systemu.
Gdy aplikacja inicjuje zapis we/wy SYNC, jądro kolejkuje operację zapisu danych, umieszczając proces aplikacji w zablokowanym we/wy. Niestety oznacza to, że proces bieżącej aplikacji jest zablokowany i nie może wykonywać żadnego innego przetwarzania (ani we/wy) do momentu zakończenia operacji zapisu.
Gdy aplikacja inicjuje odczyt ASYNC I/O, funkcja read() zwykle zwraca wartość po odczycie podzbioru dużego bloku danych. Aplikacja musi wielokrotnie wywoływać metodę read() z rozmiarem danych pozostałych do odczytania, dopóki wszystkie wymagane dane nie zostaną wczytane. Każde dodatkowe wywołanie read wprowadza pewien narzut, ponieważ wprowadza przełączanie kontekstu między przestrzenią użytkownika a jądrem. Zaimplementowanie ciasnej pętli do wielokrotnego wywoływania read() marnuje cykle procesora, z których mogłyby korzystać inne procesy. W związku z tym zwykle implementuje się blokowanie za pomocą select(), dopóki następna read() nie zwróci niezerowego odczytu bajtów. tj. ASYNC jest tworzony do blokowania, tak jak robi to odczyt SYNC.
Gdy aplikacja inicjuje asynchroniczny zapis we/wy, jądro aktualizuje odpowiednie strony w pamięci podręcznej stron i oznacza je jako brudne. Następnie kontrolka szybko wraca do aplikacji, która może nadal działać. Dane są później usuwane na dysk twardy w bardziej optymalnym czasie (przy niskim obciążeniu procesora) w bardziej optymalny sposób (zapisy sekwencyjne).
W związku z tym odczyty SYNC i zapisy ASYNC są ogólnie dobrym rozwiązaniem, ponieważ pozwalają jądru zoptymalizować kolejność i czas podstawowych żądań we/wy.