c,stdin / out中的快速I / O.

时间:2017-04-23 15:34:14

标签: c performance optimization stdout stdin

this link指定的编码竞赛中,您需要在stdin上阅读大量数据,进行一些计算并在stdout上提供大量数据。< / p>

在我的基准测试中,尽管我尽可能地尝试优化它,但几乎只有i / o才需要时间。

你输入的是一个字符串(1 <= len <= 100'000)和一对int的q行,其中q也是1 <= q <= 100'000

我在100倍大的数据集上对我的代码进行了基准测试(len = 10M,q = 10M),这就是结果:

 Activity            time      accumulated

 Read text:          0.004     0.004
 Read numbers:       0.146     0.150
 Parse numbers:      0.200     0.350
 Calc answers:       0.001     0.351
 Format output:      0.037     0.388
 Print output:       0.143     0.531

通过实现我自己的格式化和内联数字解析,我设法在使用printfscanf时将时间减少到1/3。

然而,当我将我的解决方案上传到比赛网页时,我的解决方案需要1.88秒(我认为这是超过22个数据集的总时间)。当我看到高分时,有几个实现(在c ++中)在0.05秒内完成,比我快了近40倍!怎么可能?

我想我可以通过使用2个线程来加速它,然后我可以开始计算并写入stdout,同时仍然从stdin读取。但是,在我的大型数据集的理论上最佳情况下,这将减少min(0.150, 0.143)的时间。我仍然没有接近高分......

在下图中,您可以看到消耗时间的统计信息。

Statistics of the consumed time

该程序由网站编译,并附带以下选项:

gcc -g -O2 -std=gnu99 -static my_file.c -lm

并且像这样定时:

time ./a.out < sample.in > sample.out

我的代码如下所示:

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

#define MAX_LEN (100000 + 1)
#define ROW_LEN (6 + 1)
#define DOUBLE_ROW_LEN (2*ROW_LEN)

int main(int argc, char *argv[])
{
    int ret = 1;

    // Set custom buffers for stdin and out
    char stdout_buf[16384];
    setvbuf(stdout, stdout_buf, _IOFBF, 16384);
    char stdin_buf[16384];
    setvbuf(stdin, stdin_buf, _IOFBF, 16384);

    // Read stdin to buffer
    char *buf = malloc(MAX_LEN);
    if (!buf) {
        printf("Failed to allocate buffer");
        return 1;
    }
    if (!fgets(buf, MAX_LEN, stdin))
        goto EXIT_A;

    // Get the num tests
    int m ;
    scanf("%d\n", &m);

    char *num_buf = malloc(DOUBLE_ROW_LEN);
    if (!num_buf) {
        printf("Failed to allocate num_buffer");
        goto EXIT_A;
    }

    int *nn;
    int *start = calloc(m, sizeof(int));
    int *stop = calloc(m, sizeof(int));
    int *staptr = start; 
    int *stpptr = stop;
    char *cptr;
    for(int i=0; i<m; i++) {
        fgets(num_buf, DOUBLE_ROW_LEN, stdin);
        nn = staptr++;
        cptr = num_buf-1;
        while(*(++cptr) > '\n') {
            if (*cptr == ' ')
                nn = stpptr++;
            else
                *nn = *nn*10 + *cptr-'0';
        }
    }


    // Count for each test
    char *buf_end = strchr(buf, '\0');
    int len, shift;
    char outbuf[ROW_LEN];
    char *ptr_l, *ptr_r, *out;
    for(int i=0; i<m; i++) {
        ptr_l = buf + start[i];
        ptr_r = buf + stop[i];
        while(ptr_r < buf_end && *ptr_l == *ptr_r) {
            ++ptr_l;
            ++ptr_r;
        }

        // Print length of same sequence
        shift = len = (int)(ptr_l - (buf + start[i]));
        out = outbuf;
        do {
            out++;
            shift /= 10;
        } while (shift);
        *out = '\0';
        do {
            *(--out) = "0123456789"[len%10];
            len /= 10;
        } while(len);
        puts(outbuf);
    }



    ret = 0;

    free(start);
    free(stop);
EXIT_A:
    free(buf);
    return ret;
}

3 个答案:

答案 0 :(得分:2)

感谢您的提问,我自己去解决了这个问题。你的时间比我的好,但我还在使用一些stdio功能。

我根本不认为0.05秒的高分是真实的。我怀疑它是高度自动化系统的产物,它返回导致错误,并且没有人验证过它。

如何捍卫这种说法?没有真正的算法复杂性:问题是 O(n)。 &#34;技巧&#34;是为输入的每个方面编写专门的解析器(并避免仅在调试模式下完成工作)。 22次试验的总时间是50毫秒,这意味着每个试验的平均时间为2.25毫秒?我们在可衡量性的门槛附近下降。

在某种程度上,像你自己解决的问题这样的竞争是不幸的。他们强化了天真的观念,即表演是计划的最终衡量标准(清晰度没有得分)。更糟糕的是,他们鼓励围绕诸如scanf&#34;以及表现&#34;而在现实生活中,让程序正确快速地运行基本上不需要避免甚至调整stdio。在复杂的系统中,性能来自避免 I / O,仅传递数据一次,以及最小化副本。有效地使用DBMS通常是关键(尽管如此),但这些事情从未出现在编程挑战中。

将数字解析和格式化为文本确实需要时间,并且在极少数情况下可能成为瓶颈。但答案几乎没有改写解析器。相反,答案是将文本解析为方便的二进制形式,并使用它。简而言之:编译。

尽管如此,一些观察结果可能有所帮助。

你不需要动态记忆来解决这个问题,而且它没有帮助。问题陈述说输入数组可能多达100,000个元素,并且试验次数可能多达100,000个。每个试验是两个整数字符串,最多6个数字,每个由空格分隔,并以换行符结束:6 + 1 + 6 + 1 = 14.总输入,最大值为100,000 + 1 + 6 + 1 + 100,000 * 14: 16 KB。您可以使用1 GB的内存。

我刚刚分配了一个16 KB的缓冲区,并使用read(2)一次性读取它。然后我对该输入进行了一次传递。

您有使用异步I / O和线程的建议。问题陈述说你是在CPU时间上测量的,所以这些都没有帮助。两点之间的最短距离是一条直线;单次读入静态分配的内存不会浪费任何动作。

他们衡量绩效的一个荒谬方面是他们使用 gcc -g 。这意味着在测量性能的代码中调用断言(3)!在测试22上我无法在4秒内完成,直到我删除了我的断言。

总而言之,你做得很好,我怀疑你被困惑的胜利者是一个幽灵。你的代码确实很有用,你可以省去动态内存和调优stdio。我敢打赌,通过简化它可以缩短你的时间。如果表现很重要,那就是我引起你注意的地方。

答案 1 :(得分:0)

您应该连续分配所有缓冲区。 分配一个缓冲区,该缓冲区是所有缓冲区的大小(num_buff,start,stop),然后按照大小将点重新排列到相应的偏移量。 这可以减少缓存未命中\页面错误。

由于读取和写入操作似乎消耗了大量时间,因此您应该考虑添加线程。一个线程应该处理I \ O而另一个线程应该处理计算。 (值得检查另一个打印线程是否也能加快速度)。确保在执行此操作时不使用任何锁定。

答案 2 :(得分:0)

回答这个问题很棘手,因为优化在很大程度上取决于你所遇到的问题。 一个想法是查看您尝试阅读的文件的内容,看看是否有您可以使用的模式或事物。 您编写的代码是一种“通用”解决方案,用于从文件读取,执行某些操作然后写入文件。但是,如果您不是每次都随机生成文件且内容始终相同,为什么不尝试为该文件编写解决方案?

另一方面,您可以尝试使用低级系统功能。我想到的是mmap,它允许您将文件直接映射到内存并访问该内存,而不是使用scanffgets

我发现可能有帮助的另一件事是你的解决方案中你有两个while循环,为什么不尝试只使用一个?另一件事是做一些异步I / O读取,所以不是在循环中读取整个文件,然后在另一个循环中进行计算,你可以尝试在开头读取一部分,开始处理异步并继续读。 这个link可能有助于异步部分