为什么这个 C++ 程序在 Windows 上比 Linux 上慢?

7

请考虑以下程序:

#define _FILE_OFFSET_BITS 64   // Allow large files.
#define REVISION "POSIX Revision #9"

#include <iostream>
#include <cstdio>
#include <ctime>

const int block_size = 1024 * 1024;
const char block[block_size] = {};

int main()
{
    std::cout << REVISION << std::endl;  

    std::time_t t0 = time(NULL);

    std::cout << "Open: 'BigFile.bin'" << std::endl;
    FILE * file;
    file = fopen("BigFile.bin", "wb");
    if (file != NULL)
    {
        std::cout << "Opened. Writing..." << std::endl;
        for (int n=0; n<4096; n++)
        {
            size_t written = fwrite(block, 1, block_size, file);
            if (written != block_size)
            {
                std::cout << "Write error." << std::endl;
                return 1;
            }
        }
        fclose(file);
        std::cout << "Success." << std::endl;

        time_t t1 = time(NULL);
        if (t0 == ((time_t)-1) || t1 == ((time_t)-1))
        {
            std::cout << "Clock error." << std::endl;
            return 2;
        }

        double ticks = (double)(t1 - t0);
        std::cout << "Seconds: " << ticks << std::endl;

        file = fopen("BigFile.log", "w");
        fprintf(file, REVISION);
        fprintf(file, "   Seconds: %f\n", ticks);
        fclose(file);

        return 0;
    }

    std::cout << "Something went wrong." << std::endl;
    return 1;
}

这段代码的作用是将4GB的零填充写入磁盘文件,并计算所需时间。

在Linux下,平均需要148秒。在同一台PC上运行Windows,则平均需要247秒。

我到底做错了什么?!

这段代码在Linux编译器GCC和Windows编译器Visual Studio下编译,但我无法想象编译器对纯I/O基准测试会有任何可测量的影响。在所有情况下使用的文件系统都是NTFS。

我只是不明白为什么存在如此巨大的性能差异。我不知道为什么Windows运行得如此缓慢。我应该如何强制Windows以显然可行的最大速度运行?

(上述数字是基于旧的Dell笔记本电脑上的OpenSUSE 13.1 32位和Windows XP 32位。但我在办公室里的几台PC上观察到了类似的速度差异,运行不同版本的Windows。)

编辑:可执行文件和它所写的文件都驻留在格式为NTFS且几乎为空的外部USB硬盘上。碎片几乎肯定不是问题。可能是某种驱动程序问题,但我看到了在运行不同版本的Windows的其他几个系统上出现了相同的性能差异。没有安装任何杀毒软件。

只是为了好玩,我尝试直接使用Win32 API。(显然,这仅适用于Windows。)时间变得有些不稳定,但仍然在之前的几个百分点之内。除非我指定FILE_FLAG_WRITE_THROUGH;否则它会变得明显更慢。一些其他标志使其变慢,但我找不到可以加速它的标志...


2
请问您使用的是哪种文件系统?我猜可能是ext4和NTFS,但最好确认一下。 - cadaniluk
1
@MathematicalOrchid 为了好玩,为什么不尝试在Windows上使用gcc?最重要的是,fwrite仍然取决于编译器。 - PaulMcKenzie
1
也许xp正在运行而没有适当的驱动程序,因此传输到磁盘的速度较慢。或者是防病毒软件干扰了。或者Linux在写入之前在RAM中进行缓冲,而Windows则首先进行刷新...等等等等...无论如何:任何不来自石器时代的磁盘,连续写入速度低于70 Mb/s都很慢。 - Marged
1
我认为“同一台电脑”指的是双重启动,而不是相同的机器。假设使用硬盘(而非固态硬盘),物理分区在驱动器上的位置会导致非常大的性能差异。早期的物理位置比后期的物理位置要快得多。 - JSF
1
获得良好的磁盘写入速度在很大程度上取决于文件的确切位置(外部轨道最佳),卷的碎片化(导致磁盘寻道)以及文件系统缓存可用的RAM数量。这在“旧戴尔笔记本电脑”上显然是不够的。仅文件位置就足以解释速度差异。在XP上,磁盘碎片整理是一项手动维护任务。 - Hans Passant
显示剩余20条评论
3个回答

3
你需要将文件内容同步到磁盘,否则你只是测量操作系统执行缓存的程度。在关闭文件之前调用fsync。如果不这样做,大部分执行时间很可能都花费在等待缓存被刷新以便新数据可以存储在其中,但肯定有一部分你写入的数据在你关闭文件时还没有被写出到磁盘上。因此,执行时间的差异可能是由于Linux在运行过程中缓存更多的写操作,直到它耗尽可用的缓存空间。相比之下,如果在关闭文件之前调用fsync,所有写入的数据应该在你进行时间测量之前刷新到磁盘上。我怀疑如果你添加一个fsync调用,两个系统的执行时间不会有太大差异。

0

你的测试不是衡量性能的好方法,因为不同操作系统和库中的不同优化可能会产生巨大的差异(编译器本身不必产生很大的差异)。

首先,我们可以考虑 fwrite(或任何操作 FILE* 的函数)是在操作系统层面之上的一个库层。有可能存在不同的缓冲策略,这些策略会对性能产生影响。例如,实现 fwrite 的一种聪明的方式是刷新缓冲区,然后直接将数据块发送到操作系统,而不是经过缓冲层。这可能会在下一步产生巨大的优势。

其次,我们有可以处理写入的操作系统/内核。一种聪明的优化方法是通过别名复制页面,然后在其中一个别名更改时使用写时复制。Linux已经(几乎)在为进程分配内存(包括BSS部分,其中数组位于其中)时执行此操作-它只将页面标记为零,并且可以为所有这些页面保留单个这样的页面,然后在某人更改零页面时创建新页面。再次执行此技巧意味着内核可以仅在磁盘缓冲区中对别名进行页面。这意味着当写入这些零块时,内核不会因为只占用4KiB的实际内存(除了页表)而在磁盘缓存上运行低。如果数据块中有实际数据,则也可以使用此策略。

这意味着写入可以非常快速地完成,甚至在数据实际上不需要传输到磁盘(在fwrite完成之前),即使数据甚至不必从内存中的一个位置复制到另一个位置。

因此,您使用不同的库和不同的操作系统,它们在不同的时间执行不同的任务并不奇怪。


0

对于全为零的页面,有特殊的优化措施。在写入之前,您应该填充页面以避免此情况。


好的,所以我尝试使用随机数据填充数组。(这一步不是计时的一部分。)在 Windows 上写入文件所需的时间几乎完全相同 [我没有检查 Linux]。 - MathematicalOrchid
1
我非常确定仅仅写入0并不足以在文件中创建稀疏区域。您需要使用特定于操作系统的调用/ioctl,或使用ftruncateseek超出文件末尾。 - davmac
@MathematicalOrchid:问题出在Linux的测量上,你需要重新做一下。(或者至少这是理论上的。) - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接