C++中最快的输入方法是什么?

14

我正在参加各种编程奥林匹克竞赛,并试图提高时间效率。我正在寻找最快的获取输入的方法,使用gcc编译器而不需要任何外部库。

我以前使用过cin和cout,但发现scanf和printf更快。还有更快的方法吗?我并不太在意空间复杂度,更喜欢更好的时间效率。


2
你是在寻找实现时间还是运行时间?如果是运行时间,你可能会对我在这里的回答感兴趣:https://dev59.com/hmox5IYBdhLWcg3w95E9#8854366 - bames53
文件在比赛中很常见吧?光纤通道SAN驱动使它们真的非常快速 :) - Hans Passant
3个回答

23

流相比C-API函数总是慢的这个观点是一个普遍的误解,因为默认情况下,它们与C层同步。所以,是的,这是一种特性而不是错误。

如果不牺牲类型安全(和可读性,根据您的口味),您可能会通过使用以下方法在流中获得更好的性能:

std::ios_base::sync_with_stdio (false);

一个小指示器:
#include <cstdio>
#include <iostream>

template <typename Test> 
void test (Test t)
{
    const clock_t begin = clock();
    t();
    const clock_t end = clock();
    std::cout << (end-begin)/double(CLOCKS_PER_SEC) << " sec\n";
}

void std_io() {
    std::string line;
    unsigned dependency_var = 0;
    
    while (!feof (stdin)) {
        int c;
        line.clear();
        while (EOF != (c = fgetc(stdin)) && c!='\n')
            line.push_back (c);
        dependency_var += line.size();
    }
    
    std::cout << dependency_var << '\n';
}

void synced() {
    std::ios_base::sync_with_stdio (true);
    std::string line;
    unsigned dependency_var = 0;
    while (getline (std::cin, line)) {
        dependency_var += line.size();
    }
    std::cout << dependency_var << '\n';
}

void unsynced() {
    std::ios_base::sync_with_stdio (false);
    std::string line;
    unsigned dependency_var = 0;
    while (getline (std::cin, line)) {
        dependency_var += line.size();
    }
    std::cout << dependency_var << '\n';
}

void usage() { std::cout << "one of (synced|unsynced|stdio), pls\n"; }

int main (int argc, char *argv[]) {
    if (argc < 2) { usage(); return 1; }
    
    if (std::string(argv[1]) == "synced") test (synced);
    else if (std::string(argv[1]) == "unsynced") test (unsynced);
    else if (std::string(argv[1]) == "stdio") test (std_io);
    else { usage(); return 1; }

    return 0;
}

使用g++ -O3编译,并使用一个大文本文件:

cat testfile | ./a.out stdio
...
0.34 sec

cat testfile | ./a.out synced
...
1.31 sec

cat testfile | ./a.out unsynced
...
0.08 sec

这如何应用到你的情况取决于具体情况。修改这个玩具基准测试,添加更多测试,并比较诸如std::cin >> a >> b >> cscanf ("%d %d %d", &a, &b, &c);之类的内容。我保证,在优化的情况下(即不处于调试模式),性能差异将是微妙的。
如果这还不能满足您的需求,您可能会尝试其他方法,例如首先读取整个文件(可能会带来更好的性能),或者内存映射(这是一种非便携式的解决方案,但大型桌面有这些功能)。
更新
格式化输入:scanf vs.流
#include <cstdio>
#include <iostream>

template <typename Test> 
void test (Test t)
{
    const clock_t begin = clock();
    t();
    const clock_t end = clock();
    std::cout << (end-begin)/double(CLOCKS_PER_SEC) << " sec\n";
}

void scanf_() {
    char x,y,c;
    unsigned dependency_var = 0;
    
    while (!feof (stdin)) {
        scanf ("%c%c%c", &x, &y, &c);
        dependency_var += x + y + c;
    }
    
    std::cout << dependency_var << '\n';
}

void unsynced() {
    std::ios_base::sync_with_stdio (false);
    char x,y,c;
    unsigned dependency_var = 0;
    while (std::cin) {
        std::cin >> x >> y >> c;
        dependency_var += x + y + c; 
    }
    std::cout << dependency_var << '\n';
}

void usage() { std::cout << "one of (scanf|unsynced), pls\n"; }

int main (int argc, char *argv[]) {
    if (argc < 2) { usage(); return 1; }
    
    if (std::string(argv[1]) == "scanf") test (scanf_);
    else if (std::string(argv[1]) == "unsynced") test (unsynced);
    else { usage(); return 1; }

    return 0;
}

结果:

scanf: 0.63 sec
unsynced stream: 0.41 

3
流式API比C-API慢,但有原因(它要完成更多的工作)。流式API在读写器中整合了整个Locale内容,而这在C-API中是不存在的,这可能会很耗费资源(尽管用“C” Locale尝试减少,但仍然存在)。这里的评论假设我们使用格式化输入/输出而不是直接读写。 - Martin York
@LokiAstari:不一定。scanf始终需要解析格式字符串,而对于流来说,在运行时它不存在。 - Sebastian Mach
@LokiAstari:提供格式化输入的示例。在第一次阅读时忽略它(抱歉):C-io 还使用 locales(http://linux.die.net/man/3/setlocale)。 - Sebastian Mach
@SebastianMach 先生,我需要跟您交谈,我们能聊聊吗? - Suraj Jain

5

一般来说,缓冲输入是最快的。你越不经常刷新输入缓冲区,输入速度就会越快。关于这个话题的全面而且非常有启发性的讨论,请参见此问题。简而言之,使用大缓冲区大小的read()是最快的,因为它几乎直接在你的操作系统中对应相应的系统调用。


0

可能 scanf 比使用流略快一些。虽然流提供了很多类型安全性,并且不必在运行时解析格式字符串,但它通常具有不需要过多的内存分配的优点(这取决于您的编译器和运行时)。也就是说,除非性能是您唯一的终极目标并且您处于关键路径上,否则您应该真正倾向于更安全(更慢)的方法。

这里 Herb Sutter 写了一篇非常美味的文章

http://www.gotw.ca/publications/mill19.htm

这篇文章详细介绍了字符串格式化程序(如sscanf和lexical_cast)的性能,以及影响它们运行速度快慢的因素。这与C风格IO和C++风格之间的性能影响可能有些类似。格式化程序的主要区别在于类型安全性和内存分配数量。


请注意,默认情况下,流会与C-API同步。如果您禁用同步,则流通常会比C-API更快。 - Sebastian Mach

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