Debugging Pitfall: Object Creation, Loops and Performance Optimization

As a computer science tutor, I usually tell my newer students not to worry too much about performance optimization, first focus on understanding what you’re doing and getting your program to work. That’s usually my initial approach with my own programs as well, but there have been cases where I can’t always take that view. As a game developer, of course I need to keep performance in mind, but I worked on one major instance where a seemingly trivial bit of code caused a significant performance problem in a business, non-gaming application.

Our team was doing a significant update for a client’s enterprise-level web system, and one of the most important features was sending out blast emails to large numbers of recipients. Some of these emails would be sent to upwards of 10,000 people. As is common in email automation, each email needed to have the recipient name and other relevant, but recipient-specific information populated into the message. If I recall, we were using Java’s JavaMail API tools to construct and send the messages.

During user acceptance testing, a client user notified our team about a serious defect: sometimes it would take five minutes or more for an email to be generated and sent out to a large recipient list (again, by large, I mean thousands of recipients). The developer who wrote that section of the code was not available, so after taking a phone call and in-person meeting from our client liaison, I settled in and started looking through the code myself.

I’ve written elsewhere about some of my debugging techniques, such as using system logs and creating your own logging statements, and how to use bug trackers (or create your own tracker, which you can read about at https://medium.com/@cloudyheavengames/a-winning-technique-to-make-you-a-powerful-debugger-73c2dda088f0), so I won’t go too much into the actual process I used. Making heavy use of the system’s logging capabilities, as well as knowing the general location in the code that dealt with email, I had a pretty good idea of where to look for the problem.

The specific cause was not so readily obvious, and might not occur to newer coders. Even more experienced coders might fall into this subtle trap, as evidenced our team members’ experience levels. The issue was related to loops and object creation. As I mentioned, the basic body of the blast email was the same for each user, but we just substituted names and other information for each recipient. To do this task, we used a loop to go through each user in the recipient list and create and send the individual email. In pseudocode, the process would look something like this:

for(each user i in the list)

{

    • Create an email message object, let’s call it messageObject
    • Create a line of text for the greeting, using the recipient’s name
    • Create a paragraph of text for the rest of the standard message
    • Join the greeting text and the standard message to form a complete message text
    • Set messageObject’s text to our complete message
    • Set the messageObject’s attachment to a standard file (the same for every recipient)
    • Send the messageObject to the email server for delivery, with user i as the recipient

}

Of course, due to the time since I worked on the project, and because I don’t have copies or ownership of the code, these aren’t the exact details, but this description is pretty close to the algorithm.

Do you see a problem with this code? Our approach might be fine for a few messages, but what about for a large recipient list? Take a minute and consider it, then read on…

Okay, so notice what we’re doing in the first line of code inside the brackets. We create a message object. In the JavaMail API, this could be a MimeMessage object (if you want more details on the API, check https://javaee.github.io/javamail/docs/api/). If we have 10,000 recipients, we’re creating an object 10,000 times, destroying it at the end of each loop iteration, then going back to the top of the loop and creating basically the same object again. Everything about the message is the same for each recipient, except for a greeting with the recipient’s name, and the actual recipient address. So why do we need to create a brand new MimeMessage object 10,000 times? If you look at the documentation for MimeMessage (you can read it at https://docs.oracle.com/javaee/6/api/javax/mail/internet/MimeMessage.html), you’ll notice that we have methods to set the message recipient and the message text (setRecipients() and setText(), respectively). We’re also wasting time by creating a new string for the standard text in each loop iteration.

So the better solution might be to create the message object and standard text outside of the loop, then inside the loop, create the specific greeting line inside the loop, and then set the recipient and send the message inside the loop. New code might look like:

Create new messageObject

Create string with standard message text

Set messageObject’s attachment

for(each user i in the list)

{

      • Create a line of text for the greeting, using the recipient’s name
      • Join the greeting text and the standard message to form a complete message text
      • Set messageObject’s text to our complete message
      • Send the messageObject to the email server for delivery, with user i as the recipient

}

Creating and destroying objects take up time in Java. You might not notice it for small, standard operations, but when you are doing work on a large scale like we’ve been discussing, that can add up. If you’re interested, I also found an article by developer Andre Spiegel about the subject, in which he did a timed test of object creation in Java: http://drmirror.net/2013/06/06/object-creation/. He does point out that newer versions of Java have been better optimized, so that this process does not take as much time. However, bear in mind that you might be working for a client that does not always use the latest version of Java, which often happens for government or non-profit clients, or on projects that have a lot of older legacy code that you’re updating and working on. It takes a lot of work and planning to migrate and test an entire system to an updated software version, and sometimes there’s not time or resources to risk taking down the system for long periods of time. Additionally, Java is not the only language where you create and destroy objects, so just because Java’s Virtual Machine has improved, does not mean that the same holds true for other languages.

To sum up, debugging is not always about tracking down catastrophic crashes. Sometimes, seemingly trivial optimizations can make a huge difference in whether or not a certain feature is usable. So be mindful when running programs in extreme situations, such as with large numbers of users, particularly when you’re working with loops!

JOIN OUR NEWSLETTER
Sign up to receive updates about new tutorials, game news, and educational opportunities.
We hate spam. Your email address will not be sold or shared with anyone else.

Share This:

Tagged , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.