【问题标题】:What are the other options of faster IO in CC中更快的IO的其他选择是什么
【发布时间】:2013-07-04 12:45:53
【问题描述】:

我实现了归并排序并将其用作this codechef problem 的解决方案。这是submissions。代码放在下面。

我认为导致执行缓慢的问题是我的 IO 在main 函数中很慢。我知道输入的元素数量,因此必须有一些更快的方式来读取输入,而不是我正在做的方式。

是否有更快的 IO 方法而不是我在 main 函数中使用的方法?我听说过使用缓冲区,fgetssscanf,但我不知道它们是否更快。

任何代码示例都会有所帮助。

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

void merge_parts(int arr[], int length)
{
    int *ans;
    int i, j, k;
    int temp = length/2;

    ans = malloc(sizeof(int) * length);

    //This while and next if-else puts the merged array into temporary array ans
    for (j = temp, i = k = 0; (i < temp && j < length); k++){
        ans[k] = (arr[i] < arr[j]) ? arr[i++] : arr[j++];
    }

    if(i >= temp){
        while(j < length){
            ans[k++] = arr[j++];
        }
    }
    else{
        while(i < temp){
            ans[k++] = arr[i++];
        }
    }

    //This while loops puts array ans into original array arr
    for(i = 0; i < length; i++){
        arr[i] = ans[i];
    }

    free(ans);
}

void merge_sort(int arr[], int length)
{
    if(length > 1)
    {
        merge_sort(&arr[0], (length/2));
        merge_sort(&arr[length/2], (length - length/2));
        merge_parts(arr, length);
    }
}

int main()
{
    int length;
    int *arr;
    scanf("%d", &length);
    arr = malloc(sizeof(int) * length);

    for(int i = 0; i < length; i++)
        scanf("%d", &arr[i]);

    merge_sort(arr, length);

    for(int i = 0; i < length; i++)
        printf("%d ", arr[i]);

    free(arr);
    return 0;
}

EDIT3:

[我删除了 EDIT 和 EDIT2,因为它们不再相关]

我正在使用的merge_sort算法

void merge_parts(int arr[], int length)
{
    int ans[length];
    int i, j, k;
    int temp = length/2;
    //This while and next if-else puts the merged array into temporary array ans
    for (j = temp, i = k = 0; (i < temp && j < length); k++){
        ans[k] = (arr[i] < arr[j]) ? arr[i++] : arr[j++];
    }

    if(i >= temp){
        while(j < length){
            ans[k++] = arr[j++];
        }
    }
    else{
        while(i < temp){
            ans[k++] = arr[i++];
        }
    }

    //This while loops puts array ans into original array arr
    for(i = 0; i < length; i++){
        arr[i] = ans[i];
    }
}

void merge_sort(int arr[], int length)
{
    if(length > 1)
    {
        merge_sort(&arr[0], (length/2));
        merge_sort(&arr[length/2], (length - length/2));
        merge_parts(arr, length);
    }
}

merge1.c

#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
#include<time.h>

#define SORTING_ALGO_CALL merge_sort

char buffer[4096];
int bufcount;
int bufpos;

int get_next_char()
{
    if (!bufcount)
    {
        bufcount = fread(buffer, 1, 4096, stdin);
        bufpos = 0;
        if (!bufcount){
            return EOF;
        }
    }
    bufcount--;
    return buffer[bufpos++];
}

int readnum()
{
    int res = 0;
    char ch;
    do
    {
        ch = get_next_char();
    } while (!isdigit(ch) && ch != EOF);

    if (ch == EOF){
            return 0xbaadbeef;    // Don't expect this to happen.
    }

    do
    {
        res = (res * 10) + ch - '0';
        ch = get_next_char();
    } while(isdigit(ch));
    return res;
}


int main()
{
    clock_t time1, time2;
    double time_taken;

//FIRST READ
    time1 = clock();

    int length = readnum();
    while (length < 1)
    {
        printf("\nYou entered length = %d\n", length);
        printf("\nEnter a positive length: ");
        length = readnum();
    }

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nReading length = %f\n", time_taken);
    time1 = clock();

    int *arr;
    if ((arr = malloc(sizeof(int) * length)) == NULL)
    {
        perror("The following error occurred");
        exit(-1);
    }

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nAllocating array = %f\n", time_taken);
    time1 = clock();

    for (int i = 0; i < length; i++){
        arr[i] = readnum();
    }

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nReading array = %f\n", time_taken);
    time1 = clock();

    SORTING_ALGO_CALL(arr, length);

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nSorting array = %f\n", time_taken);
    time1 = clock();
/*
    for (int i = 0; i < length; i++){
        printf("%d ", arr[i]);
    }
*/
//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nPrinting Sorted array = %f\n", time_taken);
    time1 = clock();

    free(arr);

//SECOND READ, PRINT
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nFreeing array = %f\n", time_taken);

    return 0;
}

merge2.c

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

#define SORTING_ALGO_CALL merge_sort

int main()
{
    clock_t time1, time2;
    double time_taken;

//FIRST READ
    time1 = clock();

    int length;
    scanf("%d", &length);
    while (length < 1)
    {
        printf("\nYou entered length = %d\n", length);
        printf("\nEnter a positive length: ");
        scanf("%d", &length);
    }

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nReading length = %f\n", time_taken);
    time1 = clock();

    int *arr;
    if ((arr = malloc(sizeof(int) * length)) == NULL)
    {
        perror("The following error occurred");
        exit(-1);
    }

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nAllocating array = %f\n", time_taken);
    time1 = clock();

    for (int i = 0; i < length; i++){
        scanf("%d", &arr[i]);
    }

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nReading array = %f\n", time_taken);
    time1 = clock();

    SORTING_ALGO_CALL(arr, length);

//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nSorting array = %f\n", time_taken);
    time1 = clock();
/*
    for (int i = 0; i < length; i++){
        printf("%d ", arr[i]);
    }
*/
//SECOND READ, PRINT AND NEXT FIRST READ
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nPrinting Sorted array = %f\n", time_taken);
    time1 = clock();

    free(arr);

//SECOND READ, PRINT
    time2 = clock();
    time_taken = (double)(time2 - time1) / CLOCKS_PER_SEC;
    printf("\nFreeing array = %f\n", time_taken);

    return 0;
}

merge1.c 和 merge2.c 都包含合并排序的 2 个函数。

我用于为 2 个文件生成最坏情况(递减顺序)输入的文件

#include<stdio.h>

int main()
{
    int j = 100000;
    printf("%d\n", j);
    for(int i = j; i > 0; i--)
        printf("%d\n", i);

    return 0;
}

merge1.c 的计时结果

Reading length = 23.055000

Allocating array = 0.000000

Reading array = 0.010000

Sorting array = 0.020000

Printing Sorted array = 0.000000

Freeing array = 0.000000

merge2.c 的计时结果

Reading length = 22.763000

Allocating array = 0.000000

Reading array = 0.020000

Sorting array = 0.020000

Printing Sorted array = 0.000000

Freeing array = 0.000000

【问题讨论】:

    标签: c sorting optimization io


    【解决方案1】:

    您几乎可以通过编写自己的小函数来读取数字来击败scanf

    如果所有数字都是decimal 并由非数字分隔,这将起作用:

     char buffer[4096]; 
     int bufcount;
     int bufpos;
    
     int get_next_char()
     {
         if (!bufcount)
         {
             bufcount = fread(buffer, 1, 4096, stdin);
             bufpos = 0;
             if (!bufcount){
                return EOF;
             }
         }
         bufcount--;
         return buffer[bufpos++]; 
     }
    
    
     int is_digit(int ch)
     {
         if (ch >= '0' && ch <= '9')
            return 1;
         return 0;
     }
    
     int readnum()
     {
         int res = 0;
         int ch;
         do
         {
             ch = get_next_char();
         } while(!is_digit(ch) && ch != EOF);
         if (ch == EOF)
         {
            return 0xbaadbeef;    // Don't expect this to happen. 
         }
         do
         {
             res = (res * 10) + (ch - '0');
             ch = get_next_char();
         } while(is_digit(ch));
         return res;
     }
    

    scanf 中的代码比这复杂得多,并且很可能调用getcfgetc,这比上面的代码效率要低一些。但是,值得准确衡量您在哪里花费时间。打印每个函数的时间作为输出的一部分。

    【讨论】:

    • 我应该使用this method 来测量执行时间吗?另外如何打印每个功能的时间?我的意思是在归并排序中有很多递归的函数调用。我假设您没有要求执行所有这些的时间。那将是一团糟。
    • 是的,只要时间超过十分之几秒,clock() 就可以正常工作。首先,我会测量输入阶段和排序阶段所花费的时间。
    • 我已经在我的机器上运行了你的代码(我修改了“make numbers”以根据 argv 生成一个任意数字),它没有显示这个问题。因为你的代码在堆栈上使用了一个大的临时存储空间,所以我不能运行超过 2M 的数字,但是根据我的输入,读取需要 0.13 秒,排序需要 0.12 秒。
    • 使用scanf 而不是readnum 读取数字需要0.4 秒。
    • 即使将缓冲区大小降低到 16 字节,readnum 仍然比 scanf 变体快约 2 倍,但读取 2M 个数字的速度正在接近 0.2 秒。
    【解决方案2】:

    我会补充 Mats 的答案,而不是使用 stdin,将文件名作为输入。 然后打开文件(如果在 Windows 上,则为二进制格式)。 获取文件长度,malloc 足够大的缓冲区,将整个文件读入其中,然后关闭文件。 然后我会使用一个字符指针解析到缓冲区。 这样,获取下一个字符不需要函数调用。 速度很难被击败。

    解析整数的代码是:

    num = 0;
    while(isdigit(*pc)){
      num = num*10 + (*pc++ - '0');
    }
    

    【讨论】:

    • Snipet 只是目前的正整数,但是是的,这总体上非常快。 (我看到潜在的竞争是 >= 0,所以这一切都很好)
    • 我的代码应该使用合格的编译器生成大致相同的代码。但是,它将读取较小的块,根据我的经验,这更好(只要块足够大),因为读取大量数据需要时间,并且操作系统将在后台预取,但它可以如果您将所有内容作为一个整体阅读,请不要这样做。而且由于输入的是stdin,因此可能无法一次性检查输入的大小。
    • 就我而言,我必须使用stdin。这是我无法改变的限制。如果有办法使用stdin,正如你所说,我很乐意听到。
    • @AseemBansal:在这种情况下,我会使用 Mats 的方法,以缓冲区大小的块处理它,但我会像我写的那样使用解析代码,因为他比我更相信编译器:)
    • Mat 实际上在他的readnum 函数中编写了相同的代码。好吧,措辞不完全相同,但功能相同
    【解决方案3】:
    • 在优化问题中,经验法则是最好的。尝试获取每一步所花费时间的数值。加载 - 排序 - 等等...您可以为此使用分析器(如 gprof)。

    • 为了加快 IO,您应该考虑减少对 scanf 的调用。由于您有 scanf 要求的数量,您可以为此特定部分设计更好的算法。

    • Scanf 做了很多事情,解析第一个 arg,然后读取字节并将其转换为格式。如果我们想走得更快,我们将使用“数据问题”来跳过一些步骤。首先,我们知道我们只是在 N(数学)上使用数字定义。其次,我们知道每个字节都是数字或分隔符。我们可以使用它。

    所以我们使用 read() 系统调用可以从文件描述符中读取一些字节。标准输入的文件描述符在操作系统之间变化,但通常为0。

    宏算法可以是:

    index = 0
    buffer = new array[10000];
    numberOfByteRead = 1
    while there is byte that have been read at last call of read.
          numberOfByteRead = read said 10000 byte to buffer;
          parse the buffer
    ;;
    
    parse(buffer,numberOfByteRead)
    for all true byte in buffer :
       switch (buffer[0])
          case '0': { the mathematical operation on arr[index] that fit for '0'; break;  }
          case '1': { ... break;}
          case ' ': {index++; break;}
    ;;
    

    代码不是很有趣,但比 scanf 快。 大于 10000 的值会减少 IO 时间,但会增加内存。 你必须平衡。

    【讨论】:

    • my top results here。带时间的顶部3.19 使用stdlibqsort 而我的merge sort 使用3.29(第二个)。所以我猜我的实现不是速度的瓶颈。我同意应该少打电话给scanf。但我不知道任何其他读取输入的方法。这就是我询问other options of faster IO in C 的原因。有什么建议吗?
    • 分析是个好主意。但我通常在 Windows 平台上工作,因此使用基于 linux 的分析器可能需要一些工作。我会尝试使用它。关于我可以使用的算法有什么建议吗?
    • 这个技巧可能会有所帮助:if(n%2==1)scanfOneNumber;n--; while(n>0) 扫描fTwoNumbers。但是恕我直言,您应该编写自己的 scanf 函数,专门用于读取数字。
    • 我注意到在您的实现中,您在每个 merge_parts 处都使用了 malloc/free。我认为可以通过在开始时分配一个新数组并使用索引来改善这一点。它应该花费更少。
    • 我一直认为每个数组的大小都需要在编译时知道,或者需要进行动态内存分配。感谢那。它有助于减少时间,但它以某种方式增加了内存使用量。任何想法为什么?我也不知道如何编写我自己的scanf 函数版本。我从来没有做过这样的事情。我能想到的方法是使用标准库,但除非我在新的scanf 的定义中使用scanf 以外的其他函数,否则这将是相同的。任何关于寻找什么或在哪里寻找的指针?
    【解决方案4】:
    static char buff[8*1000000];
    int i, length, blen;
    int *ap, *p;
    int n = 0;
    char ch, *cp = buff;
    
    scanf("%d%*c", &length);
    p = ap = malloc(sizeof(*ap) * length);
    
    blen = fread(buff, 1, 8*1000000, stdin);
    while(blen--){
        if(isdigit(ch=*cp++)){
            n = n * 10 + ch - '0';
        } else {
            *p++ = n;
            n = 0;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-06-06
      • 1970-01-01
      • 2011-09-21
      • 2011-02-26
      • 2011-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多