[英]Opening and writing to multiple files in C
输入是一个大约 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
而已。 简单的。 一次只打开一个文件,无需浪费时间进行排序。
现在可以对此进行进一步的改进,其中包括:
除非下一行有不同的键,否则不要关闭上一行的文件。 这将捕获同一键连续有一百条记录的情况,并在这种情况下保持文件打开。
维护打开文件句柄的缓存,例如最近使用的列表(比如 16 个不同的键)。 同样,这将阻止关闭,直到必须重用文件句柄,但它会处理集群效率更高的情况(例如客户 1、2、3、7、1、2、3、2、2、3、 7,4,...)。
但基本理论保持不变:不要尝试一次打开 8000(或 40000)个文件,当你可以用更少的时间打开它。
或者,只需处理数据,将其全部存储到数据库中,然后使用查询创建具有一系列查询的每个文件。 这是否比上面的解决方案更快,应该进行测试,事实上,这里给出的每个建议都应该如此。 测量,不要猜测!
现在,既然我已经调用了优化咒语,让我们做一些计时,记住这特定于我的硬件,可能与你的不同。
从以下脚本开始,它会生成一个百万行的文件,其中每行的前八个字符是一个介于10000000
和10032767
之间的随机数。 我们将使用字符 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
生成的文件大小约为 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);
fclose(fIn);
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;
}
对于 100M 文件,这需要大约 2 秒(0:00:02),并使用 200M 和 400M 文件对其进行测试表明它是线性扩展的。 这意味着对于一个排序的 70G 文件,您会看到大约 1400 秒或 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 文件运行它时,需要 244 秒(0:04:04)。 同样,使用 200M 和 400M 文件进行测试表明线性缩放。 因此,对于70G的文件,这将是47:26:40,你会打电话算不上什么在你的分两个小时的排序和处理选项的改进。
但是,让我们尝试不同的策略,使用以下程序,每次通过输入文件维护一百个文件句柄(完成一百次):
#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;
}
这是一个细微的变化,它实际上处理输入文件一百次,每次只处理一百个单独的输出文件的行。
当在 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;
}
对于 100M 文件,这需要大约 12 秒,并且会给我们 2:20:00,接近排序但不完全在那里。
不幸的是,当我们进行下一个合乎逻辑的步骤时,尝试一次打开整个 10000 个文件,我们看到:
Error 24 opening '1020_out.dat'
这意味着我们终于达到了极限(标准输入、标准输出、标准错误和大约 1019 个其他文件句柄),这表明 1024 个句柄大约是我们所允许的全部。
所以也许排序和处理方法是最好的方法。
我不知道 Unix 平台上的限制,但是在 Windows 中,您可以使用 WINAPI 打开任意数量的文件,或者使用 _setMaxstdio 设置最大打开文件句柄数,默认情况下为 512(使用 fopen)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.