我注意到,在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似乎无法向文件系统发出奇数大小的请求。 这是怎么回事?
答案 0 :(得分:2)
这是实现工件。
MS CRT会保留所有FILE
的缓冲,即使您告诉它不要这样做也是如此。而是将文件缓冲区设置为内部缓冲区,并保留两个字节的空间。这样可以保留一个代码路径而不是两个,并且简化了fgetc
和fputc
中快速路径的实现。
#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;
}