为什么将奇数大小的读取请求一分为二?

时间:2018-09-02 23:39:13

标签: winapi fread

我注意到,在Windows上,每当我发出一个奇数长度的无缓冲fread()请求时,它都会分为2个请求(通过procmon观察):

a)为我要求的长度1感到恐惧

b)最后一个字节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成功偏移量:0,长度:2,优先级:正常

ReadFile c:\ work \ cpptry \ Debug \ cpptry.exe成功偏移量:2,长度:2

Windows似乎无法向文件系统发出奇数大小的请求。 这是怎么回事?

1 个答案:

答案 0 :(得分:2)

这是实现工件。

MS CRT会保留所有FILE的缓冲,即使您告诉它不要这样做也是如此。而是将文件缓冲区设置为内部缓冲区,并保留两个字节的空间。这样可以保留一个代码路径而不是两个,并且简化了fgetcfputc中快速路径的实现。

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

有些人可能会被缓冲区的大小所困扰(准无缓冲时为2个字节),但是在_fread_nolock_s函数中我们可以找到优化方法 女巫试图绕过文件缓冲区直接将缓冲区大小的倍数直接读取到目标。

请参见CRT来源中的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);
}

以及VC 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;
}