将大量数据保存到文件的最快方法

5
我在Java、C#和C++中进行一些数值计算。其中一些会保存大量数据(到文本文件)。最快的方法是什么? C++。
ofstream file;
file.open(plik);
for(int i=0;i<251;i++){
    for(int j=0;j<81;j++)
        file<<(i-100)*0.01<<" "<<(j-40)*0.01<<" "<<U[i][j]<<endl;
    file<<endl;
}

我猜这很快(我对吗?:)) Java
void SaveOutput(double[][] U, String fileName) throws IOException
{
    PrintWriter tx = new PrintWriter(new FileWriter(fileName));
    for(int i=0;i<251;i++)
    {
        for(int j=0;j<81;j++)
        {
            tx.println(String.format("%e %e %e ",(i - 100) * dz, (j - 40) * dz, U[i][j]));
        }
        tx.println();
    }
    tx.close();
}

这个 C# 的例子很相似。

但我有些困惑。我为每一行创建了一个 String 对象(会产生很多垃圾)。在这个例子中,不是那么多,但有时我有 1000 万行。这让我产生了以下问题:

  1. C++ 的示例能更快吗?
  2. 我应该使用 Java 的 StringBuilder,还是因为行数太多也不好用?
  3. 还有其他方法或库吗?
  4. C# 呢?

谢谢!


2
只是一个小技巧,使用 '\n' 而不是 endl,因为 endl 强制流刷新每一行。 - Inverse
必须是文本文件吗?以二进制格式保存可能会显著加快速度。您正在运行哪个操作系统?可能有特定于操作系统的功能(例如Windows中的重叠I/O),可以加快速度。 - Guy Sirton
是的,必须这样做。我使用gnuplot,需要查看文件等。 - Lukasz Madon
不要使用 file<<endl; 这会强制刷新文件缓冲区到磁盘上。让缓冲区在满时自行刷新。(注意:缓冲区的设计大小是为了在您的操作系统上高效运行(因此让它自己完成任务))。替换为 file << "\n"; 这将有效地减少写入次数,并以更大的块进行写入(瓶颈可能是写入块的数量而不是字节数,因此尽可能使块更大)。 - Martin York
8个回答

5

对其进行分析。运行代码,计时,看需要多长时间。如果所需时间可接受,就使用它。否则,找出哪个部分花费了很长时间来运行,并对其进行优化。

  • 让它正确。
  • 让它快速。

按照这个顺序。(有些人在这两个之前加上“让它运行/构建”...)

话虽如此,我以前实际上运行过这种度量。简单来说:你正在等待磁盘,而磁盘非常慢。不管你是用C、C ++还是Java编写,它们都在等待硬盘。

这里有一个我之前在C中做的关于各种I/O方法的帖子。不完全是你要找的,但可能会有启发。


1
性能分析对于I/O密集型程序来说很困难。 - Diego Sevilla
这个程序总是可以接受的,因为我可以在晚上运行它。我正在寻找一些提示或模式,就像当我开始使用Java编程时,我使用了类似tx.println(val1 + " " + val2 + " " +等的东西。这有点愚蠢,并且会使我的计算变慢。 - Lukasz Madon
@lukas 确实吗?你有现代 javac 构建中 a + b + c 比 StringBuilder(对于一些理性长度的 a、b 和 c)慢的基准测试吗? - user166390
1
I/O绑定应用程序的真正问题在于主要时间是写入磁盘操作。处理器大部分时间处于空闲状态,那么该如何进行性能分析呢? - Diego Sevilla
不仅仅是3个值,而是更多(记不清大概20个),它会在每次循环中创建一个新的StringBuilder对象。链接:http://chaoticjava.com/posts/stringbuilder-vs-string/ 顺便说一下,你知道997(我的积分)是最高的三位数质数吗?:p - Lukasz Madon
@Diego 但是有些问题涉及到字符串,如果有人不知道字符串是不可变的,就会出现问题。http://www.yoda.arachsys.com/csharp/stringbuilder.html - Lukasz Madon

4

一个词:档案。

需要注意的是,在缓冲(文件)流中插入std::endl会导致它刷新,这可能会降低性能(从语言的角度来看,这意味着缓冲区被写出,尽管这可能不一定意味着物理磁盘访问)。如果只是打印换行符,请使用'\n'——这永远不会更差。


2

首先,最重要的是:使用缓冲写入器!

这可能包括在某些语言中启用通道缓冲或使用类似于Java中的BufferedWriter的等效工具。如果不这样做,可能会导致性能大幅下降,因为输出流可能会被“过度刷新”--上面的示例代码违反了这一点(FileWriter不知道缓冲)!

在许多情况下,可以考虑CPU和主内存访问“便宜”,而IO“昂贵”--在这种微不足道的情况下,改进对IO本身的访问(例如缓冲和不[过度]刷新)将产生最明显的收益。现代VM和JIT非常擅长它们所做的事情,并且短暂对象的分配/释放可能是最不必要的“担忧”。


1
使用Java.nio类来创建通道。通道是Java中新的功能,比旧的流速度更快。您还应该缓冲写入。我不记得通道是否默认缓冲。我需要读一些才能告诉你。
最后,如果您创建了大量字符串并立即抛弃它们,那没关系。我怀疑这不会使您的磁盘写入变慢。磁盘IO比CPU慢得多。
这是我所想的:
fileChannel = new FileOutputStream("test.txt").getChannel();
for(int i=0;i<251;i++) {
  for(int j=0;j<81;j++) {
    fileChannel.write(ByteBuffer.wrap((String.format("%e %e %e ",(i - 100) * dz, (j - 40) * dz, U[i][j]) + "\n").toBytes());
  }
fileChannel.close();

哎呀,忘记加上 toBytes() 了 :) - Amir Raminfar
1
据我所知,除非需要使用选择器(非阻塞IO)、mmap或其他“额外”功能,否则真的没有理由使用NIO。对于阻塞读/写操作来说,“旧”的Java IO API与NIO一样快,而多线程IO并不比选择器/nb IO慢(总是)。 - user166390
这并不总是正确的。根据操作系统,使用nio可能会更快。https://dev59.com/ZXI-5IYBdhLWcg3w8NSB - Amir Raminfar

1
首先请注意,这个I/O绑定的程序不会因为小细节的改变(例如使用C++流或printf)而有很大的改善。
对于C/C++部分,一些人认为使用旧的printf操作更快。它可能更快,但不是数量级的差异,所以我不会费心去做。
至于Java版本,我认为它已经非常优化了。
至于C#,我不能说,我的医生不允许我:)

0

我认为在C或C++中使用fprintf会更快。


@JimR,那就是我的想法。不过,简单的答案是试一试。 - user325117
我尝试过了。我建立了一个报告生成器,项目经理坚持我们使用“适当的C ++特性”,但是在这种情况下,C ++流明显较慢。那是大约在2002年左右,事情可能已经改变了,但是... - JimR
每个 << 都是一种重载,其中类型由编译器检查。每个 %d 都在运行时解析,任何错误都是未定义行为。我期望 fprintf 编译速度更快,但 operator<<(ostream&, int) 更快。另外,它足够简单,可以内联。fprintf 可能有 2KB 的代码。 - MSalters

0

Lukas,

首先,我主要使用C#,所以这里的所有内容都与.NET有关。

考虑到你正在处理的行数,我不会创建字符串或使用StringBuilder。 StringBuilder只有在从许多较小的段创建字符串时才有帮助。

我认为你最好使用文件系统对象的流版本。这样,你根本不需要存储字符串,因此你的内存使用量应该相当小。

此外,如果你的内存真的很少,你可以创建一个非托管字符串并P/Invoke进去。

Erick


0

至于Java,您无需创建所有这些字符串。摆脱 String.format 并直接写入字节。

使用nio并毫不留情地进行分析


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接