Spire.PDF is a professional PDF library applied to creating, writing, editing, handling and reading PDF files without any external dependencies. Get free and professional technical support for Spire.PDF for .NET, Java, Android, C++, Python.

Tue May 02, 2023 10:59 am

Overview

We currently use Spire.PDF for Java v3.8.2 to create PDFs.
With this, we had some issues with multithreading.
So to evaluate, if the newest version of Spire.PDF for Java fixes this, I created some tests.
These tests have revealed 3 issues:

1. Creating a PDF with Spire.PDF for Java v9.4.9 is much slower than creating the same PDF with Spire.PDF for .Net v9.4.0.
2. Using multithreading to create PDFs is slower than creating the PDFs sequentially. (!)
3. Using multithreading quite regularily throws exceptions.

Below, I will explain these problems in more detail.
(You can find my test projects for .Net 6 and Java as attachments. All tests were run with a valid Spire.PDF license which obviously is not included here.)

1) Creating PDFs in Java is much slower than in .Net
If I create a simple PDF with multiple pages and just insert a text with a custom true type font, it takes way longer in Java.

.Net 6:
"PDF creation (2000 files à 20 pages) took 00:58 minutes"
"PDF creation (50 files à 2000 pages) took 02:16 minutes"

Code: Select all
public static void CreatePdf(string fileName)
{
    PdfDocument pdfDocument = new();

    for (int i = 0; i < numPages; i++)
    {
        PdfPageBase pdfPage = pdfDocument.Pages.Add(PdfPageSize.A4);
        pdfPage.Canvas.DrawString(
            loremIpsum,
            trueTypeFont,
            PdfBrushes.Black,
            new RectangleF(0, 0, pdfPage.ActualSize.Width, pdfPage.ActualSize.Height));
    }

    pdfDocument.SaveToFile(fileName);
    pdfDocument.Close();
}


Java:
"PDF creation (2000 files à 20 pages) took 2:20 minutes (140 seconds)"
"PDF creation (50 files à 2000 pages) took 6:4 minutes (364 seconds)"

Code: Select all
public static void createPdf(String fileName) {
    PdfDocument pdfDocument = new PdfDocument();

    for (int i = 0; i < numPages; i++) {
        PdfPageBase pdfPage = pdfDocument.getPages().add(PdfPageSize.A4);
        pdfPage.getCanvas().drawString(
            loremIpsum,
            trueTypeFont,
            PdfBrushes.getBlack(),
            new Rectangle(0, 0, (int)pdfPage.getActualSize().getWidth(), (int)pdfPage.getActualSize().getHeight()));
    }

    pdfDocument.saveToFile(fileName);
    pdfDocument.close();
}


Conclusion: Spire.PDF for Java takes 2.5x as long as the implementation in .Net does!



2) Multithreading is slower than using a single thread
If I run the above PDF-creation using multithreading, it increases performance in .Net, but actually makes it slower in Java.

.Net 6:
"PDF creation (2000 files à 20 pages) took 00:58 minutes"
"Parallel PDF creation (2000 files à 20 pages) took 00:44 minutes"

"PDF creation (50 files à 2000 pages) took 02:16 minutes"
"Parallel PDF creation (50 files à 2000 pages) took 01:42 minutes"

Code: Select all
Parallel.For(
        0,
        fileCount,
        i => Controller.CreatePdf($"Test_parallel{i}.pdf"));


Java:
"PDF creation (2000 files à 20 pages) took 2:20 minutes (140 seconds)"
"Parallel PDF creation (2000 files à 20 pages) took 3:46 minutes (226 seconds)"

"PDF creation (50 files à 2000 pages) took 6:4 minutes (364 seconds)"
"Parallel PDF creation (50 files à 2000 pages) took 9:30 minutes (570 seconds)"

Code: Select all
int processorCount = Runtime.getRuntime().availableProcessors();
ExecutorService executorService = Executors.newFixedThreadPool(processorCount + 1);
            
for (int i = 0; i < fileCount; i++) {
    final int number = i;
    executorService.submit(() -> Controller.createPdf(String.format("Test_parallel%d.pdf", number)));
}
            
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.HOURS);


Conclusion: Something is really wrong with Spire.PDF when using multiple threads!



3) Multithreading throws exceptions
When using large files, there doesn't seem to be an issue.
Multiple test runs were fine.
But when using small files which don't take long to create, exceptions are thrown regularily (on a Windows 10 computer).
These exceptions are not always the same, but always happen during saving and never during in-memory creation of the PDF.

Exception "Cannot access a disposed object":
Code: Select all
class com.spire.pdf.packages.sprmqu: Cannot access a disposed object.
   Object name: 'MemoryStream'.
   com.spire.pdf.packages.sprpqv.spr↡┽(MemoryStream.java:115)
   com.spire.pdf.packages.sprnno.spr〄®(Unknown Source)
   com.spire.pdf.packages.sprnno.spr⑫®(Unknown Source)
   com.spire.pdf.packages.sprzze.spr┝⌬(Unknown Source)
   com.spire.pdf.packages.sprzze.spr┛⌬(Unknown Source)
   com.spire.pdf.packages.sprzze.spr╼⌬(Unknown Source)
   com.spire.pdf.packages.sprhgf.spr▁∬(Unknown Source)
   com.spire.pdf.packages.sprnne.spr▁∬(Unknown Source)
   com.spire.pdf.packages.sprkze.spr╆⌫(Unknown Source)
   com.spire.pdf.packages.sprkze.spr□⌬(Unknown Source)
   com.spire.pdf.packages.sprrno.spr┶╻(Unknown Source)
   com.spire.pdf.packages.sprrno.spr┼╻(Unknown Source)
   com.spire.pdf.packages.sprrno.spr⌧╻(Unknown Source)
   com.spire.pdf.packages.sprrno.spr⑉╻(Unknown Source)
   com.spire.pdf.PdfNewDocument.spr┊¶(Unknown Source)
   com.spire.pdf.PdfDocumentBase.save(Unknown Source)
   com.spire.pdf.PdfDocument.saveToFile(Unknown Source)
   Controller.createPdf(Controller.java:26)
   Program.lambda$0(Program.java:35)
   java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
   java.util.concurrent.FutureTask.run(FutureTask.java:266)
   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
   java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
   java.lang.Thread.run(Thread.java:750)
      at com.spire.pdf.packages.sprpqv.spr↡┽(MemoryStream.java:115)
      at com.spire.pdf.packages.sprnno.spr〄®(Unknown Source)
      at com.spire.pdf.packages.sprnno.spr⑫®(Unknown Source)
      at com.spire.pdf.packages.sprzze.spr┝⌬(Unknown Source)
      at com.spire.pdf.packages.sprzze.spr┛⌬(Unknown Source)
      at com.spire.pdf.packages.sprzze.spr╼⌬(Unknown Source)
      at com.spire.pdf.packages.sprhgf.spr▁∬(Unknown Source)
      at com.spire.pdf.packages.sprnne.spr▁∬(Unknown Source)
      at com.spire.pdf.packages.sprkze.spr╆⌫(Unknown Source)
      at com.spire.pdf.packages.sprkze.spr□⌬(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr┶╻(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr┼╻(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr⌧╻(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr⑉╻(Unknown Source)
      at com.spire.pdf.PdfNewDocument.spr┊¶(Unknown Source)
      at com.spire.pdf.PdfDocumentBase.save(Unknown Source)
      at com.spire.pdf.PdfDocument.saveToFile(Unknown Source)
      at Controller.createPdf(Controller.java:26)
      at Program.lambda$0(Program.java:35)
      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
      at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      at java.lang.Thread.run(Thread.java:750)


Exception "ArrayIndexOutOfBoundsException":
Code: Select all
java.lang.ArrayIndexOutOfBoundsException
      at com.spire.pdf.packages.sprpqv.write(MemoryStream.java:389)
      at com.spire.pdf.packages.sprnno.spr〄®(Unknown Source)
      at com.spire.pdf.packages.sprzze.spr⅓⌫(Unknown Source)
      at com.spire.pdf.packages.sprzze.spr╖⌫(Unknown Source)
      at com.spire.pdf.packages.sprzze.spr⅟⌬(Unknown Source)
      at com.spire.pdf.packages.sprqaf.spr▁∬(Unknown Source)
      at com.spire.pdf.packages.sprnne.spr▁∬(Unknown Source)
      at com.spire.pdf.packages.sprkze.spr╆⌫(Unknown Source)
      at com.spire.pdf.packages.sprkze.spr□⌬(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr┶╻(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr┼╻(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr⌧╻(Unknown Source)
      at com.spire.pdf.packages.sprrno.spr⑉╻(Unknown Source)
      at com.spire.pdf.PdfNewDocument.spr┊¶(Unknown Source)
      at com.spire.pdf.PdfDocumentBase.save(Unknown Source)
      at com.spire.pdf.PdfDocument.saveToFile(Unknown Source)
      at Controller.createPdf(Controller.java:26)
      at Program.lambda$0(Program.java:35)
      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
      at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      at java.lang.Thread.run(Thread.java:750)


The result is, that some PDFs are simply corrupt and cannot be read while others can be read but still show an error, that something is not ok with them.

Conclusion: Spire.PDF for Java cannot be reliably used for multithreading!



All three of these issues together mean that Spire.PDF for Java is not very performant and cannot take advantage of multi-core CPUs that are standard in todays computers.
So, could you please investigate the following issues and provide a fix?

- High priority: Allow multithreading of PDF creation (no exceptions, performance gains over singlethreading)
- Medium-low priority: Increase generall PDF creation performance to match the one from Spire.PDF for .Net

RicoScheller
 
Posts: 52
Joined: Tue Jul 02, 2019 10:34 am

Wed May 03, 2023 10:04 am

Hi,

Thanks for your feedback.
For the first issue, I have tested the two projects you provided, found that the speed of .net 6 is about 1 time faster than in java.
For the second issue, I did find that using multithread is rather slower than single thread.
For the third issue, I reproduced this issue you mentioned, some exceptions were thrown during this process.

I will log the three issues into our issue tracking system when we return to office. Our developers will investigate these issues. Sorry for the inconvenience caused. Once there are any updates available, I will inform you asap.

Best regards,
Triste
E-iceblue support team
User avatar

Triste.Dai
 
Posts: 1000
Joined: Tue Nov 15, 2022 3:59 am

Thu Nov 09, 2023 3:56 pm

Hello

Has there been any progress on this?
We are currently evaluating our licenses and are questioning whether purchasing licenses for a new version would benefit us.

RicoScheller
 
Posts: 52
Joined: Tue Jul 02, 2019 10:34 am

Fri Nov 10, 2023 8:51 am

Hello,

Thank you for your follow-up.
After investigating by our development team, we have identified that the issue was caused by the "createPdf()" function in the code provided by you, which was declared as static.

We have made the necessary changes to the code and found that the performance has significantly improved, and there are no reported errors.
"PDF creation (2000 files à 20 pages) took 1:26 minutes (86 seconds)"
"Parallel PDF creation (2000 files à 20 pages) took 0:29 minutes (29 seconds)"
"PDF creation (50 files à 2000 pages) took 3:8 minutes (188 seconds)"
"Parallel PDF creation (50 files à 2000 pages) took 1:1 minutes (61 seconds)"

I have attached the modified project here. Please download it and use Spire.PDF for Java Version:9.10.3 to test it.

If you encounter any issues during the testing process or need any further assistance, please don't hesitate to contact us. We value your feedback and appreciate your cooperation.

Sincerely,
Annika
E-iceblue support team
User avatar

Annika.Zhou
 
Posts: 1657
Joined: Wed Apr 07, 2021 2:50 am

Mon Nov 13, 2023 7:54 am

Wow, thank you.

I can confirm that simply not making it static resolves the issue, even as far back as Spire.PDF 3.8.2.

PDF creation (50 files à 2000 pages) took 3:13 minutes (193 seconds)
Parallel PDF creation (50 files à 2000 pages) took 0:39 minutes (39 seconds)


It also really seems to fix the exceptions, since I couldn't reproduce them anymore.

It is weird, why using static variables/methods would slow down the programm (the multithreading exceptions I can somehow understand), but at least we know now to just avoid static declarations when using Spire.PDF for Java.

RicoScheller
 
Posts: 52
Joined: Tue Jul 02, 2019 10:34 am

Mon Nov 13, 2023 9:17 am

Hi,

Thanks for your inquiry.

The slowdown you are experiencing may be attributed to the efficiency of the file system resources when creating files within static methods. Static methods often share resources across multiple threads, leading to resource contention and slower execution. This contention can significantly impact the overall performance when handling large numbers of document creations.

As a comparison, we suggest trying to create regular TXT documents instead of using our PDF product. You can experiment with both static and non-static methods while performing multi-threaded document creation. By doing so, you can compare the time taken for document creation and assess if there is a noticeable difference in performance.

If you have any other questions or concerns, just feel free to contact us. we are here to help.

Best regards,
Triste
E-iceblue support team
User avatar

Triste.Dai
 
Posts: 1000
Joined: Tue Nov 15, 2022 3:59 am

Return to Spire.PDF

cron