I am evaluating Aspose.Words for use as a batch converter for RTF-to-PDF, and have obtained a Trial license.
The program/service in question will receive a “list of files” (as files to be read from disk or as byte[] arrays) and then process each in turn. This is a classic opportunity to use TPL and the Parallel.Foreach construct. My test machine has 4 processors available (4 threads), so here we go…
To test this, I built a trivial console application that loads a reasonably complex RTF file into a byte[] array, creates several copies of that array into unique separately allocated MemoryStreams, and then iterates over those streams to process the result. This ensures that there is no memory contention - these are not ‘shared memory’ type operations. Each load/save pair is completely separate.
When 1 processor is used (either with or without Parallel.foreach), the time to load and save the stream is encouraging. However, when more than one processor is used (MaxDegreeOfParallelism = 2 or 4), then I notice that the time taken for Aspose.Words.Document(inputStream) is significantly deteriorated depending on the number of threads. Putting a lock() around the call demonstrates that the Document() load function is not thread safe.
Here are some results:
Processing 4 documents using 1 threads
Document: doc1 Read: 721 Save: 1066 ms
Document: doc2 Read: 773 Save: 868 ms
Document: doc3 Read: 680 Save: 847 ms
Document: doc4 Read: 601 Save: 886 ms
Total elapsed time: 6482 ms
Now with 4 threads and no lock():
Processing 4 documents using 4 threads
Document: doc2 Read: 2039 Save: 2715 ms
Document: doc4 Read: 2091 Save: 2674 ms
Document: doc3 Read: 2038 Save: 2736 ms
Document: doc1 Read: 2139 Save: 2683 ms
Total elapsed time: 4862 ms
And again with a lock() { } around the Document() load call:
Document: doc2 Read: 628 Save: 1516 ms
Document: doc3 Read: 997 Save: 1350 ms
Document: doc1 Read: 1224 Save: 1185 ms
Document: doc4 Read: 1132 Save: 929 ms
Total elapsed time: 4972 ms
As you can see, the time for 1 thread (1 document at a time) is ~1.5 seconds. But adding threads, and without the lock(), the time balloons to ~4.7 seconds (as the threads compete). Then, with the lock(), the time improves to ~2.1 seconds. Still not great. It is likely that the serialized “lock” allows some independence for the Save() function as well, so my guess is that the Save() is also not thread safe (in fact, now that I think of it, I will test that next - using 2 lock()s).
Can you please advise the best way to utilize the elegance of Parallel.Foreach to process multiple documents on multiple processors? Is it reasonable to expect that with a small amount of overhead for parallelization, the time for 1 document should scale linearly, or approx 1.2x (allowing for TPL overhead)?
I am happy to provide the source code - nothing proprietary. Let me know.
Aspose team, please advise asap. This will determine if we purchase a significant license, including other products in the family such as Aspose.PDF or Aspose.Barcode.
Thank you!