2013-03-28 6 views
20

Мы экспериментируем с изменением встроенной системы баз данных SQLite , чтобы использовать mmap() вместо обычных вызовов read() и write() для доступа к базе данных файл на диске. Использование одного большого отображения для всего файла . Предположим, что файл достаточно мал, что у нас нет проблем найти место для этого в виртуальной памяти.Как переносить файл, доступный с помощью mmap()

Пока все хорошо. Во многих случаях использование mmap() кажется немного быстрее чем read() и write(). А в некоторых случаях гораздо быстрее.

Изменение размера отображения, чтобы зафиксировать транзакцию записи, которую расширяет файл базы данных, кажется, является проблемой. Для того, чтобы расширить файл базы данных, код может сделать что-то вроде этого:

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

затем скопировать новые данные в конце нового отображения памяти. Однако munmap/mmap нежелательно, так как это означает, что в следующий раз при каждой странице файла базы данных происходит обращение к второстепенной ошибке страницы и системе необходимо выполнить поиск в кеше страницы ОС для правильного кадра до , связанного с виртуальным адрес памяти. Другими словами, он замедляет вниз по последующим считываниям базы данных.

В Linux мы можем использовать нестандартный системный вызов mremap() вместо munmap()/mmap(), чтобы изменить размер отображения. Это, по-видимому, позволяет избежать ошибок .

ВОПРОС: Как это должно быть рассмотрено в других системах, таких как OSX, , у которых нет mremap()?


У нас есть две идеи в настоящее время. И вопрос относительно каждого:

1) Создавайте сопоставления, большие, чем файл базы данных. Затем, расширяя файл базы данных, просто вызовите ftruncate(), чтобы расширить файл на диске и продолжить использование того же сопоставления.

Это было бы идеально и, кажется, работает на практике. Тем не менее, мы обеспокоены этим предупреждением на странице человека:

«Влияние изменения размера основного файла в отображения на страницах, которые соответствуют добавлены или удалены регионов файл не определен «.

ВОПРОС: Это что-то, о чем мы должны беспокоиться? Или анахронизм в этот момент?

2) При расширении файла базы данных, использовать первый аргумент MMAP() запросить отображение, соответствующее новых страницы файла базы данных , расположенный непосредственно после текущего отображения в виртуальной памяти . Эффективное расширение исходного отображения. Если система не может выполнить запрос на размещение нового картографирования сразу после , сначала вернитесь к munmap/mmap.

На практике мы обнаружили, что OSX очень хорош в размещении картинок таким образом, поэтому этот трюк работает там.

ВОПРОС: если система не выделит вторую отображение сразу вслед за первым в виртуальной памяти, это то безопасно, в конечном счете Unmap их обоих, используя один большой вызов munmap()?

+0

Я делаю то же самое. В Solaris 10 'munmap' выполняет синхронный' msync', если я правильно помню. Фактически 'msync' всегда был синхронным на Solaris 10, даже когда был задан параметр' MS_ASYNC'. Это были пара последних гвоздей в гробу Солярис. –

+0

Я не думаю, что №1 выполнимо. Создание сопоставления, большего, чем файл, приводит к тому, что конец файла недоступен (хотя он может быть «отображен»), а 'ftruncate()' не будет обновлять сопоставление. – twalberg

ответ

3
  1. Я думаю, что # 2 - лучшее в настоящее время решение. В дополнение к этому на 64-битных системах вы можете явно создать свое сопоставление по адресу, который ОС никогда не выберет для сопоставления (например, 0x6000 0000 0000 0000 в Linux), чтобы избежать того, что ОС не может сразу разместить новое сопоставление после первого один.

  2. Всегда можно отключить mutiple mappinsg с помощью одного вызова munmap. Вы даже можете отформатировать часть карты, если хотите это сделать.

+6

Большинство 64-битных реализаций реального мира (то есть фактического процессора) не поддерживают 64-разрядные адресные пространства. например, ни один из существующих amd64 cpus не поддерживает адрес 0x6000 0000 0000 0000. –

4
  1. Использование fallocate() вместо ftruncate(), где доступны. Если нет, просто откройте файл в режиме O_APPEND и увеличьте файл, записав некоторое количество нулей. Это значительно сокращает фрагментацию.

  2. Используйте «Огромные страницы», если они доступны - это значительно уменьшает накладные расходы при больших сопоставлениях.

  3. pread()/pwrite()/pwritev()/preadv() с небольшим размером блока на самом деле не очень медленный. На самом деле гораздо быстрее, чем IO.

  4. Ошибки ввода-вывода при использовании mmap() будут генерировать только segfault вместо EIO или так.

  5. Большая часть проблем с производительностью SQLite WRITE сконцентрирована в хорошем транзакционном использовании (то есть вы должны отлаживать, когда действительно выполняется COMMIT).

+3

Использование 'fallocate()' defesats delayed allocation, принудительное восстановление диска и обновление метаданных для немедленного выделения физических блоков для новой файловой области, а не разрешения выделения, когда загрязненные страницы будут затем очищены. Фактически, использование 'fallocate()' может * ухудшить * фрагментацию, если несколько файлов расширяются одновременно: вы получите свои блоки, чередующиеся на диске. Как правило, вы должны использовать 'fallocate()' для предварительного выделения большого файла, размер которого вы знаете заранее (например, файл, который нужно скопировать или загрузить). –

Смежные вопросы