为什么iText7 for .NET使用(更多)内存比iTextSharp5多?

3
我正在使用itext在.NET环境下生成PDF。为了优化执行时间,我正在从itextsharp 5.5.13迁移到.NET的itext 7.1.1。
生成的PDF主要由图像组成。我使用多线程并行生成文档。
itext7似乎更快,但内存使用量更高。由于我同时生成多个文档,因此会出现内存不足的情况。
我运行了一个简单的测试,使用相同的输入数据,输出文件大小为5MB。以下是两个版本库的代码。我的代码有什么问题吗?
itextsharp 5:
时间:1:18,RAM:峰值173MB,然后稳定在65MB左右。
public string GenerateImagesReport(IEnumerable<IChartData> data, string basename)
    {
        var doc = PdfUtility.CreateDoc();

        string path = Shared.BuildPdfPath(basename);
        using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
        {
            PdfWriter writer = PdfWriter.GetInstance(doc, fs);

            float left = 30f;
            float bottom = PdfUtility.GetYPosition(ReportElem.Chart2);
            float width = PdfUtility.CHART_WIDTH;
            float heigth = PdfUtility.CHART_hEIGTH * 2 + PdfUtility.V_SPACE1 + PdfUtility.V_SPACE2 + PdfUtility.GROUP_BY;

            doc.NewPage();
            doc.Open();
            PdfTemplate ImageTemplate;
            PdfContentByte cb = writer.DirectContent;
            Image img;
            foreach (var chart in data)
            {
                // chart image
                ImageTemplate = cb.CreateTemplate(width, heigth);
                img = Image.GetInstance(chart.ImageBytes, true);
                img.ScaleAbsolute(width, heigth);
                img.SetAbsolutePosition(0, 0);
                ImageTemplate.AddImage(img);

                cb.AddTemplate(ImageTemplate, left, bottom);

                chart.DestroyImage();
                doc.NewPage();
            }
            doc.Close();
        }

        return path;
    }

itext 7

时间:1:09,RAM:峰值753MB,持续到结束

public string GenerateImagesReport(IEnumerable<IChartData> data, string basename)
    {
        string path = Shared.BuildPdfPath(basename);
        using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
        {
            PdfWriter writer = new PdfWriter(fs);
            var pdf = new PdfDocument(writer);
            var pageSize = PageSize.LETTER;
            var doc = new Document(pdf, pageSize);

            float left = 30f;
            float bottom = PdfUtility.GetYPosition(ReportElem.Chart2);
            float width = PdfUtility.CHART_WIDTH;
            float heigth = PdfUtility.CHART_hEIGTH * 2 + PdfUtility.V_SPACE1 + PdfUtility.V_SPACE2 + PdfUtility.GROUP_BY;

            PdfPage page;
            PdfCanvas canvas;
            ImageData imgd;
            Image img;
            page = pdf.AddNewPage();
            foreach (var chart in data)
            {
                canvas = new PdfCanvas(page, true);

                imgd = ImageDataFactory.Create(chart.ImageBytes);
                img = new Image(imgd, left, bottom);
                img.ScaleAbsolute(width, heigth);

                new Canvas(canvas, pdf, pageSize)
                    .Add(img);

                chart.DestroyImage();
                page = pdf.AddNewPage();
            }

            doc.Close();
        }

        return path;
    }

更新

我正在使用Visual Studio Profiler监控内存使用情况。根据Yaroslav Veremenko的建议,我在内存使用方面看到了一些改进。不同的图表标记了生成PDF实际开始处理的时间。

使用itextsharp 0:43 itextsharp memory usage

使用itext7 0:26 itext7 memory usage

使用itext7 - page.Flush() 0:42 itext7 memory usage 2


1
iText QA在此。我不同意关闭投票。问题非常清晰,包含代码示例。答案也非常高质量。我将标记该问题为收藏,并稍后回来查看。作为一个Linux人,我对Windows中的性能监视没有太多经验,但我会将代码移植到Java并查看它在Linux上的表现。我可能会在评论中提出其他问题。 - Amedee Van Gasse
第一个问题:你是如何准确测量内存使用情况的? - Amedee Van Gasse
如果这个问题被关闭,请让我知道,以便我可以投票重新开放它。我同意Amedee的评论:好问题。 - Bruno Lowagie
我将代码移植到Java,内存消耗大致相同。对于一个每页有两张图片的500页文档,内存消耗超过1000MB。@AmedeeVanGasse - sillo01
我还没有时间做这件事,但我没有忘记它。 - Amedee Van Gasse
2个回答

4
我不熟悉这个库,但可能是在使用后没有销毁PdfCanvasCanvas对象,导致它们停留在内存中,直到文档被销毁。根据文档,您必须在绘制图表后释放内存。

确保在完成对画布的写入后调用PdfCanvas.release()。

来源:https://github.com/itext/itext7-dotnet/blob/dd5c209cff35c137ed451fef6e11a96889a52fe9/itext/itext.kernel/pdf/canvas/PdfCanvas.cs#L69 更新 我刚刚在本地运行了它。在我的示例中,峰值为500MB。之后我添加了:
page.Flush(true);

它下降到了250MB。
参考:http://itextsupport.com/apidocs/itext7/7.0.2/com/itextpdf/kernel/pdf/PdfPage.html#flush-boolean- 更新2:
使用page.Flush(true)的内存使用情况。
    foreach (var chart in Enumerable.Range(0, 10))
    {
        canvas = new PdfCanvas(page, true);
        imgd = ImageDataFactory.Create((byte[])converter.ConvertTo(data, typeof(byte[])));
        img = new iText.Layout.Element.Image(imgd, left, bottom);
        img.ScaleAbsolute(width, heigth);
        new Canvas(canvas, pdf, pageSize)
            .Add(img);
        // this line has been added
        page.Flush(true);
        page = pdf.AddNewPage();
    }

graph


不起作用。尝试了但没有任何区别:bool immediateFlush = true; new Canvas(canvas, pdf, pageSize, immediateFlush) .Add(img); - sillo01
@sillo01,请告诉我它是否适用于您,我现在有点好奇。 - Yaroslav Veremenko
内存使用量看起来与添加的页面/图片成比例增加。更新后我会告诉你结果。 - sillo01
你是如何准确地测量内存使用情况的? - Amedee Van Gasse
1
@AmedeeVanGasse 我正在使用 dotMemory。 - Yaroslav Veremenko

1

最重要的是将 PdfWriter 包装在 using 中,因为它实现了 IDisposable 接口,并且在并行使用时应该有很大帮助。我还删除了对 Canvas 的实例化,因为当您现有的 PdfCanvas 对象可以完成相同的工作时,这似乎是不必要和浪费的。此外,我将您的字段移动到 Foreach 作用域中,以便它们更有可能被 GC 收集。

using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
    using (PdfWriter writer = new PdfWriter(fs)) //**Implements IDisposable - This should help hugely when used in Parallel**
    {
         var pdf = new PdfDocument(writer);
         var pageSize = PageSize.LETTER;
         var document = new Document(pdf);

          foreach (var chart in data)
          {
             var page = pdf.AddNewPage(); //When it get's reassigned on the next iteration, Garbage collection will take over
             PdfCanvas canvas = new PdfCanvas(page, true); //When it get's reassigned on the next iteration, Garbage collection will take over
             canvas.AddImage(ImageDataFactory.Create(chart.ImageBytes), pageSize, false); //1x Less Object in Memory but you will need to play around with params for precision.
             chart.DestroyImage();
          }

          document.Close();
    }
}

1
我仍然认为根据文档应该手动释放PdfCanvas,即使GC稍后可以完成它。无论如何,这样做不会有任何损害。根据我的回答,最大的内存使用是将整个文档保留在内存中。通过page.Flush(true)将页面刷新到磁盘后,内存使用量至少减少了一半。 - Yaroslav Veremenko
你是指 PdfCanvasRelease() 吗?如果是的话,那么它可能会有所帮助...我最担心的是实例化新对象 (Canvas) 时会重复内存,以及没有调用 PdfWriter 上的 Dispose(),而 PdfWriter 则持有整个文档。这一点应该会带来巨大的节省。 - Kitson88
使用 page.Flush(true) 可以显著改善内存使用情况。然而,对我来说 itextsharp 更好。 - sillo01

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