c - 在C中打开并写入多个文件

标签 c unix file-descriptor

输入是一个大约70gb的文件,它的每一行都包含客户机信息。一个程序读取这个文件并为每个客户机生成一个文件。有8000个客户,但我们必须提供40000个客户。目前,unix sort命令用于按客户端对文件进行排序,然后写入客户端文件。这样,程序只打开一个文件处理程序来创建文件。
我们不想使用sort命令,因为它大约需要1.5个小时。然而,这意味着8000个打开的文件处理程序需要保持打开状态。可能需要修改内核参数。
不改变内核参数就可以打开这么多文件吗?我试过浏览libevent网站,但不确定这是否是正确的解决方案。

最佳答案

您不一定需要同时打开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

就是这样。简单。一次只打开一个文件,不会浪费时间进行排序。
现在,这方面还有进一步的改进,其中包括:
不要关闭前一行的文件,除非下一行有不同的键。这将捕获这样的情况:对于同一个键,一行有100条记录,在这种情况下,将保持文件打开。
像最近使用的列表(比如16个不同的键)一样,维护一个打开文件句柄的缓存。同样,这将防止关闭,直到必须重用一个文件句柄,但它将继续处理集群效率更高的情况(例如客户1、2、3、7、1、2、3、2、2、3、7、4,…)。
但基本原理是一样的:当你可以用更少的钱过日子时,不要试图一次打开8000(或40000)个文件。
或者,只需处理数据,将其全部保存到数据库中,然后使用查询创建包含一系列查询的每个文件。是否比上面的解决方案更快,应该进行测试,事实上这里给出的每个建议都应该如此。量度,别猜!
现在,既然我已经引用了优化的咒语,让我们做一些计时,记住这是我的硬件特有的,可能与你的不同。
从下面的脚本开始,它生成一个一百万行文件,其中每行的前八个字符是1000000010032767之间的随机数。我们将使用字符5到8(包括字符5到字符8)来给出客户编号,即10000个客户,每个客户大约有100行:
#!/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

生成的文件大小约为50米。我们只需将其两个副本连接到另一个文件,就可以将其扩展到100米,这为每个客户提供了大约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;
}

这提供了一个基线图,只需将所有条目写入一个文件,运行时间不到一秒钟。
现在让我们每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];
    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;
}

100米文件大约需要2秒(0:00:02),用200米和400米文件测试表明它是线性缩放的。这意味着,对于一个排序后的70g文件,你看到的是大约1400s或0:23:20。请注意,这将超出您的分类成本,您有1.5小时(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;
}

当我们用100M文件运行这个文件时,需要244s(0:04:04)。同样,使用200米和400米的文件进行测试表明了线性缩放。所以,对于70g文件,这将是47:26:40,并不是你所说的比你的两小时排序和处理选项的改进。
但是,让我们尝试一种不同的方法,使用以下程序,它通过输入文件每次维护100个文件句柄(执行100次):
#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;
}

这是一个微小的变化,它实际处理输入文件100次,每次只处理100个单独输出文件的行。
在100M文件上运行时,大约需要28秒(0:00:28)。再一次,对于200米和400米的文件,这似乎是线性缩放,因此70g文件应该采用5:26:40。
甚至还没有接近2小时以下的数字。
那么,当我们一次打开1000个输出文件时会发生什么:
#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;
}

100米的文件大约需要12秒,大概是2:20:00,接近同类,但还不完全符合。
不幸的是,当我们进入下一个逻辑步骤,试图一次性打开10000个文件时,我们看到:
Error 24 opening '1020_out.dat'

这意味着我们终于达到了极限(标准输入、标准输出、标准错误和大约1019个其他文件句柄),这表明1024个句柄是我们所允许的全部。
所以也许分类和处理方法是最好的方法。

关于c - 在C中打开并写入多个文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10121838/

相关文章:

c - Doxygen:在文件级别分组数据结构

c++ - 滚动大图像故障的 MSDN 示例

shell - sh:如何避免破坏编号的文件描述符?

创建替换 stdin 的文件描述符(在 select() 中)

c - posix mqueue 中的错误文件描述符

c - 2个内联函数在C中相互调用的行为

c++ - 未定义/未指定/实现定义的行为警告?

linux - 哪些系统调用不会被信号中断?

linux - 为 "echo"输出创建时间戳到文件

Linux,获得 root 访问权限的唯一方法是执行 setuid-root 文件?