【问题标题】:how to effectively write text file in C and the size of file would be 5GB如何有效地用 C 编写文本文件,文件大小为 5GB
【发布时间】:2015-06-12 12:24:26
【问题描述】:
FILE *fp;
fp = fopen(pch, "wb");
while(1)
fwrite(p, sizeof(char), strlen(p) / sizeof(char), fp);

我使用这些代码写入txt文件,但是当输出文件很大时(它的大小可能会增长到5GB)会很慢,我需要等待很长时间。 谁能告诉我一个更好的写入txt文件的方法?

编辑: p 是一个 const char * 变量。我可能需要在电脑上等待一小时。我只是检查一下 txt 文件的大小是否增长到 20GB

while (!done)    
    {
        const char *p = icmDisassemble(processor[i], currentPC);
        fwrite(p, sizeof(char), strlen(p) / sizeof(char), fp);
        fwrite("\n", sizeof(char), 1, fp);
        done = (icmSimulate(processor[i], 1) != ICM_SR_SCHED);
    }

【问题讨论】:

  • need to wait a long time => 秒?分钟?小时?固态硬盘?硬盘? USB?网络? p 是什么?
  • 请注意,sizeof(char) 是多余的(根据定义它是 1) - 所以更改:fwrite(p, sizeof(char), strlen(p) / sizeof(char), fp);fwrite(p, 1, strlen(p), fp);
  • 哪个平台?你在哪里写?
  • 您的系统使用的是哪个文件系统?
  • 什么是p?换句话说,每次fwrite() 调用要写入多少字节?

标签: c io


【解决方案1】:

我已经测试了你的代码的 null 实现,看看 fwrite 的性能如何,我相信瓶颈肯定不是 fwrite

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

char *icmDisassemble(int x, int y) {
        return "The rain in Spain falls mostly on the plain";
        // return "D'oh";
        // return "Quel ramo del lago di Como, che volge a mezzogiorno, tra due catene non interrotte di monti, tutto a seni e a golfi, a seconda dello sporgere e del rientrare di quelli, vien, quasi a un tratto, a ristringersi, e a prender corso e figura di fiume, tra un promontorio a destra, e un’ampia costiera dall’altra parte; e il ponte, che ivi congiunge le due rive, par che renda ancor più sensibile all’occhio questa trasformazione, e segni il punto in cui il lago cessa, e l’Adda rincomincia, per ripigliar poi nome di lago dove le rive, allontanandosi di nuovo, lascian l’acqua distendersi e rallentarsi in nuovi golfi e in nuovi seni.";
}

#define ICM_SR_SCHED 0

int icmSimulate(int x, int y) {
        return ICM_SR_SCHED;
}

int main() {
    int done;
    int i = 0;
    int processor[1] = { 0 };
    int currentPC = 0;

    FILE *fp;

    fp = fopen("test.dat", "w");

    while (!done)
    {
        const char *p = icmDisassemble(processor[i], currentPC);
        fwrite(p, sizeof(char), strlen(p) / sizeof(char), fp);
        fwrite("\n", sizeof(char), 1, fp);
        done = (icmSimulate(processor[i], 1) != ICM_SR_SCHED);
    }
}

测试结果

touch test.dat; ( ./testx & ); for i in $( seq 1 7 ); do \
    date | tr "\n" "\t"; du -sh test.dat; \
    sleep 10; done; \
killall -KILL testx; rm -f test.dat

台式机 SATA 磁盘(非 SSD)的写入速度介于 50 到 60 MB/s 之间,即每分钟 2 Gb。这比dd 慢了大约 50%,或相同数量级:

time dd if=/dev/zero of=test.dat bs=1M count=5300
5300+0 records in
5300+0 records out
5557452800 bytes (5.6 GB) copied, 61.5375 s, 90.3 MB/s

real    1m2.105s
user    0m0.000s
sys     0m9.544s

我的硬件时钟持续保持在 100 MB/s 左右,因此 90.3 MB/s 是一个可信的数字(现在正在使用该系统,可能会减慢一点)。

更改字符串长度不会显着改变次数:

// "D'oh"

Fri Jun 12 19:36:50 CEST 2015   1.5M    test.dat
Fri Jun 12 19:37:00 CEST 2015   751M    test.dat
Fri Jun 12 19:37:10 CEST 2015   1.5G    test.dat
Fri Jun 12 19:37:20 CEST 2015   2.2G    test.dat
Fri Jun 12 19:37:31 CEST 2015   2.9G    test.dat
Fri Jun 12 19:37:41 CEST 2015   3.6G    test.dat
Fri Jun 12 19:37:51 CEST 2015   4.4G    test.dat

// First lengthy sentence of *I Promessi Sposi*

Fri Jun 12 19:39:42 CEST 2015   8.4M    test.dat
Fri Jun 12 19:39:52 CEST 2015   1.2G    test.dat
Fri Jun 12 19:40:02 CEST 2015   2.1G    test.dat
Fri Jun 12 19:40:14 CEST 2015   3.1G    test.dat
Fri Jun 12 19:40:25 CEST 2015   4.0G    test.dat
Fri Jun 12 19:40:35 CEST 2015   4.8G    test.dat
Fri Jun 12 19:40:45 CEST 2015   5.7G    test.dat

// "The rain in Spain"

Fri Jun 12 19:41:21 CEST 2015   7.3M    test.dat
Fri Jun 12 19:41:31 CEST 2015   1.2G    test.dat
Fri Jun 12 19:41:43 CEST 2015   2.1G    test.dat
Fri Jun 12 19:41:53 CEST 2015   3.0G    test.dat
Fri Jun 12 19:42:03 CEST 2015   3.9G    test.dat
Fri Jun 12 19:42:13 CEST 2015   4.6G    test.dat
Fri Jun 12 19:42:23 CEST 2015   5.3G    test.dat

那么瓶颈在哪里?

我真的看到很少的选择。

  • 它在磁盘上。尝试在您的计算机上运行我的代码几分钟。它应该写入大约 10 GB 的垃圾。显着降低的数字可能表明磁盘设置、文件系统甚至物理支持或接口硬件存在问题。

  • 它是 icmDisassemble。你说不是,但我们假设它经常返回零长度字符串。通过返回 "",我得到了更差的性能:1.5 Gb/分钟而不是 4-5。

在后一种情况下,您可以尝试计算得到的行长:

tr -c "\n" "." < YourLargeOutputFile | sort | uniq -c

这是随机文件中strings 的结果,显示大多数行只有四个字节长(如预期的那样):

  10931 ....
   4319 .....
   1680 ......
    629 .......
    288 ........
    142 .........
     54 ..........
     21 ...........
     18 ............
      6 .............
      3 ..............
      4 ...............
      3 ................
      1 .................

如果您看到大量的零长度行,则可以这样做:

  const char *p = icmDisassemble(processor[i], currentPC);
  // Ignore zero-length output.
  if (p[0]) {
    fwrite(p, sizeof(char), strlen(p) / sizeof(char), fp);
    fwrite("\n", sizeof(char), 1, fp);
  }

另一种可能性是尝试使用更大的缓冲区。使用 64K 缓冲区,这应该绰绰有余,即使编写零长度字符串和回车,我也能再次获得正常性能:

Fri Jun 12 20:03:15 CEST 2015   6.5M    test.dat
Fri Jun 12 20:03:25 CEST 2015   1.3G    test.dat
Fri Jun 12 20:03:35 CEST 2015   2.1G    test.dat
Fri Jun 12 20:03:45 CEST 2015   3.0G    test.dat
Fri Jun 12 20:03:56 CEST 2015   3.7G    test.dat
Fri Jun 12 20:04:06 CEST 2015   4.3G    test.dat
Fri Jun 12 20:04:17 CEST 2015   5.2G    test.dat

这是修改后的代码(注意,缓冲区不是以零结尾的——“\n”覆盖了以零结尾的)。

#define ICM_BUF_LEN 0x10000
char *buffer = malloc(ICM_BUF_LEN);
size_t bufptr = 0;

while (!done)
{
    const char *p = icmDisassemble(processor[i], currentPC);
    if ((strlen(p) + bufptr + 1) >= ICM_BUF_LEN) {
            fwrite(buffer, 1, bufptr, fp);
            bufptr = 0;
    }
    strcpy(buffer + bufptr, p);
    bufptr += strlen(p);
    buffer[bufptr++] = '\n';
    done = (icmSimulate(processor[i], 1) != ICM_SR_SCHED);
}
fwrite(buffer, 1, bufptr, fp);
free(buffer); buffer = NULL;

保存strlen 调用(将第一个strlen 保存到变量中并使用memcpy)不会明显改变结果。在我的系统上,两倍大的缓冲区也无法带来任何好处。

  • 是icmDisassemble。请注意,不是总是,而是有时。也许在非常罕见的情况下,它会发现一些尴尬的数据并阻塞,或者失去很长时间来恢复或仔细检查或调用昂贵的功能。我们如何检查这个?您可以为函数计时 - 考虑到速度的数量级,我们只需要能够欣赏毫秒;有几个 sn-ps 可以做到这一点。

    int times[1000];
    for (j = 0; j < 1000; j++) { times[j] = 0; }
    
    while (!done)
    {
        size_t  s;
        int     ms1 = getTimeMilliseconds();
        const char *p = icmDisassemble(processor[i], currentPC);
        int     ms2 = getTimeMilliseconds() - ms1;
        if (ms2 > 999) {
            ms2 = 999;
        }
        times[ms2]++;
    

运行后,或者如果时钟超过了合适的运行时间,则将数组转储到标准输出,忽略零条目,并得到如下结果:

times
----
0         182493 <-- times obviously not zero, but still < 1 ms
1         9837
2         28
3         5
6         1
135       1 <---- two suspicious glitches (program preempted by kernel?)
337       1 <--
999       5 <-- on five occasions the function has stalled

如果是这种情况,您可以在 icmDisassemble 调用之后立即添加回溯部分,以检查时间并在诊断信息第一次超过合理限制时转储。

此外,比较 wall time 和 CPU time 可能会产生有价值的信息 - 例如揭示 其他东西 正在抢占您的程序,或者它花费大部分时间等待某事。

【讨论】:

    猜你喜欢
    • 2011-03-25
    • 1970-01-01
    • 2014-03-28
    • 2021-12-10
    • 1970-01-01
    • 2011-04-04
    • 2011-02-21
    • 2011-02-27
    • 2010-12-27
    相关资源
    最近更新 更多