非缓冲输出速度非常慢

12

我使用https://dev59.com/8HNA5IYBdhLWcg3wfN2O#13456219中提到的方法从数据库生成一个非常大的 .csv 文件。

这个方法在某个程度上运行得很好。但当导出文件太大时,会出现OutOfMemoryException错误。

如果我通过修改代码来关闭输出缓冲区,就可以解决这个问题:

protected override void WriteFile(System.Web.HttpResponseBase response)
{
    response.BufferOutput = false; // <--- Added this
    this.Content(response.OutputStream);
}

文件下载完成,但是与启用输出缓冲时相比速度要慢几个数量级(在本地主机上对启用/禁用缓冲的同一文件进行了测量)。

我知道它慢了,但为什么会变得如此之慢?有什么方法可以提高处理速度吗?

更新

根据评论中的建议,使用File(Stream stream, String contentType)也是一种选择。然而,我不知道如何创建stream。数据是基于数据库查询动态组装的,而MemoryStream将耗尽连续的物理内存。欢迎提供建议。

更新2

评论中建议交替从数据库读取和写入流会导致性能下降。我修改了代码,将流写入操作放到一个单独的线程中(使用生产者/消费者模式)。但是性能没有明显的改善。


我不明白为什么你需要那个链接的答案。从你的操作中返回 File(myStream, "text/csv") 不好吗?http://msdn.microsoft.com/en-us/library/dd493017(v=vs.100).aspx - ta.speot.is
我是否理解错了?你将响应输出流作为输入传递给Content?然后再次写入响应输出流?你重写的是哪个类的WriteFile方法? - Erik Funkenbusch
@ErikFunkenbusch:被覆盖的方法来自于FileResult。Content()是一个Action<Stream>,它允许我提供一个按照结构化方式写入到response.OutputStream的方法。http://msdn.microsoft.com/en-us/library/system.web.mvc.fileresult.writefile(v=vs.118).aspx - Eric J.
如果启用输出缓存,服务器需要向客户端发送新块时会从缓存中获取,这非常快速,因此速度会比较慢。例如,如果发送一个块需要100毫秒,获取下一个块只需要1毫秒,那么就是101毫秒+101毫秒+...等等。如果没有缓存,则从数据库读取下一个块可能需要100毫秒,因此现在发送块需要100毫秒,获取下一个块也需要100毫秒,所以是200毫秒+200毫秒+...等等。输出缓存有效地让您使用“发送时间”来预读,而未缓存则会阻塞并按需读取。 - Erik Funkenbusch
你可以实现一种机制,在其中你只需“足够”提前读取,以保持缓冲区充满...但这可能是你需要根据数据来解决的问题。也许你可以使用即时可枚举对象来始终准备好下一个块... - Erik Funkenbusch
显示剩余5条评论
2个回答

9
我不知道 ASP.NET 和 IIS 具体是如何处理输出流的,但可能使用了太小的块。使用一个带有非常大缓冲区(如 4MB)的 BufferedStream。
根据您的评论,它起作用了。现在,调整缓冲区大小以节省内存并减少工作集。这对缓存很有好处。
作为主观评论,我感到失望,甚至需要这样做。IIS 应该自动使用正确的缓冲区,在 TCP 连接中极其容易。
编辑自 OP 的代码:
public ActionResult Export()
{
    // Domain specific stuff here
    return new FileGeneratingResult("MyFile.txt", "text/text",
            stream => this.StreamExport(stream), false);
}

private void StreamExport(Stream stream)
{
    using (BufferedStream bs = new BufferedStream(stream, 256*1024))
    using (StreamWriter sw = new StreamWriter(bs))  
    foreach (var stuff in MyData())
    {
        sw.Write(stuff);
    }
}

性能提升达到了100倍的数量级。我也很惊讶需要外部的“BufferedStream”。 - Eric J.
1
一个256KB的缓冲区在主观上与一个4MB的缓冲区速度大致相同。由于这是一个很少使用的功能,我不会进一步进行调整。任何处理大量此类请求的读者都应该测试较小的缓冲区以找到良好性能和小内存占用的最佳缓冲区大小。 - Eric J.
@EricJ。我知道这已经有一段时间了,但我遇到了类似的问题,我发现这很有启发性。您能否更新您的问题以显示您如何使用BufferedStream?我对如何连接它感到困惑。谢谢。 - LoJo
@LoJo:根据这个答案,我创建了一个简化版本的代码,并将其粘贴在这里。 - Eric J.
1
我通过在写入器上放置缓冲区进一步提高了性能,生成了一个非常大的csv文件(约11GB)。我只是假设当我在读取器上放置缓冲区时,由于它也是约11GB,我将进一步提高性能。谢谢! - interesting-name-here

0
在Eric的最新更新中,他提到了使用另一个线程。我也曾经遇到过这个问题,用于实现数据库导出。以下是我使用的解决方案的示例代码:

处理临时文件流


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