我注意到在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无法向文件系统发出奇怪的请求。那是怎么回事?
这是实现工件。
MS CRT保持所有FILE
s缓冲,即使你告诉它不要这样做。而是将文件缓冲区设置为内部缓冲区,空格为两个字节。这允许保留一个代码路径而不是两个代码路径,并简化了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;
}