【问题标题】:C read big file into char* array too slowC将大文件读入char *数组太慢
【发布时间】:2017-01-13 02:24:40
【问题描述】:

我想在一行的第一个字符不是“”时读取一个大文件。 但是我写的代码很慢。我怎样才能加快例行程序? 有没有比 getline 更好的解决方案?

void readString(const char *fn)
{
    FILE *fp;
    char *vString;
    struct stat fdstat;
    int stat_res;

    stat_res = stat(fn, &fdstat);
    fp = fopen(fn, "r+b");

    if (fp && !stat_res)
    {
      vString = (char *)calloc(fdstat.st_size + 1, sizeof(char));

      int dataEnd = 1;
      size_t len = 0;
      int emptyLine = 1;
      char **linePtr = malloc(sizeof(char*));
      *linePtr = NULL;

      while(dataEnd)
      {
        // Check every line
        getline(linePtr, &len, fp);

        // When data ends, the line begins with space (" ")
        if(*linePtr[0] == 0x20)
           emptyLine = 0;           

        // If line begins with space, stop writing
        if(emptyLine)
           strcat(vString, *linePtr);
        else
           dataEnd = 0;
      }

      strcat(vString, "\0");
      free(linePtr);
      linePtr = NULL;
    }
}

int main(int argc, char **argv){
    readString(argv[1]);
    return EXIT_SUCCESS;
}

【问题讨论】:

  • calloc = malloc + memset(..., 0, ...) 一步到位。
  • malloc(0) 没有返回size_t 的有效地址,请按照getline 手册中所述从堆栈传递一个变量:size_t len = 0; ... getline(&line, &len, stream)
  • 谢谢!我已经修好了..但这并没有加快我的代码速度;)
  • 当你调用getline时,你需要传入一个缓冲区的地址,以及一个保存该缓冲区长度的size_t的地址。像这样的东西: char *buf = malloc(numberOfBytes); size_t bufsize = numberOfBytes; getline(&buf, &bufsize, f);
  • 您可以尝试将mmap() 与文件一起使用,然后不需要将其读入内存。

标签: c performance file stream getline


【解决方案1】:

您是否尝试过使用 fread 读取文件并在每个步骤中读取更大的数据块,然后在读取后解析数据?比如:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

char *readString(const char *fn)
{
    FILE *fp;
    char *vString;
    struct stat fdstat;
    int stat_res;

    stat_res = stat(fn, &fdstat);
    fp = fopen(fn, "r+b");

    if (fp && !stat_res) {
    vString = (char *) calloc(fdstat.st_size + 1, sizeof(char));

    int newline = 1;
    int index = 0;
    while (index < fdstat.st_size) {
        int len =
        fdstat.st_size - index >
        4096 ? 4096 : fdstat.st_size - index;
        char *buffer = (char *) malloc(len);
        int read_len = fread(buffer, 1, len, fp);
        int i;
        if (newline) {
        if (read_len > 0 && buffer[0] == ' ') {
            return vString;
        }
        newline = 0;
        }
        for (i = 0; i < read_len; ++i) {
        if (buffer[i] == '\n') {
            if (i + 1 < read_len && buffer[i + 1] == ' ') {
            memcpy(vString + index, buffer, i + 1);
            return vString;
            }
            newline = 1;
        }
        }
        memcpy(vString + index, buffer, read_len);
        index += read_len;
    }
    }
    return vString;
}

int main(int argc, char **argv)
{
    char *str = readString(argv[1]);
    printf("%s", str);
    free(str);
    return EXIT_SUCCESS;
}

【讨论】:

    【解决方案2】:

    我怎样才能加快例行程序?

    程序性能方面最可疑的方面是strcat()。在每次调用时,它需要从头开始扫描整个目标字符串以找到附加源字符串的位置。因此,如果您的文件行的长度受一个常数(甚至是一个大常数)限制,那么您的方法的性能会随着文件长度的平方成比例变化。

    不过,渐近复杂性分析不一定能说明问题。代码的 I/O 部分与文件长度成线性关系,并且由于 I/O 比内存中的数据操作要昂贵得多,因此对于足够小的文件,这将支配你的性能。如果你处于那种状态,那么你可能不会比现在做得更好。不过,在这种情况下,您仍然可以通过fread() 一次读取整个文件,然后通过strstr() 扫描它以查找数据结尾来做得更好:

    size_t nread = fread(vString, 1, fdstat.st_size, fp);
    
    // Handle nread != fdstat.st_size ...
    
    // terminate the buffer as a string
    vString[nread] = '\0';
    
    // truncate the string after the end-of-data:
    char *eod = strstr(vString, "\n ");
    if (eod) {
        // terminator found - truncate the string after the newline
        eod[1] = '\0';
    } // else no terminator found
    

    它是线性扩展的,因此它也解决了您的渐近复杂性问题,但如果感兴趣的数据通常比文件短得多,那么在这些情况下,它会让您执行比您更昂贵的 I/O需要做。在那种情况下,一种选择是分块阅读,正如@laissez_faire 所建议的那样。另一种方法是调整您的原始算法以跟踪 vString 的结尾,以便使用 strcpy() 而不是 strcat() 来附加每个新行。该版本的关键部分如下所示:

    char *linePtr = NULL;
    size_t nread = 0;
    size_t len = 0;
    
    *vString = '\0';  // In case the first line is end-of-data
    for (char *end = vString; ; end += nread) {
        // Check every line
        nread = getline(&linePtr, &len, fp);
    
        if (nread < 0) {
            // handle eof or error ...
        }
    
        // When data ends, the line begins with space (" ")
        if (*linePtr == ' ') {
            break;
        }
        strcpy(end, *linePtr);
    }
    
    free(linePtr);
    

    另外,请注意

    • 您不需要最初将分配给*vString 的内存填零,因为您只需用真正感兴趣的数据覆盖这些零(然后忽略缓冲区的其余部分)。

    • 您不应强制转换malloc()-family 函数的返回值,包括calloc()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-21
      • 2015-07-11
      • 2017-01-20
      • 2023-04-05
      • 1970-01-01
      • 2011-08-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多