【问题标题】:Unbuffered Output Very Slow无缓冲输出非常慢
【发布时间】:2014-09-24 07:22:23
【问题描述】:

我使用中概述的方法从数据库生成一个非常大的 .csv 文件

https://stackoverflow.com/a/13456219/141172

在一定程度上它工作正常。当导出的文件太大时,我得到一个OutOfMemoryException

如果我通过这样修改代码来关闭输出缓冲:

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

文件下载完成。但是,它比启用输出缓冲时慢了几个数量级(在本地主机上针对具有缓冲真/假的同一文件测量)。

我知道这比较慢,但为什么它会慢到相对爬行?我可以做些什么来提高处理速度?

更新

按照 cmets 的建议,也可以选择使用 File(Stream stream, String contentType)。但是,我不确定如何创建stream。数据是基于 DB 查询动态组装的,MemoryStream 将耗尽连续的物理内存。欢迎提出建议。

更新 2

在 cmets 中建议交替从数据库读取和写入流会导致降级。我修改了代码以在单独的线程中执行流写入(使用生产者/消费者模式)。性能没有明显差异。

【问题讨论】:

  • 我不明白你为什么需要那个链接的答案。从您的操作中简单地返回 File(myStream, "text/csv") 有什么问题? msdn.microsoft.com/en-us/library/dd493017(v=vs.100).aspx
  • 我读错了吗?您将响应输出流作为输入传递给 Content?然后哪个再次写入响应输出流?您要覆盖哪个类的 WriteFile 方法?
  • @ta.speot.is:我不确定如何创建 myStream。数据是基于 DB 查询动态组装的,MemoryStream 将耗尽连续的物理内存。你能展示一个模式来创建一个可以传递给File(myStream, "text/csv")myStream吗?
  • @ErikFunkenbusch:被覆盖的方法来自 FileResult。 Content() 是一个Action&lt;Stream&gt;,允许我提供一种以结构化方式写入response.OutputStream 的方法。 msdn.microsoft.com/en-us/library/…
  • 嗯,它会很慢,因为在输出缓存打开的情况下,当服务器需要一个新块发送给客户端时,它会从缓存中获取它,这非常快。因此,如果发送一个块需要 100 毫秒,获取下一个块需要 1 毫秒,这是 101 毫秒 + 101 毫秒 +.. 等等。如果没有缓存,从数据库读取下一个块可能需要 100 毫秒,所以现在发送需要 100 毫秒块和 100 毫秒来获取下一个块,所以它是 200 毫秒 + 200 毫秒 + ......等等......输出缓存有效地让您使用“发送时间”提前读取,而未缓存它会阻塞并按需读取。跨度>

标签: asp.net-mvc asp.net-mvc-4


【解决方案1】:

我不知道 ASP.NET 和 IIS 对输出流的确切作用,但可能使用的块太小了。使用非常大的缓冲区(例如 4MB)挂接 BufferedStream

根据您的 cmets 它有效。现在,调低缓冲区大小以节省内存并拥有更小的工作集。适合缓存。

作为一个主观评论,我很失望这甚至是必要的。 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
  • 256KB 的缓冲区在主观上与 4MB 的缓冲区一样快。由于这是一个很少使用的功能,我不打算进一步调整。阅读这篇处理大量此类请求的任何人都最好测试较小的缓冲区,以找到最佳的缓冲区大小,以获得良好的性能和较小的内存占用。
  • @EricJ。我意识到这是不久前的事了,但我遇到了类似的问题,我发现这很有启发性。您能否更新您的问题以显示您如何使用BufferedStream?我对如何连接它感到困惑。谢谢。
  • @LoJo:粘贴在我根据这个答案创建的代码的简化版本中。
  • 我通过在写入器上放置一个缓冲区进一步提高了性能。生成一个非常大的 csv 文件(~11GB)。我只是假设当我把它放在我的阅读器上时,我会进一步提高性能,因为它也是 ~11GB。谢谢!
【解决方案2】:

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

Handling with temporary file stream

【讨论】:

    猜你喜欢
    • 2013-01-30
    • 2012-02-08
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 2014-05-10
    • 2010-10-24
    • 1970-01-01
    • 2023-01-24
    相关资源
    最近更新 更多