【问题标题】:Opening and writing to multiple files in C在 C 中打开和写入多个文件
【发布时间】:2012-04-24 16:43:02
【问题描述】:

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

【问题讨论】:

  • 不能维护一个有序(按客户端)的文件索引,然后一个一个处理客户端吗?

标签: c unix file-descriptor


【解决方案1】:

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

就是这样。简单的。一次只打开一个文件,不会浪费时间排序。

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

  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

生成的文件大小约为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 的文件大约需要 2s (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 个句柄。

所以也许排序和处理方法最好的方法。

【讨论】:

  • 感谢您的努力。您为我们提供了很多解决方案以及它们如何与其他解决方案相互关联。不幸的是,目前使用的方法似乎是最好的。由于许可成本,我们不允许实施数据库解决方案,老年人也不允许使用免费数据库。再次感谢。
  • @BharatElwe,立即拒绝可能的解决方案的人是白痴(不是你,我说的是你的这些“前辈”)。但也许你必须成为像我一样干瘪的老山羊,然后才能公开称他们为白痴并逃脱惩罚:-)我至少会尝试 DB解决方案。如果您发现它可以在 27 分钟内完成与最快的非 DB 解决方案在两小时内完成的工作相同的工作,您可以将其展示给老年人并让他们做出决定。但至少您已经提出建议,如果他们随后选择忽略您的建议,那是他们的问题,而不是您的问题。
【解决方案2】:

我不知道 Unix 平台的限制,但是在 Windows 中,您可以使用 WINAPI 打开任意数量的文件,或者使用 _setMaxstdio 设置打开文件句柄的最大数量,默认情况下为 512(使用 fopen) .

Here's a similar solution which may help you out!

【讨论】:

    猜你喜欢
    • 2017-04-09
    • 1970-01-01
    • 1970-01-01
    • 2016-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多