【问题标题】:How do I reduce the time complexity of a file IO program?如何降低文件 IO 程序的时间复杂度?
【发布时间】:2020-07-08 16:09:24
【问题描述】:

我编写了这段代码来查找 C 文件中某个单词的出现次数。该代码运行良好。但肯定需要很多时间。为了统计一个单词在 650MB 大小的文件中出现的次数,需要 151.1 秒,这是很多时间。我想以 80MB/秒的速度处理它。如何提高时间复杂度?非常感谢

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
int main(){
    FILE *fptr;
    int l,i=0,count=0,total=0;
    char name[100],n,word[25],k;
    printf("\nEnter the word to be found:");
    scanf("%s",word);
    l=strlen(word);
    printf("\nEnter the file name:");
    scanf("%s",name);
    fptr=fopen(name,"r");
    if(fptr==NULL){
        printf("\nProblem with opening the file");
        exit(1);
    }
    n=fgetc(fptr);
    while((feof(fptr)==0)){
        if(n==toupper(word[i])||n==tolower(word[i])){
            count++;
            i++;
        }
        else if(n!=word[i]){
            if(count>1){
                fseek(fptr, -count, SEEK_CUR);
            }
            count=0;
            i=0;
        }
        if(count==l){
            total++;
            count=0;
            i=0;
        }
        n=fgetc(fptr);
    }
    if(total==0){
        printf("\nThe word %s does not exist in the file",word);
    }
    printf("\nThe word %s occurred %d time(s) in the file",word,total);
}

【问题讨论】:

  • 是受CPU限制,还是受I/O限制?
  • stackoverflow.com/questions/12629749/how-does-grep-run-so-fast the time complexity of a file IO program? 你确定你问的是算法的时间复杂度,而不仅仅是如何更快地做到这一点?时间复杂度和执行时间是相关的,但仍然不同。
  • 你无法提高时间复杂度——它已经是 O(n),你只想让它更快。最直接的方法是大块读取文件——最好是一大块——然后在这些内存缓冲区上进行查找。
  • @KamilCuk 我只是希望它更快
  • @LeeDanielCrocker 这可能就是我想做的。我怎样才能做到这一点?

标签: c file-io


【解决方案1】:

您的程序也可能遭受某种形式的 I/O 放大,它会一遍又一遍地重新读取相同的数据。

这是你的主要文件读取循环:

n=fgetc(fptr);
while((feof(fptr)==0)){
    if(n==toupper(word[i])||n==tolower(word[i])){
        count++;
        i++;
    }
    else if(n!=word[i]){
        if(count>1){
            fseek(fptr, -count, SEEK_CUR);
        }
        count=0;
        i=0;
    }
    if(count==l){
        total++;
        count=0;
        i=0;
    }
    n=fgetc(fptr);
}

减少到只有 I/O 调用:

n=fgetc(fptr);
while((feof(fptr)==0)){
    if(n!=word[i]){
        if(count>1){
            fseek(fptr, -count, SEEK_CUR);
        }
        count=0;
        i=0;
    }

    n=fgetc(fptr);
}

发生了什么:

  1. 您以只读模式打开文件
  2. 由于文件是缓冲的,当您第一次调用fgetc() 时,您的程序实际上是从文件的当前偏移量读取文件并填满其缓冲区。这意味着您的程序可以立即读取多达几 kB(通常为 4kB 或 8kB,具体取决于您的系统)。
  3. 您的程序循环通过对fgetc() 的几次调用,每次调用都会向您的代码返回一个char 值(保存在int 中)。大多数情况下,char 只是从与 fptr 关联的缓冲区中复制而来。
  4. 您的程序调用fseek()。该调用使缓冲数据无效
  5. 在您下次调用 fgetc() 时,您的程序再次填满其缓冲区,大​​部分时间会重新读取已读取的数据。

根据您的程序调用fseek() 的频率,您的程序读取的数据可能比文件中实际包含的数据多几百到几千倍。

它并没有看起来那么糟糕,因为大多数读取都希望不是从磁盘一路读取,而是由系统的page cache 满足。但是每个fseek() 调用都会导致一个无关的上下文切换,以及使用fgetc() 一次读取char 的所有额外调用,可能会大大减慢您的程序。

简单地用fread() 之类的东西读取大块数据就可以了,但是因为你在数据流中“备份”(你的fseek() 调用),你必须考虑到“备份”到的可能性上一个数据块。

要可靠地做到这一点有点困难和乏味。

如果单词不跨越两行,最简单的解决方案是使用fgets()(或 POSIX 系统上的getline())逐行阅读:

for (;;)
{
    // define MAX_LINE_LENGTH to a suitable value
    char line[ MAX_LINE_LENGTH ];

    char *result = fgets( line, sizeof( line ), fp );

    // EOF (or error - either way there's no more data to be read)
    if ( result == NULL )
    {
        break;
    }

    // remove newline (if you want)
    line[ strcspn( line, "\n" ) ] = '\0';

    // now process a line of text
        .
        .
        .
}

按行阅读还允许使用标准函数(例如 strtok())将输入拆分为单独的单词,然后使用 strncasecmp() 查找与您正在查找的单词不区分大小写的匹配项。

【讨论】:

    【解决方案2】:

    一次读取更大的缓冲区。 fgetc() 用于一次读取一个字节,这是您可以读取的最小量,因此您正在最大化读取文件所需的步骤数。每个读取操作都有一些开销。 (每个 fgetc 调用不一定会导致从磁盘进行实际读取——在幕后会发生一些缓存和预读。)因此,您进行的调用越少,程序处理相同内容所需的操作就越少数据量。

    从技术上讲,大批量读取不会降低“时间复杂度”。就文件大小而言,它仍然大致是线性的,因此它是同一类别的复杂性。它会快很多,这才是你真正关心的。

    另外,我知道您只是为了这个问题而展示了简短的示例代码,但是您正在使用不安全的 scanf() 调用读取固定大小的缓冲区“word”和“name”。由于单词只有 25 个字节长,如果用户输入一个 26 个字符长的单词,他们可能会崩溃或利用您的程序。

    【讨论】:

      猜你喜欢
      • 2016-01-08
      • 2020-09-15
      • 1970-01-01
      • 1970-01-01
      • 2012-07-28
      • 1970-01-01
      • 2015-12-01
      • 2011-06-15
      相关资源
      最近更新 更多