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