为什么奇数大小的fread请求分为两部分?

问题描述 投票:1回答:1

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

a)为我要求的长度1而言

b)最后一个字节的2字节fread

这有一个明显的性能开销,如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 Offset:0,Length:2,Priority:Normal

ReadFile c:\ work \ cpptry \ Debug \ cpptry.exe SUCCESS Offset:2,Length:2

好像Windows无法向文件系统发出奇怪的请求。那是怎么回事?

winapi fread
1个回答
2
投票

这是实现工件。

MS CRT保持所有FILEs缓冲,即使你告诉它不要这样做。而是将文件缓冲区设置为内部缓冲区,空格为两个字节。这允许保留一个代码路径而不是两个代码路径,并简化了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;
}
© www.soinside.com 2019 - 2024. All rights reserved.