типы буферов - PullRequest
       10

типы буферов

4 голосов
/ 06 июля 2010

Недавно интервьюер спросил меня о типах буферов. Какие типы буферов есть? На самом деле этот вопрос возник, когда я сказал, что буду записывать все системные вызовы в файл журнала для мониторинга системы. Он сказал, что будет медленно записывать каждый вызов файла. Как это предотвратить. Я сказал, что буду использовать буфер, и он спросил меня, какой тип буфера? Может кто-нибудь объяснить мне типы буферов, пожалуйста.

Ответы [ 5 ]

6 голосов
/ 06 июля 2010

В C под UNIX (и, возможно, также в других операционных системах) обычно есть два буфера, по крайней мере, в данном сценарии.

Первый существует в библиотеках времени выполнения C, в которых записываемая информация буферизуется перед доставкой в ​​ОС.

Второй находится в самой ОС, где информация буферизуется до физической записи на базовый носитель.

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

Это было достигнуто с помощью последовательности:

fflush (fh); fsync (fileno (fh));

Первый из них фактически гарантировал, что информация была передана из буферов времени выполнения C в операционную систему, а второй - что она была записана на диск. Помните, что это дорогостоящая операция, и ее следует выполнять только в том случае, если вам абсолютно необходима немедленно записанная информация (мы сделали это только на уровне ведения журнала SUPER_ENORMOUS_IMPORTANT).

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

Но, если вы не сделаете это без каких-либо вызовов функций, я не смогу увидеть, что это будет намного быстрее, чем то, что уже дает вам буферизация fprint.


После пояснения в комментариях, что этот вопрос на самом деле касается буферизации внутри ядра:

По сути, вы хотите, чтобы это было максимально быстро, эффективно и работоспособно (не подвержено сбоям или нехватке ресурсов).

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

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

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|s|t|r|i|n|g| |t|o| |w|r|i|t|e|.| | | | | | |T|h|i|s| |i|s| |t|h|e| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                                 ^           ^
                                 |           |
                   Buffer next --+           +-- Buffer start

тогда вам нужно будет написать "This is the ", а затем "string to write.".

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

Только если вы собираетесь переполнить буфер, вы начинаете делать хитрые вещи.

Вы можете выбрать один из двух подходов:

  • Либо очистите буфер в том виде, в котором он стоит, установите указатель next обратно на начало обработки нового сообщения; или
  • Добавьте часть сообщения, чтобы заполнить буфер, затем очистите его и установите указатель next в начало для обработки остальной части сообщения.

Вероятно, я бы выбрал второе, учитывая, что вам все равно придется учитывать сообщения, которые слишком велики для буфера.

То, о чем я говорю, примерно так:

initBuffer:
    create buffer of size 10240 bytes.
    set bufferEnd to end of buffer + 1
    set bufferPointer to start of buffer
    return

addToBuffer (size, message):
    while size != 0:
        xfersz = minimum (size, bufferEnd - bufferPointer)
        copy xfersz bytes from message to bufferPointer
        message = message + xfersz
        bufferPointer = bufferPointer + xfersz
        size = size - xfersz
        if bufferPointer == bufferEnd:
            write buffer to underlying media
            set bufferPointer to start of buffer
        endif
    endwhile

Это в основном эффективно обрабатывает сообщения любого размера за счет уменьшения количества физических записей. Конечно, будет оптимизация - возможно, что сообщение могло быть скопировано в пространство ядра, поэтому нет смысла копировать его в буфер, если вы все равно собираетесь его записать. Вы также можете записать информацию из копии ядра непосредственно на базовый носитель и передать только последний бит в буфер (так как вы должны сохранить его).

Кроме того, вы, вероятно, захотите сбросить неполный буфер на базовый носитель, если в течение некоторого времени ничего не записывалось.Это уменьшит вероятность отсутствия информации о вероятности сбоя самого ядра.

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

2 голосов
/ 06 июля 2010

Есть также кольцевые буферы , которые имеют ограниченные требования к пространству и, вероятно, лучше всего известны в Unix dmesg.

0 голосов
/ 06 июля 2010

Исправьте меня, если я ошибаюсь, но не буду использовать файл mmap 'd для журнала, чтобы избежать как издержек небольших системных вызовов write, так и возможности потери данных, если приложение (но не ОС) ) разбился? Мне кажется, это идеальный баланс между производительностью и надежностью.

0 голосов
/ 06 июля 2010

В целом, существуют буферы «первым пришел-первым обслужен» ( FIFO ), также известные как очереди;и есть буферы «Последний * -В-первом-выходе» ( LIFO ), также известные как стеки.

Для реализации FIFO существуют кольцевые буферы ,которые обычно используются, когда был выделен байтовый массив фиксированного размера.Например, драйвер клавиатуры или устройства последовательного ввода-вывода может использовать этот метод.Это обычный тип буфера, используемый, когда невозможно динамически распределить память (например, в драйвере, который требуется для работы подсистемы виртуальной памяти (ВМ) ОС).

Где динамическийпамять доступна, FIFO может быть реализован многими способами, особенно с помощью структур данных, полученных из связанного списка.

Кроме того, для реализации буфера FIFO могут использоваться биномиальные кучи, реализующие очереди приоритетов .1015 *

Частным случаем ни буфера FIFO, ни LIFO не являются буферы повторной сборки сегмента TCP.Они могут содержать сегменты, полученные вне очереди («из будущего»), которые удерживаются в ожидании получения промежуточных сегментов, которые еще не получены.

* Моя аббревиатура лучше, но большинство будет называть LIFO " Последний In, First Out ", а не Последний .

0 голосов
/ 06 июля 2010

Что мне приходит в голову, это временные буферы и размерные буферы. Таким образом, вы можете либо просто записать все, что находится в буфере, в файл один раз каждые x секунд / минут / часов, либо что угодно. В качестве альтернативы вы можете подождать, пока не появятся x записей журнала или x байтов данных журнала, и записать их все сразу. Это один из способов, которым log4net и log4J делают это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...