Win32 IO性能问题

时间:2009-12-22 16:40:43

标签: c performance winapi file-io

最近我遇到了微软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;
}

正如您所看到的,它们实际上是完全相同的,但性能(在发布模式下)却大相径庭。在WriteFileSetFilePointer中花费的时间使fwritefseeko所花费的时间相形见绌,这似乎违反直觉。

想法?

更新:perfmon指出fflushFlushFileBuffers便宜约10倍,而fwriteWriteFile慢约1.1倍。最终结果是FlushFileBuffers以与fflush相同的方式使用,导致巨大的性能损失。从FILE_ATTRIBUTE_NORMALFILE_FLAG_RANDOM_ACCESS也没有变化。

4 个答案:

答案 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实现都是同步的。