QuestPDF / QuestPDF

QuestPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API. Easily generate PDF reports, invoices, exports, etc.
https://www.questpdf.com
Other
11.65k stars 603 forks source link

Potential performance regression for Images #583

Closed MarcinZiabek closed 1 year ago

MarcinZiabek commented 1 year ago

Another thing we noticed is that UseOrignalImage() was 5x slower pr PDF:

snip kjfTrvd6@2x

Originally posted by @johnkors in https://github.com/QuestPDF/QuestPDF/issues/571#issuecomment-1573240098

MarcinZiabek commented 1 year ago

@johnkors I have tried to validate the performance problem:

I used this repository and modified it to use an actual image as a logo. Its resolution is 900x300 pixels, JPEG format, 64 KB.

image

I am performing 10 runs, each generating 1000 PDF documents.

Results:

  1. With version 2022.12.6, I have got the following running times (in ms): 26998, 26721, 26762, 26751, 26734, 26750, 26730, 27143, 27826, 27788
  2. With version 2023.5.2: 31440, 31120, 31153, 31334, 32118, 31500, 31402, 31485, 31759, 32401. A slight increase is expected as the new version introduces image compression and resolution scaling, so to reduce PDF file size
  3. With version 2023.5.2 but with image loaded only once and cached: 27166, 27243, 27181, 26786, 27138, 27092, 27084, 27118, 26986, 27136. These results are identical with the 2022.12.6 release.
  4. With version 2023.5.2 but with the UseOriginalImage option: 27869, 26876, 27778, 27986, 27730, 27258, 27271, 27591, 27574, 27419.

I did not observe any significant performance regression, as on your chart. What are the memory constraints on your testing machine? How much RAM does it have? What garbage collector does it use?

johnkors commented 1 year ago

Would be useful if you could share how you used the image APIs and how it differs from #571, as it's not part of the repo you linked to.

Things I'd check:

From my brief testing, the UseOriginalImage() was the culprit when it came to the crashes , but it introduced a perf regression.

I'll check if image size can reduce the regression in time spent to produce the pdf.

Setup:

MarcinZiabek commented 1 year ago

What is the resolution of your image?

MarcinZiabek commented 1 year ago

@johnkors Great news! 😁

I reproduced the issue using a specifically crafted PNG file: something of a very high resolution that compresses down to a tiny file (e.g., an empty image). I suppose that you have experienced the issue due to a significantly lower amount of RAM in the Azure App Service, especially compared to a typical development machine.

Then, I decided to try different image scaling algorithms using SkiaSharp API. One of them resolves the issues. I have published a new 2023.5.3 release that hopefully will solve performance and termination problems.

Also, I have integrated an example of how to utilize the new Image API to cache shared images across documents and therefore achieve lower memory pressure with higher performance. Please take a look here.

Thank you so much for your patience and helping me resolve the problem. Would you please try the new version and confirm?

johnkors commented 1 year ago

Nice! This looks better after I

Now we see PDFs generated on average in ~100ms, previoysly well above 1s.

I've monitored the memory/cpu footprint between runs, and it was nowhere near it's ceiling - so I don't think that's the underlying issue (at least for us). We only see a spike in CPU. But again, it's not really that heavy of a load as compared to what you ran in your concurrent test.

snip pxQ5DM5x

johnkors commented 1 year ago

Anyhow, I think the QuestPDFs could do with some details under the "Working with images" section detailing some of these findings and pitfalls 👍

MarcinZiabek commented 1 year ago

That's fantastic! I'm glad it is finally fixed. Thank you so much for your help.

Also, I made a note to improve this part of the documentation. As soon as I finish adjusting the license details (to make it more user-friendly), I will do this enhancement 😁