最近我遇到了微软CRTL实现的“有趣”问题。 tmpfile
将临时文件放在根目录中,并完全忽略临时文件目录。这对于没有root目录权限的用户(例如,在我们的群集上)存在问题。此外,使用_tempnam
将要求应用程序记住删除临时文件,如果没有大量的返工,它将无法执行。
因此,我咬了一下子弹并编写了所有IO例程(create_temp,read,write,seek,flush)的Win32版本,这些例程调用了适当的方法。我注意到的一件事是图书馆现在表现糟糕。
测试套件的结果:
CRTL: 4:30.05 elapsed
Win32: 11:18.06 elapsed
Stats measured in my routines:
Writes: 3129934 ( 44,642,745,008 bytes)
Reads: 935903 ( 8,183,423,744 bytes)
Seeks: 2205757 (2,043,782,657,968 bytes traveled)
Flushes: 92442
CRTL v.Win32方法示例:
int io_write(FILE_POINTER fp, size_t words, const void *buffer)
{
#if !defined(USE_WIN32_IO)
{
size_t words_written = 0;
/* read the data */
words_written = fwrite(buffer, sizeof(uint32_t), words, fp);
if (words_written != words)
{
return errno;
}
}
#else /* !defined(USE_WIN32_IO) */
{
DWORD bytesWritten;
if (!WriteFile(fp, buffer, words * sizeof(uint32_t), &bytesWritten, NULL)
|| (bytesWritten != words * sizeof(uint32_t)))
{
return GetLastError();
}
}
#endif /* USE_WIN32_IO */
return E_SUCCESS;
}
正如您所看到的,它们实际上是完全相同的,但性能(在发布模式下)却大相径庭。在WriteFile
和SetFilePointer
中花费的时间使fwrite
和fseeko
所花费的时间相形见绌,这似乎违反直觉。
想法?
更新:perfmon指出fflush
比FlushFileBuffers
便宜约10倍,而fwrite
比WriteFile
慢约1.1倍。最终结果是FlushFileBuffers
以与fflush
相同的方式使用,导致巨大的性能损失。从FILE_ATTRIBUTE_NORMAL
到FILE_FLAG_RANDOM_ACCESS
也没有变化。
答案 0 :(得分:2)
传统上,C运行时库函数缓冲数据并仅触发写操作(因此需要像fflush
这样的函数)。我认为WriteFile
不会缓冲写操作,因此每次调用WriteFile
时都会触发I / O操作,而使用fwrite
时,I / O会在缓冲区被触发时触发已达到一定的规模。
从测量结果可以看出,缓冲的I / O往往效率更高......
答案 1 :(得分:2)
我认为这可能是由于这个问题,在MSDN的FlushFileBuffers
页面上有描述:
由于磁盘缓存交互 在系统内, FlushFileBuffers函数可以 每次使用后效率低下 多次写入磁盘驱动器设备 写入正在单独执行。 如果应用程序正在执行 多次写入磁盘并且还需要 确保写入关键数据 持久性媒体,应用程序 应该使用无缓冲的I / O而不是 经常调用FlushFileBuffers。 要打开无缓冲I / O的文件, 用。调用CreateFile函数 FILE_FLAG_NO_BUFFERING和 FILE_FLAG_WRITE_THROUGH标志。这个 防止文件内容 缓存并刷新元数据 每次写入磁盘。更多 信息,请参阅CreateFile。
通常,FlushFileBuffers
是“昂贵的”操作,since it flushes everything in the write-back cache:
FlushFileBuffers():此函数将刷新回写缓存中的所有内容 不知道缓存的哪个部分属于您的文件。这可能需要很长时间, 取决于缓存大小和媒体的速度。有多必要?有 一个经过并写出脏页的线程,所以很可能不是这样 必要的。
我认为fflush
不会刷新整个回写缓存。在这种情况下,它的效率更高,但效率可能会导致潜在的数据丢失。 CRT的fflush
源代码确认了这一点,因为_commit
调用了FlushFileBuffers
:
/* lowio commit to ensure data is written to disk */
if (str->_flag & _IOCOMMIT) {
return (_commit(_fileno(str)) ? EOF : 0);
}
从_commit
:
if ( !FlushFileBuffers((HANDLE)_get_osfhandle(filedes)) ) {
retval = GetLastError();
}
答案 2 :(得分:2)
我可能会发疯,但是如果只使用tmpfile
来代替使用fopen(temporaryname, "wbTD+")
的{{1}}代替您生成自己的temporaryname
会不会更容易?
至少那时你不必担心重新实现<file.h>
。
答案 3 :(得分:1)
我还不清楚问题是什么。首先谈谈管理临时文件的生命周期,然后跳转到包装整个文件的i / o接口。您是否在询问如何管理临时文件而不会包含所有文件I / O的性能损失?或者您是否对CRT函数如何比它们构建在其上的WinAPI函数更快感兴趣?
C运行时函数和WinAPi函数之间的一些比较是苹果和橙子的比较。
C运行时函数缓冲库内存中的I / O.操作系统中还有另一层缓冲(和缓存)。
fflush
将库缓冲区中的数据刷新到OS。它可以直接进入磁盘,也可以进入OS缓冲区以便以后写入。 FlushFileBuffers
将来自OS缓冲区的数据传输到磁盘上,这通常比将数据库从库缓冲区移动到OS缓冲区要花费更长的时间。
未对齐的写入很昂贵。 OS缓冲区可以实现未对齐写入,但它们并不能真正加快进程。在将数据推送到OS之前,库缓冲区可能会接受多次写入,从而有效地减少了对磁盘的未对齐写入次数。
这也是可能的(虽然这只是一个猜测),库例程正在利用磁盘的重叠(异步)I / O,你的直接到WinAPI实现都是同步的。