在C中打开和写入多个文件

Bha*_*lwe 4 c unix file-descriptor

输入是一个大约70GB的单个文件,其中每一行都包含客户端信息.程序读取此文件并为每个客户端生成一个文件.有8000个客户,但我们必须为40000个客户提供服务.目前,UNIX排序命令用于按客户端对文件进行排序,然后写入客户端文件.这样,程序只打开一个文件处理程序来创建文件.我们不想使用sort命令,因为它消耗大约1.5小时.但这意味着打开8000个文件处理程序将需要保持打开状态.可能需要修改内核参数.是否可以在不更改内核参数的情况下打开这么多文件.我试过通过libevent网站,但我不确定这是否是正确的解决方案.

pax*_*blo 11

您不一定需要同时打开8000个文件句柄,也不需要对数据进行排序.除非您需要对每个客户线进行排序,否则排序是一种浪费.

据推测,您可以通过线路上的某个项目识别客户端.让我们说(例如)它是每行的前8个字符,那么你的伪代码如下所示:

delete all files matching "*_out.dat"
for each line in file:
    key = left (line, 8)
    open file key + "_out.dat" for append
    write line to file
    close file
Run Code Online (Sandbox Code Playgroud)

而已.简单.一次只打开一个文件,没有时间浪费排序.

现在可以对其进行进一步的改进,其中包括:

  1. 除非下一行具有不同的键,否则不要关闭上一行的文件.这将捕获同一个键连续有一百条记录并在这种情况下保持文件打开的情况.

  2. 保持打开文件句柄的缓存,如最近使用的列表(例如16个不同的键).同样,这将阻止关闭,直到必须重用文件句柄,但它也将处理集群更有效的情况(例如客户1,2,3,7,1,2,3,2,2,3, 7,4,...).

但基本理论仍然是相同的:当你可以少花钱时,不要试图一次打开8000(或40000)文件.


或者,只需处理数据,将其全部存储到数据库中并使用查询然后使用一系列查询创建每个文件.是否应该测试比上述解决方案更快的事实,因为事实上应该在这里给出每个建议.测量,不要猜!


现在,因为我已经调用了这个优化口号,让我们做一些时间安排,记住这是我的硬件特有的,可能与你的硬件有所不同.

先从下面的脚本,产生一百万行的文件,其中每行的前八个字符是之间的随机数1000000010032767包容性.我们将使用5到8的字符来为我们提供客户编号,每位客户大约一百行的一万名客户:

#!/bin/bash
line='the quick brown fox jumps over the lazy dog'
for p0 in 1 2 3 4 5 6 7 8 9 0 ; do
 for p1 in 1 2 3 4 5 6 7 8 9 0 ; do
  for p2 in 1 2 3 4 5 6 7 8 9 0 ; do
   for p3 in 1 2 3 4 5 6 7 8 9 0 ; do
    for p4 in 1 2 3 4 5 6 7 8 9 0 ; do
     for p5 in 1 2 3 4 5 6 7 8 9 0 ; do
      ((x = 10000000 + $RANDOM))
      echo "$x$line"
     done
    done
   done
  done
 done
done
Run Code Online (Sandbox Code Playgroud)

产生的文件大小约为50M.我们可以通过简单地将它的2个副本连接到另一个文件来扩展到100M,这给了我们每个客户大约200行.


现在,检查以下程序:

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

#define FOUT_STR "1234_out.dat"

int main (void) {
    FILE *fIn, *fOut;
    char outFile[sizeof (FOUT_STR)];
    char buff[1000];

    if ((fIn = fopen ("data.dat", "r")) == NULL) {
        printf ("Error %d opening 'data.dat'\n", errno);
        return 1;
    }

    memcpy (outFile, FOUT_STR, sizeof (FOUT_STR));
    if ((fOut = fopen (outFile, "w")) == NULL) {
        printf ("Error %d opening '%s'\n", errno, outFile);
        return 1;
    }

    while (fgets (buff, sizeof (buff), fIn) != NULL) {
        fputs (buff, fOut);
    }

    fclose (fOut);
    fclose (fIn);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这给出了将所有条目写入单个文件的基线数字,并且运行时间不到一秒.


现在让我们每两百行打开一个新文件 - 如果文件已经按客户排序,这就是你看到的行为:

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

#define FOUT_STR "1234_out.dat"

int main (void) {
    FILE *fIn, *fOut;
    char outFile[sizeof (FOUT_STR)];
    char buff[1000];
    char custNum[5];
    int i = -1;

    if ((fIn = fopen ("data.dat", "r")) == NULL) {
        printf ("Error %d opening 'data.dat'\n", errno);
        return 1;
    }

    fOut = NULL;
    while (fgets (buff, sizeof (buff), fIn) != NULL) {
        i++;
        if ((i % 200) == 0) {
            if (fOut != NULL)
                fclose (fOut);
            sprintf (custNum, "%04d", i / 200);
            memcpy (outFile, FOUT_STR, sizeof (FOUT_STR));
            memcpy (outFile, custNum, 4);
            if ((fOut = fopen (outFile, "w")) == NULL) {
                printf ("Error %d opening '%s'\n", errno, outFile);
                break;
            }
        }
        fputs (buff, fOut);
    }
    if (fOut != NULL)
        fclose (fOut);

    fclose (fIn);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这需要大约2s(0:00:02)的100M文件,并用200M和400M文件测试它表明它线性扩展.这意味着使用排序的70G文件,您可以查看大约1400或0:23:20.请注意,这将超出您的排序成本1.5h(1:30:00),总成本为1:53:20.


现在让我们实现简单的程序,只需打开每个文件以追加每一行:

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

#define FOUT_STR "1234_out.dat"

int main (void) {
    FILE *fIn, *fOut;
    char outFile[sizeof (FOUT_STR)];
    char buff[1000];

    if ((fIn = fopen ("data.dat", "r")) == NULL) {
        printf ("Error %d opening 'data.dat'\n", errno);
        return 1;
    }

    while (fgets (buff, sizeof (buff), fIn) != NULL) {
        memcpy (outFile, FOUT_STR, sizeof (FOUT_STR));
        memcpy (outFile, &(buff[4]), 4);
        if ((fOut = fopen (outFile, "a")) == NULL) {
            printf ("Error %d opening '%s'\n", errno, outFile);
            break;
        }
        fputs (buff, fOut);
        fclose (fOut);
    }

    fclose (fIn);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当我们使用100M文件运行它时,需要244s(0:04:04).再次,使用200M和400M文件进行测试表明线性缩放.因此,对于70G文件,这将是47:26:40,而不是你所谓的改进,而不是你的亚2小时排序和处理选项.


但是,让我们尝试一个不同的方法,使用以下程序,每次通过输入文件维护一百个文件句柄(完成一百次):

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

#define FOUT_STR "1234_out.dat"

int main (void) {
    FILE *fIn, *fOut[100];
    char outFile[sizeof (FOUT_STR)];
    char buff[1000];
    int seg, cust;
    char segNum[3], custNum[3];

    for (seg = 0; seg < 100; seg++) {
        sprintf (segNum, "%02d", seg);

        if ((fIn = fopen ("data.dat", "r")) == NULL) {
            printf ("Error %d opening 'data.dat'\n", errno);
            return 1;
        }

        for (cust = 0; cust < 100; cust++) {
            sprintf (custNum, "%02d", cust);

            memcpy (outFile, FOUT_STR, sizeof (FOUT_STR));
            memcpy (outFile+0, segNum, 2);
            memcpy (outFile+2, custNum, 2);
            if ((fOut[cust] = fopen (outFile, "w")) == NULL) {
                printf ("Error %d opening '%s'\n", errno, outFile);
                return 1;
            }
        }

        while (fgets (buff, sizeof (buff), fIn) != NULL) {
            if (memcmp (&(buff[4]), segNum, 2) == 0) {
                cust = (buff[6] - '0') * 10 + buff[7] - '0';
                fputs (buff, fOut[cust]);
            }
        }

        for (cust = 0; cust < 100; cust++) {
            fclose (fOut[cust]);
        }

        fclose (fIn);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是一个轻微的变化,它实际上处理输入文件一百次,每次只处理意味着一百个单独输出文件的行.

当在100M文件上运行时,大约需要28秒(0:00:28).再一次,这似乎是一个200M和400M文件的线性扩展,所以70G文件应该采取5:26:40.

仍然没有接近两个小时以下的数字.


那么当我们一次打开一千个输出文件时会发生什么:

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

#define FOUT_STR "1234_out.dat"

int main (void) {
    FILE *fIn, *fOut[1000];
    char outFile[sizeof (FOUT_STR)];
    char buff[1000];
    int seg, cust;
    char segNum[2], custNum[4];

    for (seg = 0; seg < 10; seg++) {
        sprintf (segNum, "%01d", seg);

        if ((fIn = fopen ("data.dat", "r")) == NULL) {
            printf ("Error %d opening 'data.dat'\n", errno);
            return 1;
        }

        for (cust = 0; cust < 1000; cust++) {
            sprintf (custNum, "%03d", cust);

            memcpy (outFile, FOUT_STR, sizeof (FOUT_STR));
            memcpy (outFile+0, segNum, 1);
            memcpy (outFile+1, custNum, 3);
            if ((fOut[cust] = fopen (outFile, "w")) == NULL) {
                printf ("Error %d opening '%s'\n", errno, outFile);
                return 1;
            }
        }

        while (fgets (buff, sizeof (buff), fIn) != NULL) {
            if (memcmp (&(buff[4]), segNum, 1) == 0) {
                cust = (buff[5] - '0') * 100 + (buff[6] - '0') * 10 + buff[7] - '0';
                fputs (buff, fOut[cust]);
            }
        }

        for (cust = 0; cust < 1000; cust++) {
            fclose (fOut[cust]);
        }

        fclose (fIn);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

对于100M文件大约需要12秒,并且会给我们2:20:00,接近排序但不完全相同.


不幸的是,当我们进入下一个逻辑步骤时,尝试在一次打击中打开整个10000个文件,我们看到:

Error 24 opening '1020_out.dat'
Run Code Online (Sandbox Code Playgroud)

这意味着我们终于达到了极限(标准输入,标准输出,标准错误和大约1019个其他文件句柄),这表明1024个句柄是我们所允许的.

因此,排序和处理方法可能最好的方法.