Почему фред-запросы нечетного размера разделены на две части?

Я заметил, что в Windows каждый раз, когда я запускаю небуферизованный запрос fread() с нечетной длиной, он разбивается на 2 запроса (как было отмечено в procmon):

а) фред для моей запрошенной длины-1

б) 2-байтовый фред за последний байт

Это приводит к очевидным накладным расходам, таким как 2 запроса ядра вместо одного и т. Д.

Пример кода работал на Windows 10:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {
    FILE* pFile;
    char* buffer;

    pFile = fopen(argv[0], "rb");

    setbuf(pFile, nullptr);

    size_t len = 3;
    buffer = (char*)malloc(sizeof(char)*len);

    if (len != fread(buffer, 1, len, pFile)) { fputs("Reading error", stderr); exit(3); }

    free(buffer);
    fclose(pFile);
    return 0;
}

Это приводит к следующим вызовам procmon, о которых сообщают:

ReadFile c:\work\cpptry\Debug\cpptry.exe Смещение SUCCESS: 0, длина: 2, приоритет: обычный

ReadFile c:\work\cpptry\Debug\cpptry.exe Смещение УСПЕХА: 2, длина: 2

Кажется, что Windows не в состоянии выдавать запросы странного размера в файловую систему. Что с этим?

1 ответ

Это артефакт реализации.

MS CRT держит все FILEs буферизируется, даже если вы скажете, чтобы это не делалось. Вместо этого файловый буфер установлен на внутренний буфер с пространством для двух байтов. Это позволяет сохранить один путь кода вместо двух и упрощает реализацию быстрого пути в fgetc а также fputc,

#define fgetc(_stream) (--(_stream)->_cnt >= 0 ? 0xff & *(_stream)->_ptr++ : _filbuf(_stream))

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

Увидеть fread.c в источниках ЭЛТ:

/* calc chars to read -- (count/streambufsize) * streambufsize */
nbytes = (unsigned)(count - count % streambufsize);
...
nread = _read_nolock(_fileno(stream), data, nbytes);

Поскольку размер буфера файла равен 2, четное число байтов считывается непосредственно в место назначения, и в конечном итоге один байт проходит через буфер файла. Иногда в буфере могут быть некоторые байты, которые необходимо передать в место назначения, прежде чем можно будет оптимизировать чтение.

Бонус: размер буфера всегда должен быть кратным 2.

Увидеть setvbuf.c:

/*
 * force size to be even by masking down to the nearest multiple
 * of 2
 */
size &= (size_t)~1;
...
/*
 * CASE 1: No Buffering.
 */
if (type & _IONBF) {
        stream->_flag |= _IONBF;
        buffer = (char *)&(stream->_charbuf);
        size = 2;
}

Выше приведены фрагменты кода из VC 2013 CRT.

Для сравнения сниппеты от Universal CRT 10.0.17134

read.cpp

unsigned const bytes_to_read = stream_buffer_size != 0
    ? static_cast<unsigned>(maximum_bytes_to_read - maximum_bytes_to_read % stream_buffer_size)
    : maximum_bytes_to_read;
...
int const bytes_read = _read_nolock(_fileno(stream.public_stream()), data, bytes_to_read);

setvbuf.cpp

// Force the buffer size to be even by masking the low order bit:
size_t const usable_buffer_size = buffer_size_in_bytes & ~static_cast<size_t>(1);
...
// Case 1:  No buffering:
if (type & _IONBF)
{
    return set_buffer(stream, reinterpret_cast<char*>(&stream->_charbuf), 2, _IOBUFFER_NONE);
}

И отрывки из ВК 6.0 (1998)

read.c

/* calc chars to read -- (count/bufsize) * bufsize */
nbytes = ( bufsize ? (count - count % bufsize) : count );
nread = _read(_fileno(stream), data, nbytes);

setvbuf.c

/*
 * force size to be even by masking down to the nearest multiple
 * of 2
 */
size &= (size_t)~1;
...
/*
 * CASE 1: No Buffering.
 */
if (type & _IONBF) {
    stream->_flag |= _IONBF;
    buffer = (char *)&(stream->_charbuf);
    size = 2;
}
Другие вопросы по тегам