By Richard Hague April 13, 2018

Can the operation be performed in parallel?

Sometimes we can gain a significant performance benefit from changing .stream()  to .parallelStream()

If the operation is only going to be over a few objects then it can actually be slightly slower to create and manage the parallel operations but over 1000's of objects the performance benefit is more noticeable.  


   final String ftpMessage = sendingList

           .parallelStream()

            .map(Record::getPipeDelimitedMessage)

           .reduce((x, y) -> x + NEW_LINE + y)

            .orElse(BLANK)

          + NEW_LINE;


Changing the stream to a parallel stream in the code above reduced the processing time from around 89 seconds to 55 seconds for 10k messages


How efficient is x + NEW_LINE + y?

It seems innocuous, but under the hood Java is basically doing the following:

  String buf =  new  StringBuilder().append(x).append(NEW_LINE).toString();

  String result =  new  StringBuilder().append(buf).append(y).toString();

If performing the operation multiple times, you should manually create the StringBuilder and append all the values you need.  

In the case of the Java 8 Stream API, there is Collector that will do this for you:


  final  String ftpMessage = sendingList .parallelStream()

                                                   .map(Record::getPipeDelimitedMessage)

                                                  .collect(Collectors.joining(NEW_LINE))

                                                 + NEW_LINE;


This change decreased the time from 55 to 50 for 10k messages.


How much overhead is there when creating a new instance?

When dealing with a class that deals with parsing/writing/formatting  XML or JSON, it will usually have a lot of dependencies or large objects it will need create before it can be used.

If this is done only once, then the overhead gets absorbed into the application startup time, but if repeated every time an operation is performed it can have a impact on throughput.  

The most significant performance improvements were achieved by making sure we only created the formatter and objectmapper once.

Initialising the converter only once per instance reduced the time from 49 to 24 seconds for 10k messages. 60k messages with these changes took 60 seconds.

Initialising the objectmapper only once per instance resulted in a saving of around 10 seconds for 10k and 30 seconds when processing 60k messages.

Using ObjectMapper as a example:


  ObjectMapper mapper = new ObjectMapper();

  mapper.findAndRegisterModules();

  final T event;
  try {
  event = mapper.readValue(json, clazz);
  } catch (IOException e) {
  throw new RuntimeException("Error occurred parsing json: " + json, e);
  }


A quick test is to change the mapper to a static instance then create a test that will send 10k messages through the application.


 private static final ObjectMapper mapper = new ObjectMapper(); 
 static {
     AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
     AnnotationIntrospector secondary = new JacksonAnnotationIntrospector();
     AnnotationIntrospector pair = AnnotationIntrospector.pair(introspector, secondary);
     mapper.setAnnotationIntrospector(pair);
     mapper.findAndRegisterModules();
 }


Making this modification resulted in a saving of around 10 seconds for 10k and 30 seconds when processing 60k messages.

The full solution is to then make the class that uses the object mapper a component with the ObjectMapper autowired:

 @Component
 public class Factory {

    private final ObjectMapper mapper;

    @Autowired
    public Factory(ObjectMapper mapper) {
        this.mapper = mapper;
    }


The ObjectMapper will then need to be configured as a Bean:

   @Bean
    public ObjectMapper objectMapper() {
        AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
        AnnotationIntrospector secondary = new JacksonAnnotationIntrospector();
        AnnotationIntrospector pair = AnnotationIntrospector.pair(introspector, secondary);
        return Jackson2ObjectMapperBuilder
                .json()
                .annotationIntrospector(pair)
                .build().findAndRegisterModules();
    }

In this case it was added as a method in the RabbitMQ configuration.

Any class that uses the factory will then need to autowire it instead of using it as a static reference


By Richard Hague April 13, 2018

Can the operation be performed in parallel?

Sometimes we can gain a significant performance benefit from changing .stream()  to .parallelStream()

If the operation is only going to be over a few objects then it can actually be slightly slower to create and manage the parallel operations but over 1000's of objects the performance benefit is more noticeable.  


   final String ftpMessage = sendingList

           .parallelStream()

            .map(Record::getPipeDelimitedMessage)

           .reduce((x, y) -> x + NEW_LINE + y)

            .orElse(BLANK)

          + NEW_LINE;


Changing the stream to a parallel stream in the code above reduced the processing time from around 89 seconds to 55 seconds for 10k messages


How efficient is x + NEW_LINE + y?

It seems innocuous, but under the hood Java is basically doing the following:

  String buf =  new  StringBuilder().append(x).append(NEW_LINE).toString();

  String result =  new  StringBuilder().append(buf).append(y).toString();

If performing the operation multiple times, you should manually create the StringBuilder and append all the values you need.  

In the case of the Java 8 Stream API, there is Collector that will do this for you:


  final  String ftpMessage = sendingList .parallelStream()

                                                   .map(Record::getPipeDelimitedMessage)

                                                  .collect(Collectors.joining(NEW_LINE))

                                                 + NEW_LINE;


This change decreased the time from 55 to 50 for 10k messages.


How much overhead is there when creating a new instance?

When dealing with a class that deals with parsing/writing/formatting  XML or JSON, it will usually have a lot of dependencies or large objects it will need create before it can be used.

If this is done only once, then the overhead gets absorbed into the application startup time, but if repeated every time an operation is performed it can have a impact on throughput.  

The most significant performance improvements were achieved by making sure we only created the formatter and objectmapper once.

Initialising the converter only once per instance reduced the time from 49 to 24 seconds for 10k messages. 60k messages with these changes took 60 seconds.

Initialising the objectmapper only once per instance resulted in a saving of around 10 seconds for 10k and 30 seconds when processing 60k messages.

Using ObjectMapper as a example:


  ObjectMapper mapper = new ObjectMapper();

  mapper.findAndRegisterModules();

  final T event;
  try {
  event = mapper.readValue(json, clazz);
  } catch (IOException e) {
  throw new RuntimeException("Error occurred parsing json: " + json, e);
  }


A quick test is to change the mapper to a static instance then create a test that will send 10k messages through the application.


 private static final ObjectMapper mapper = new ObjectMapper(); 
 static {
     AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
     AnnotationIntrospector secondary = new JacksonAnnotationIntrospector();
     AnnotationIntrospector pair = AnnotationIntrospector.pair(introspector, secondary);
     mapper.setAnnotationIntrospector(pair);
     mapper.findAndRegisterModules();
 }


Making this modification resulted in a saving of around 10 seconds for 10k and 30 seconds when processing 60k messages.

The full solution is to then make the class that uses the object mapper a component with the ObjectMapper autowired:

 @Component
 public class Factory {

    private final ObjectMapper mapper;

    @Autowired
    public Factory(ObjectMapper mapper) {
        this.mapper = mapper;
    }


The ObjectMapper will then need to be configured as a Bean:

   @Bean
    public ObjectMapper objectMapper() {
        AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
        AnnotationIntrospector secondary = new JacksonAnnotationIntrospector();
        AnnotationIntrospector pair = AnnotationIntrospector.pair(introspector, secondary);
        return Jackson2ObjectMapperBuilder
                .json()
                .annotationIntrospector(pair)
                .build().findAndRegisterModules();
    }

In this case it was added as a method in the RabbitMQ configuration.

Any class that uses the factory will then need to autowire it instead of using it as a static reference