📜 ⬆️ ⬇️

We optimize, optimize and optimize again

On duty, I occasionally have to use a profiler, since server performance requirements are documented and cannot fall below a certain level. In addition to some obvious architectural changes and solutions, there are often repeated places from module to module, from one project to another, which create additional load on the virtual machine, which I want to share.
It just so happened that the code for working with Date most often came across the eyes because we start with it:

Date

Not one dozen times I had the opportunity to observe how, during the processing of a single request from a user, a new date object is created in several different places. Most often the goal is the same - to get the current time. In the simplest case, it looks like this:

public boolean isValid(Date start, Date end) { Date now = new Date(); return start.before(now) && end.after(now); } 

It would seem - quite obvious and correct decision. In principle, yes, except for two things:

  public boolean isValid(Date start, Date end) { long now = System.currentTimeMillis(); return start.getTime() < now && now < end.getTIme(); } 

')
Simpledateformat

Very often in web projects there is a task to translate a string into a date or vice versa a date into a string. The task is quite typical and most often looks like this:

  return new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z").parse(dateString); 

This is a correct and quick solution, but if the server has to parse a string for each user request in each of the hundreds of threads, this can significantly affect the server performance due to the rather heavyweight constructor SimpleDateFormat, and in addition to the formatter itself, many other objects are created Easy Calendar (> 400 bytes in size).

The situation could have been easily resolved by making SimpleDateFormat a static field, but it is not thread safe. And in a competitive environment, you can easily catch NumberFormatException.

The second thought is to use synchronization. But this is still a rather dubious thing. In case of a big competition between threads, we can not only not improve performance, but also worsen.

But there are solutions and at least 2 of them:


Random

In my projects, there is often a task to return a random entity to the user. Usually this kind of code looks like this:

  return items.get(new Random().nextInt(items.size())); 

Great, easy, fast. But, if there are a lot of calls to the method, it means the constant creation of new objects Random. What can be easily avoided:

  private static final Random rand = new Random(); ... return items.get(rand.nextInt(items.size())); 

It would seem that here it is - the perfect solution, but even here it is not so simple. Although Random is thread-safe, it can be slow in multi-threaded environments. But Sun Oracle has already taken care of this:

  return items.get(ThreadLocalRandom.current().nextInt(items.size())); 

As stated in the documentation - this is the most optimal solution for our problem. ThreadLocalRandom is much more efficient than Random in a multithreaded environment. Unfortunately, this class is only available starting from the 7th version (after the bug fixes , hello TheShade ). In fact, this solution is the same as with SimpleDateFormat, only with its own personal class.

Not null

Many developers avoid null values, write something like this:

 public Item someMethod() { Item item = new Item(); //some logic if (something) { fillItem(item); } return item; } 

As a result, even if something never becomes true, a huge number of objects will still be created (provided that the method is called frequently).

Regexp

If you are writing a web application, almost certainly you use regular expressions. Typical code:

 public Item isValid(String ip) { Pattern pattern = Pattern.compile("xxx"); Matcher matcher = pattern.matcher(ip); return matcher.matches(); } 

As in the first case, as soon as a new IP address arrived, we must do the validation. Again for every challenge - packs of new objects. In this particular case, the code can be slightly optimized:

 private static final Pattern pattern = Pattern.compile("xxx"); public Item isValid(String ip) { Matcher matcher = pattern.matcher(ip); return matcher.matches(); } 

Ideally, it would also be to endure the creation of a matcher outside the method, but unfortunately it is not thread-safe and you have to constantly create it. As for single-threaded environment, there is a solution:

 private static final Pattern pattern = Pattern.compile("xxx"); private final Matcher matcher = pattern.matcher(""); public Item isValid(String ip) { matcher.reset(ip); return matcher.matches(); } 

Which is perfect for ... right, ThreadLocal'a.

Truncate date

Another fairly frequent task is cutting the date by hours, days, weeks. There are many ways to do this, from Apachev's DateUtils, to your own bikes:

  public static Date truncateToHours(Date date) { Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } 

For example, quite recently, analyzing the code of the map of the phase of the Hadup, I came across such 2 lines of code that consumed 60% of the CPU:

 key.setDeliveredDateContent(truncateToHours(byPeriodKey.getContentTimestamp())); key.setDeliveredDateAd(truncateToHours(byPeriodKey.getAdTimestamp())); 

For me, this was a big surprise, but the profiler is not lying. Fortunately, the map method turned out to be thread-safe, and the creation of the calendar object was brought out of the truncateToHours () method. That increased the speed of the map method 2 times.

HashCodeBuilder

I don’t know why, but some developers use Apache auxiliary classes to generate the hashcode () and equals () methods. For example:

  @Override public boolean equals(Object obj) { EqualsBuilder equalsBuilder = new EqualsBuilder(); equalsBuilder.append(id, otherKey.getId()); ... } @Override public int hashCode() { HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); hashCodeBuilder.append(id); ... } 


This, of course, is not bad if you use these methods several times during the life of the application. But if they are invoked constantly, for example, for each key during the Sort phase hadoop Joba, then this may well affect the speed of execution.

Conclusion

Why am I not? I do not in any case urge to run and shovel the code in order to save on the creation of a pair of objects, this is information for reflection, it is quite likely that for some it will be very useful. Thank you for reading.

Source: https://habr.com/ru/post/175049/


All Articles