为什么这个C代码比这个C++代码更快?获得文件中最大的一行

Ale*_*lex 29 c c++ performance lines count

我有一个程序的两个版本基本上做同样的事情,在文件中得到一行的最大长度,我有一个大约8千行的文件,我的C代码有点原始(当然!)比我在C++中的代码.C程序运行大约需要2秒钟,而C++程序运行需要10秒钟(我正在测试两个案例的同一文件).但为什么?我期待它花费相同的时间或更多但不会慢8秒!

我在C中的代码:

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>

#if _DEBUG
    #define DEBUG_PATH "../Debug/"
#else
    #define DEBUG_PATH ""
#endif

const char FILE_NAME[] = DEBUG_PATH "data.noun";

int main()
{   
    int sPos = 0;
    int maxCount = 0;
    int cPos = 0;
    int ch;
    FILE *in_file;              

    in_file = fopen(FILE_NAME, "r");
    if (in_file == NULL) 
    {
        printf("Cannot open %s\n", FILE_NAME);
        exit(8);
    }       

    while (1) 
    {
        ch = fgetc(in_file);
        if(ch == 0x0A || ch == EOF) // \n or \r or \r\n or end of file
        {           
            if ((cPos - sPos) > maxCount)
                maxCount = (cPos - sPos);

            if(ch == EOF)
                break;

            sPos = cPos;
        }
        else
            cPos++;
    }

    fclose(in_file);

    printf("Max line length: %i\n",  maxCount); 

    getch();
    return (0);
}
Run Code Online (Sandbox Code Playgroud)

我在C++中的代码:

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string>

using namespace std;

#ifdef _DEBUG
    #define FILE_PATH "../Debug/data.noun"
#else
    #define FILE_PATH "data.noun"
#endif

int main()
{
    string fileName = FILE_PATH;
    string s = "";
    ifstream file;
    int size = 0;

    file.open(fileName.c_str());
    if(!file)
    {
        printf("could not open file!");
        return 0;
    }

    while(getline(file, s) )
            size = (s.length() > size) ? s.length() : size;
    file.close();

    printf("biggest line in file: %i", size);   

    getchar();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ins 75

我的猜测是,您使用的编译器选项,编译器本身或文件系统存在问题.我刚刚编译了两个版本(进行了优化)并针对92,000行文本文件运行它们:

c++ version:  113 ms
c version:    179 ms
Run Code Online (Sandbox Code Playgroud)

我怀疑C++版本更快的原因是因为fgetc很可能更慢.fgetc确实使用缓冲的I/O,但它正在进行函数调用以检索每个字符.我之前测试过它并fgetc没有像在一次调用中调用读取整行那样快(例如,与之相比fgets).

  • 注意:`fgetc`需要根据POSIX*每次调用锁定流*.大多数系统,包括MSVC CRT,都遵循本指南.MSVC中的C++版本每次调用"getline"时都会锁定一次流,尽管任何标准都不要求这样做. (6认同)

bam*_*s53 29

因此,在一些评论中,我回应了人们的回答,问题很可能是你的C++版本进行了额外的复制,它将字符串复制到内存中.但我想测试一下.

首先,我实现了fgetc和getline版本并计时.我确认在调试模式下,getline版本较慢,大约130μs,而fgetc版本则为60μs.鉴于传统观念认为iostream比使用stdio要慢,这并不足为奇.然而,在过去,我的经验是,iostream从优化中获得了显着的提升.当我比较我的释放模式时间时确认了这一点:使用getline约为20μs,使用fgetc约为48μs.

使用带有iostreams的getline比fgetc更快的事实,至少在发布模式下,与复制所有数据必须比不复制它的速度慢的原因相反,所以我不确定所有优化能够避免的是什么,而且我并没有真正寻找任何解释,但了解被优化的东西会很有趣.编辑:当我用分析器查看程序时,如何比较性能并不明显,因为不同的方法看起来彼此如此不同

Anwyay我想看看我是否可以通过避免使用get()fstream对象上的方法进行复制来获得更快的版本,并且正好完成C版本正在做的事情.当我这样做时,我很惊讶地发现fstream::get()在调试和发布中使用比fgetc和getline方法都要慢得多; 调试时约为230μs,发布时为80μs.

为了缩小减速度,我继续前进并做了另一个版本,这次使用附加到fstream对象的stream_buf,以及snextc()方法.这个版本是迄今为止最快的; 调试时为25μs,发布时为6μs.

我猜测使fstream::get()方法变得如此慢的原因是它为每次调用构造了一个哨兵对象.虽然我没有对此进行测试get(),但除了这些岗哨对象外,我看不到除了从stream_buf获取下一个字符之外还有什么.

无论如何,这个故事的寓意是,如果你想要快速的io,你可能最好使用高级的iostream函数而不是stdio,并且真正快速地访问底层的stream_buf.编辑:实际上这个道德可能仅适用于MSVC,请参阅底部更新以获取来自不同工具链的结果.

以供参考:

我使用了VS2010和来自boost 1.47的计时器来计时.我构建了32位二进制文​​件(似乎需要boost chrono,因为它似乎无法找到该lib的64位版本).我没有调整编译选项,但它们可能不是完全标准的,因为我在一个临时与我保留的项目中这样做.

我测试过的文件是OeuvresComplètesdeFrédéricBastiat的1.1 MB 20,000行纯文本版本,来自Project Gutenberg的FrédéricBastiat的第1版,http://www.gutenberg.org/ebooks/35390

发布模式时间

fgetc time is: 48150 microseconds
snextc time is: 6019 microseconds
get time is: 79600 microseconds
getline time is: 19881 microseconds
Run Code Online (Sandbox Code Playgroud)

调试模式时间:

fgetc time is: 59593 microseconds
snextc time is: 24915 microseconds
get time is: 228643 microseconds
getline time is: 130807 microseconds
Run Code Online (Sandbox Code Playgroud)

这是我的fgetc()版本:

{
    auto begin = boost::chrono::high_resolution_clock::now();
    FILE *cin = fopen("D:/bames/automata/pg35390.txt","rb");
    assert(cin);
    unsigned maxLength = 0;
    unsigned i = 0;
    int ch;
    while(1) {
        ch = fgetc(cin);
        if(ch == 0x0A || ch == EOF) {
            maxLength = std::max(i,maxLength);
            i = 0;
            if(ch==EOF)
                break;
        } else {
            ++i;
        }
    }
    fclose(cin);
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "fgetc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

这是我的getline()版本:

{
    auto begin = boost::chrono::high_resolution_clock::now();
    std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
    unsigned maxLength = 0;
    std::string line;
    while(std::getline(fin,line)) {
        maxLength = std::max(line.size(),maxLength);
    }
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "getline time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

fstream::get()版本

{
    auto begin = boost::chrono::high_resolution_clock::now();
    std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
    unsigned maxLength = 0;
    unsigned i = 0;
    while(1) {
        int ch = fin.get();
        if(fin.good() && ch == 0x0A || fin.eof()) {
            maxLength = std::max(i,maxLength);
            i = 0;
            if(fin.eof())
                break;
        } else {
            ++i;
        }
    }
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "get time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

snextc()版本

{
    auto begin = boost::chrono::high_resolution_clock::now();
    std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
    std::filebuf &buf = *fin.rdbuf();
    unsigned maxLength = 0;
    unsigned i = 0;
    while(1) {
        int ch = buf.snextc();
        if(ch == 0x0A || ch == std::char_traits<char>::eof()) {
            maxLength = std::max(i,maxLength);
            i = 0;
            if(ch == std::char_traits<char>::eof())
                break;
        } else {
            ++i;
        }
    }
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "snextc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

更新:

我使用libc ++在OS X上使用clang(trunk)重新进行测试.基于iostream的实现的结果保持相对相同(优化打开); fstream::get()比慢std::getline()得多慢得多filebuf::snextc().但fgetc()相对于getline()实施的改进表现并变得更快.也许这是因为完成的复制getline()成为这个工具链的问题,而它不是MSVC?也许微软的CRT实现fgetc()是坏事还是什么?

无论如何,这里是时代(我使用了更大的文件,5.3 MB):

使用-Os

fgetc time is: 39004 microseconds
snextc time is: 19374 microseconds
get time is: 145233 microseconds
getline time is: 67316 microseconds
Run Code Online (Sandbox Code Playgroud)

使用-O0

fgetc time is: 44061 microseconds
snextc time is: 92894 microseconds
get time is: 184967 microseconds
getline time is: 209529 microseconds
Run Code Online (Sandbox Code Playgroud)

-02

fgetc time is: 39356 microseconds
snextc time is: 21324 microseconds
get time is: 149048 microseconds
getline time is: 63983 microseconds
Run Code Online (Sandbox Code Playgroud)

-O3

fgetc time is: 37527 microseconds
snextc time is: 22863 microseconds
get time is: 145176 microseconds
getline time is: 67899 microseconds
Run Code Online (Sandbox Code Playgroud)

  • +1用于测试并提供很好的示例.更多答案应该是这样的. (3认同)

dat*_*olf 14

C++版本不断分配和释放std :: string的实例.内存分配是一项昂贵的操作.除此之外,还执行构造函数/析构函数.

然而,C版本使用常量内存,这是必要的:读取单个字符,将行长度计数器设置为新值(如果更高),对于每个换行符就是这样.

  • C++版本几乎不会重新分配内存.每次迭代都会重用上一行中`s`的内存.因此,如果最长的行是N个字符长,则最多将有O(log N)重新分配.很可能OP没有启用优化. (18认同)
  • 我发现很难相信分配成本与实际磁盘I/O成本相比是如此昂贵. (13认同)
  • 在单线程应用程序中分配和释放小块内存并不昂贵.它不能为运行时添加8秒. (4认同)
  • ybungalobill是对的.声明只有两个字符串变量.每次`s`获得一个新值时,都不会重新分配内存 - 只有在现有缓冲区不够大的情况下才会重新分配.重新分配完成后,缓冲区会以某个常数因子(不是常数加法)增长 - 例如加倍.即使文件中出现小得多的行,此缓冲区也不会再次收缩.在实践中,如果你在一次运行中管理甚至10次重新分配,我会感到惊讶 - 这更像是8微秒而不是8秒.构造函数,析构函数和堆分配在这里是无关紧要的. (3认同)

das*_*ght 11

你不是在比较苹果和苹果.您的C程序不会将数据从FILE*缓冲区复制到程序的内存中.它还可以在原始文件上运行.

你的C++程序需要多次遍历每个字符串的长度 - 一次在流代码中知道何时终止它返回给你的字符串,一次在构造函数中std::string,一次在代码的调用中s.length().

您可以提高C程序的性能,例如使用getc_unlocked它是否可用.但最大的胜利来自于不必复制数据.

编辑:编辑以回应bames53的评论


for*_*ran 5

只有8.000行2秒?我不知道你的线条有多长,但很可能你做错了.

这个简单的Python程序几乎可以立即执行从Project Gutenberg下载的El Quijote(40006行,2.2MB):

import sys
print max(len(s) for s in sys.stdin)
Run Code Online (Sandbox Code Playgroud)

时机:

~/test$ time python maxlen.py < pg996.txt
76

real    0m0.034s
user    0m0.020s
sys     0m0.010s
Run Code Online (Sandbox Code Playgroud)

您可以通过缓冲输入而不是通过char读取char来改进C代码.

关于为什么C++比C慢,它应该与构建字符串对象然后调用length方法有关.在C中你只是在计算你的角色.

  • 在我相当新的计算机上,以及1000万行,C程序在大约3秒内运行 - 而C++程序在不到一秒的时间内运行.(不过,在Linux上是GCC.)所以我同意这里似乎有"非常错误的东西". (5认同)
  • 输入已经被stdio缓冲了,所以我认为通过额外的缓冲你不会获得太多收益.但我同意所陈述的时间似乎很慢. (3认同)

Mar*_*k B 5

我尝试针对40K行的C++源代码编译和运行你的程序,它们都在大约25ms左右完成.我只能得出结论,你的输入文件有长的行,每行可能有10K-100K字符.在这种情况下,C版本的长行长度没有任何负面影响,而C++版本必须不断增加字符串的大小并将旧数据复制到新缓冲区.如果它必须增加足够的次数,可以解释过度的性能差异.

这里的关键是这两个程序不会做同样的事情,所以你无法真正比​​较他们的结果.如果您能够提供输入文件,我们可能会提供其他详细信息.

您可以在C++中更快地使用tellgignore完成此操作.