2013-03-28 12 views
20

Eksperymentujemy ze zmianą SQLite, wbudowanego systemu baz danych, , aby użyć mmap() zamiast zwykłych wywołań read() i write() w celu uzyskania dostępu do bazy danych plik na dysku. Używanie jednego dużego odwzorowania dla całego pliku . Załóżmy, że plik jest wystarczająco mały, abyśmy nie mieli problemu ze znalezieniem dla niego miejsca w pamięci wirtualnej.Jak rozszerzyć dostępnie do pliku dostępnego za pomocą mmap()

Jak dotąd tak dobrze. W wielu przypadkach użycie mmap() wydaje się być trochę szybsze niż read() i write(). A w niektórych przypadkach znacznie szybciej.

Zmiana rozmiaru mapowania w celu zatwierdzenia transakcji zapisu, która rozszerza plik bazy danych o , wydaje się być problemem. W celu przedłużenia plik bazy danych, kod może zrobić coś takiego:

ftruncate(); // extend the database file on disk 
    munmap();  // unmap the current mapping (it's now too small) 
    mmap();   // create a new, larger, mapping 

skopiuj nowe dane do końca nowego mapowania pamięci. Jednak munmap/mmap jest niepożądany, ponieważ oznacza, że ​​następnym razem każda strona pliku bazy danych jest dostępna, występuje błąd małej strony i system musi przeszukać pamięć podręczną strony OS dla poprawnej ramki do powiązana z wirtualną adres pamięci. Innymi słowy, spowalnia ona kolejne odczyty bazy danych.

W systemie Linux możemy użyć niestandardowego wywołania systemowego mremap() zamiast z munmap()/mmap(), aby zmienić rozmiar odwzorowania. Wydaje się, że unika to błędów związanych z mniejszą stroną.

PYTANIE: Jak to zrobić w przypadku innych systemów, takich jak OSX, , które nie mają mremap()?


Mamy obecnie dwie pomysły. I pytanie dotyczące każdego z nich:

1) Utwórz odwzorowania większe niż plik bazy danych. Następnie, rozszerzając plik bazy danych o , po prostu wywołaj funkcję ftruncate(), aby rozszerzyć plik na dysk i dalej używać tego samego odwzorowania.

To byłoby idealne i wydaje się działać w praktyce. Jednak jesteśmy martwi tego ostrzeżenia w manualu:

„Wpływ zmiany rozmiaru pliku bazowych mapowania na stronach, które odpowiadają dodanych lub usuniętych regionów plik jest nieokreślona . "

PYTANIE: Czy to coś, o co powinniśmy się martwić? A może anachronizm w tym momencie?

2) Jeżeli rozszerzenie pliku bazy danych, wykorzystanie pierwszego argumentu mmap() żądania odwzorowania odpowiadające nowej strony pliku bazy umieszczony bezpośrednio po bieżącej mapowania w wirtualnej pamięci. Skuteczne rozszerzenie początkowego mapowania. Jeśli system nie może spełnić żądania umieszczenia nowego odwzorowania natychmiast po pierwszym, cofnij się do munmap/mmap.

W praktyce okazało się, że OSX jest całkiem niezły w pozycjonowaniu mapowania w ten sposób, więc ta sztuczka działa tam.

pytanie: Jeśli system ma natychmiast przeznaczyć drugie odwzorowanie po pierwsze w pamięci wirtualnej, to jest wtedy bezpieczne ostatecznie unmap nimi zarówno przy użyciu jednego dużego wezwanie do munmap()?

+0

Robiłem dokładnie to samo. W systemie Solaris 10 'munmap' wykonuje synchroniczne' msync', jeśli dobrze pamiętam. W rzeczywistości 'msync' był zawsze synchroniczny w systemie Solaris 10, nawet jeśli podano' MS_ASYNC'. To były dwa ostatnie gwoździe w trumnie Solaris. –

+0

Nie sądzę, że # 1 jest wykonalne. Utworzenie odwzorowania większego niż plik spowoduje, że koniec pliku nie będzie dostępny (chociaż może być "odwzorowany"), a 'ftruncate()' nie zaktualizuje odwzorowania. – twalberg

Odpowiedz

3
  1. Myślę, że # 2 jest najlepszym obecnie dostępnym rozwiązaniem. Ponadto w systemach 64-bitowych możesz utworzyć swoje mapowanie bezpośrednio pod adresem, którego system operacyjny nigdy nie wybrałby do mapowania (na przykład 0x6000 0000 0000 0000 w systemie Linux), aby uniknąć sytuacji, w której system operacyjny nie może umieścić nowego mapowania natychmiast po pierwszym jeden.

  2. Zawsze bezpiecznym rozwiązaniem jest mapowanie mnapple mappinsg za pomocą pojedynczego wywołania munmap. Możesz nawet odmapować część mapowania, jeśli chcesz to zrobić.

+6

najbardziej realistyczne implementacje 64-bitowe (tj. Rzeczywiste cpus) nie obsługują 64-bitowych przestrzeni adresowych. na przykład żaden z istniejących procesorów AMD64 nie obsługuje adresu 0x0000 0000 0000 0x6000. –

4
  1. Zastosowanie fallocate (zamiast ftruncate(), gdzie jest dostępna). Jeśli nie, po prostu otwórz plik w trybie O_APPEND i zwiększ plik, zapisując kilka zer. To znacznie zmniejsza rozdrobnienie.

  2. Użyj "Ogromne strony", jeśli są dostępne - to znacznie zmniejsza obciążenie w przypadku dużych odwzorowań.

  3. pread()/pwrite()/pwritev()/preadv() z niezbyt małym rozmiarem bloku nie jest naprawdę powolny. Znacznie szybciej niż IO można faktycznie wykonać.

  4. Błędy IO podczas używania mmap() wygenerują tylko segfault zamiast EIO lub tak.

  5. Większość problemów z wydajnością SQLite WRITE koncentruje się na dobrym korzystaniu z transakcji (tzn. Należy debugować, gdy faktycznie wykonywany jest COMMIT).

+3

Użycie 'fallocate()' powoduje opóźnienie alokacji, wymuszenie szukania dysku i aktualizacje metadanych w celu natychmiastowego przydzielenia fizycznych bloków dla nowego regionu plików, zamiast zezwalania na alokację, gdy brudne strony zostaną później przepłukane. W rzeczywistości użycie 'fallocate()' może * pogorszyć * fragmentację, jeśli wiele plików jest rozszerzanych jednocześnie: skończysz z blokami przeplecionymi na dysku. Ogólnie rzecz biorąc, powinieneś używać tylko 'fallocate()', aby wstępnie przydzielić duży plik, którego rozmiar znasz z góry (np. Plik do skopiowania lub pobrania). –

Powiązane problemy