Benchmarking Pdf Generation In Ruby

At ShippingEasy, we use the ruby Prawn gem to generate shipping label PDFs for our customers. This is where we make our money, and so having this be a fast and pain-free experience is crucial to our business. Prawn has generally delivered finished PDFs well, but its performance has been not what we want. So I have started looking into how we can speed up this process. Here are some early results of benchmarking some options including upgrading Ruby, pure jRuby and jRuby invoking Java.

One thing I did early on was to just collect some basic benchmarking numbers for Prawn and its rendering of images into PDFs. There were 4 test groups:

  1. Prawn with Ruby 2.0.0 (at the time our current setup)
  2. Prawn with Ruby 2.1.2 (an upgrade we were undergoing)
  3. Prawn with jRuby and JIT compilation (no code changes)
  4. Prawn with jRuby delegating the PDF work to a Java class using PDFBox

The benchmark code used was Prawn’s png_type_6.rb (or a java equivalent) and yielded some interesting results…

Components Time Speed Increase
Ruby 2.0.0 + Prawn 6.65s
Ruby 2.1.2 + Prawn 5.10s 130%
jRuby 1.7.12 (JIT) + Prawn 4.02s 165%
jRuby 1.7.12 + Java/PDFBox 3.26s 204%

My takeaways from this are:

  1. Upgrade to ruby 2.1.2. Performance boosts + no code change = win.
  2. jRuby’s JIT compilation option is no joke. Your code interprets to bytecode once and subsequent invocations run the compiled bytecode more fast than MRI interprets ruby.
  3. The interoperability between jRuby/Java is a nice feature. I came up through the java ranks, so being able to drop to it (instead of C) when needing to go to a lower-level for performance is handy.

We have only upgraded to ruby 2.1.2 at this point, and I do not know if we’ll wind up doing anything else here. Even so, its nice to know we have additional options if we need to continue to improve performance in this area.

For the Java/PDF box benchmark, I used the following code:

 1 # encoding: utf-8
 2 
 3 $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
 4 require "benchmark"
 5 require 'java'
 6 require 'target/javapdf-1.0-SNAPSHOT-jar-with-dependencies.jar'
 7 java_import com.shippingeasy.javapdf.CreatePdf
 8 pdf_creator = CreatePdf.new
 9 
10 N=100
11 
12 Benchmark.bmbm do |x|
13   x.report("PNG Type 6") do
14     N.times do
15       pdf_creator.generate
16     end
17   end
18 end
 1 package com.shippingeasy.javapdf;
 2 
 3 import java.io.File;
 4 import java.awt.image.BufferedImage;
 5 import javax.imageio.ImageIO;
 6 
 7 import org.apache.pdfbox.pdmodel.*;
 8 import org.apache.pdfbox.pdmodel.edit.*;
 9 import org.apache.pdfbox.pdmodel.graphics.xobject.*;
10 
11 public class CreatePdf {
12   public void generate() throws Exception {
13     PDDocument doc = null;
14     try {
15       doc = new PDDocument();
16       drawImage(doc);
17       doc.save("dice.pdf");
18     } finally {
19       if (doc != null) {
20         doc.close();
21       }
22     }
23   }
24 
25   private void drawImage(PDDocument doc) throws Exception {
26     PDPage page = new PDPage();
27     doc.addPage(page);
28     PDPageContentStream content = new PDPageContentStream(doc, page);
29     content.drawImage(xImage(doc), 0, 0);
30     content.close();
31   }
32 
33   private PDXObjectImage xImage(PDDocument doc) throws Exception {
34     BufferedImage img = ImageIO.read(new File("data/images/dice.png"));
35     return new PDPixelMap(doc, img);
36   }
37 }

Lance Woodson

Dev + Data + DevOps


Contact    
View