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