从stdin c++中读取数百万个整数的最快方法

Fastest way to read millions of integers from stdin C++?

本文关键字:整数 方法 百万个 数百万 stdin c++ 读取      更新时间:2023-10-16

我正在做一个排序项目,我遇到了一个主要瓶颈,那就是读取数据。我的程序使用cinstd::ios::sync_with_stdio(false);对从stdin读取的100,000,000个整数进行排序需要大约20秒,但结果是其中10秒是在读取数据进行排序。我们确实知道要读入多少个整数(计数在我们需要排序的文件的顶部)。

我怎样才能使它更快?我知道这是可能的,因为上学期的一个学生能够在3秒多一点的时间内完成计数排序(这基本上是纯粹的阅读时间)。

程序只是输入一个文件的内容,其中包含由换行符分隔的整数,如$ ./program < numstosort.txt

感谢

相关代码如下:

    std::ios::sync_with_stdio(false);
    int max;
    cin >> max;
    short num;
    short* a = new short[max];
    int n = 0;
    while(cin >> num) { 
        a[n] = num;
        n++;
    }

这将使您的数据尽可能快地进入内存,假设Linux/POSIX运行在普通硬件上。请注意,由于显然不允许使用编译器优化,因此c++ IO不会是读取数据的最快方式。正如其他人注意到的那样,没有优化的c++代码将无法运行得尽可能快。

如果重定向文件已经作为stdin/STDIN_FILENO打开,则使用低级系统调用/c风格IO。这将不需要优化,因为它将尽可能快地运行:

struct stat sb;
int rc = ::fstat( STDIN_FILENO, &sb );
// use C-style calloc() to get memory that's been
// set to zero as calloc() is often optimized to be
// faster than a new followed by a memset().
char *data = (char *)::calloc( 1, sb.st_size + 1 );
size_t totalRead = 0UL;
while ( totalRead  < sb.st_size )
{
    ssize_t bytesRead = ::read( STDIN_FILENO,
        data + totalRead, sb.st_size - totalRead );
    if ( bytesRead <= 0 )
    {
        break;
    }
    totalRead += bytesRead;
}
// data is now in memory - start processing it

该代码将把数据作为一个长c风格字符串读入内存。缺乏编译器优化一点也不重要,因为它几乎都是裸机系统调用。

使用fstat()获取文件大小允许一次分配所需的所有内存-不需要realloc()或复制数据。

您需要添加一些错误检查,并且代码的更健壮的版本将检查确保从fstat()返回的数据实际上是具有实际大小的常规文件,而不是"无用地使用cat"例如cat filename | YourProgram,因为在这种情况下,fstat()调用不会返回有用的文件大小。您需要在调用之后检查struct statsb.st_mode字段,看看stdin流到底是什么:

::fstat( STDIN_FILENO, &sb );
...
if ( S_ISREG( sb.st_mode ) )
{
    // regular file...
}

(对于真正的高性能系统,确保正在读取数据的内存页实际上映射到进程地址空间中是很重要的。如果数据到达的速度比内核内存管理系统为数据转储到的页面创建虚拟到物理映射的速度快,那么性能真的会停滞。

为了尽可能快地处理一个大文件,你会想要多线程,一个线程读取数据并提供一个或多个数据处理线程,这样你就可以在读取数据之前开始处理数据。

编辑:解析数据

同样,防止编译器优化可能会使c++操作的开销比C风格的处理慢。基于这个假设,简单的可能会运行得更快。

在未优化的二进制文件中,这可能会更快地工作,假设数据是c风格的字符串,如上所述:

char *next;
long count = ::strtol( data, &next, 0 );
long *values = new long[ count ];
for ( long ii = 0; ii < count; ii++ )
{
    values[ ii ] = ::strtol( next, &next, 0 );
}

这也是非常脆弱的。它依赖于strtol()跳过前导空格,这意味着如果数值之间除了空格之外还有其他内容,它将失败。它还依赖于初始值计数是否正确。如果这不是真的,代码就会失败。因为它可以在检查错误之前替换next的值,如果它因为坏数据而偏离轨道,它将无可救药地丢失。

但是它应该在不允许编译器优化的情况下尽可能快。

这就是不允许编译器优化的疯狂。您可以编写简单、健壮的c++代码来完成所有的处理,使用一个好的优化编译器,并且可能运行得几乎和我发布的代码一样快——它没有错误检查,如果输入意外的数据,将会以意想不到的方式失败。

如果您使用SolidState硬盘驱动器,则可以使其更快。如果你想问一些关于代码性能的问题,你需要首先发布你是如何做事情的。

您可以通过将数据读入缓冲区,然后将缓冲区中的文本转换为内部表示来加快程序的速度。

这背后的想法是所有的流设备都喜欢保持流。启动和停止流浪费时间。块读取在一个事务中传输大量数据。

虽然cin是缓冲的,但通过使用cin.read和缓冲区,可以使缓冲区比cin使用的大得多。

如果数据具有固定宽度的字段,则有机会加快输入和转换过程。

编辑1:示例

const unsigned int BUFFER_SIZE = 65536;
char               text_buffer[BUFFER_SIZE];
//...
cin.read(text_buffer, BUFFER_SIZE);
//...
int value1;
int arguments_scanned = snscanf(&text_buffer, REMAINING_BUFFER_SIZE, 
                                "%d", &value1);

棘手的部分是处理数字的文本在缓冲区末尾被截断的情况。

您可以运行这个小测试,与您的测试进行比较,有没有注释行?

#include <iostream>
#include <cstdlib>
int main()
{
    std::ios::sync_with_stdio(false);
    char buffer[20] {0,};
    int t = 0;
    while( std::cin.get(buffer, 20) )
    {
 //       t = std::atoi(buffer);
        std::cin.ignore(1);
    }
    return 0;
}

read test:

#include <iostream>
#include <cstdlib>
int main()
{
    std::ios::sync_with_stdio(false);
    char buffer[1024*1024];

    while( std::cin.read(buffer, 1024*1024) )
    {
    }

    return 0;
}