Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java 8: Efficient use of multi core processors with lambdas and streams

Fabian Stäber
September 11, 2014
6.2k

Java 8: Efficient use of multi core processors with lambdas and streams

Lambdas and streams are more than just a more elegant way to express the same functionality. One of the major enhancements is that the underlying implementation of the stream API may process streams in parallel. This enables developers to use modern multi core processors efficiently with very little programming overhead.

This talk shows how to use parallel streams to improve performance of a classical JEE server application.

---

Lambda expressions and streams are among the most important new features in Java 8. In many cases, lambda expressions enable the developer to write more concise and expressive code than with previous Java versions. Streams are a new API for applying lambda expressions to traditional data structures, like ArrayList and HashMap.

However, the combination of lambdas and streams is more than just an elegant way to express the same functionality. One of the major enhancements is the ability to process streams in parallel. Parallel stream processing enables developers to use modern multi core processors efficiently with very little programming overhead.

This talk presents Java 8 lambdas and streams, with a special focus on the parallelization features:

* How can we use the parallelization features of the new standard library?

* How can we implement our own data structures to support parallel processing?

* What are the use cases for parallel stream processing, and when is parallel processing not feasible?

* What are the technologies supporting parallel stream processing under the hood?

These topics will be exemplified in a classical JEE server application.

---

Example Code: https://github.com/ConSol/parallel-streams-example

---

JavaZone 2014, http://www.javazone.no

Fabian Stäber

September 11, 2014
Tweet

Transcript

  1. Slide Example • Boilerplate – pom.xml – RestConfig (modern web.xml)

    • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 4
  2. Slide • Boilerplate – pom.xml – RestConfig (modern web.xml) •

    Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.consol.research</groupId> <artifactId>lambda</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> </dependencies> <build> <finalName>lambda</finalName> </build> </project> Example 26 March 2014 www.consol.de 5 <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>7.0</version> </dependency>
  3. Slide package de.consol.research; import ... @ApplicationPath("api") public class RestConfig extends

    Application {} Example • Boilerplate – pom.xml – RestConfig (modern web.xml) • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 6 @ApplicationPath("api") public class RestConfig extends Application {}
  4. Slide Example • Boilerplate – pom.xml – RestConfig (modern web.xml)

    • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 7 package de.consol.research; import java.time.LocalDate; public class Contract { private final State state; private final int priceInCent; private final LocalDate date; public Contract(State state, int priceInCent, LocalDate date) { this.state = state; this.priceInCent = priceInCent; this.date = date; } public State getState() { return state; } public int getPriceInCent() { return priceInCent; } public LocalDate getDate() { return date; } } public class Contract { private final State state; private final int priceInCent; private final LocalDate date;
  5. Slide Example • Boilerplate – pom.xml – RestConfig (modern web.xml)

    • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 8 package de.consol.research; public enum State { BW, // Baden-Württemberg BY, // Bayern BE, // Berlin BB, // Brandenburg HB, // Bremen HH, // Hamburg HE, // Hessen MV, // Mecklenburg-Vorpommern NI, // Niedersachsen NW, // Nordrhein-Westfalen RP, // Rheinland-Pfalz SL, // Saarland SN, // Sachsen ST, // Sachsen-Anhalt SH, // Schleswig-Holstein TH // Thüringen } public enum State { BW, // Baden-Württemberg BY, // Bayern BE, // Berlin BB, // Brandenburg
  6. Slide Example • Boilerplate – pom.xml – RestConfig (modern web.xml)

    • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 9 package de.consol.research; import ... public class TestDataGenerator { public static List<Contract> makeTestData() { return Arrays.asList( new Contract(BY, 2000, now()), new Contract(BY, 3500, now().minusDays(1)), new Contract(NW, 7000, now().minusMonths(1)), new Contract(HE, 400, now().minusMonths(1) .minusDays(2)) ); } } public class TestDataGenerator { public static List<Contract> makeTestData() { return Arrays.asList( new Contract(BY, 2000, now()), new Contract(BY, 3500, now().minusDays(1)), new Contract(NW, 7000, now().minusMonths(1)),
  7. Slide Example • Boilerplate – pom.xml – RestConfig (modern web.xml)

    • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 10
  8. Slide Example • Boilerplate – pom.xml – RestConfig (modern web.xml)

    • Model – Contract – State enum – TestDataGenerator • View – JavaScript, HTML • Controller – Dashboard. 26 March 2014 www.consol.de 11 package de.consol.research; import ... @Path("dashboard") public class Dashboard { private List<Contract> contracts = TestDataGenerator.makeTestData(); } @Path("dashboard") public class Dashboard { private List<Contract> contracts = TestDataGenerator.makeTestData(); }
  9. Slide Stream API 26 March 2014 www.consol.de 14 @GET @Path("total-sales")

    @Produces(APPLICATION_JSON) public int totalSales() { return contracts .stream() .filter(contract -> contract.getDate().isAfter(now().minusYears(1))) .mapToInt(Contract::getPriceInCent) .sum(); }
  10. Slide Stream API 26 March 2014 www.consol.de 15 Task 2:

    Sales by State GET /lambda/api/dashboard/sales-by-state { "BY": 5500, "NW": 7000, "HE": 400 }
  11. Slide Stream API 26 March 2014 www.consol.de 16 @GET @Path("sales-by-state")

    @Produces(APPLICATION_JSON) public Map<State, Integer> salesByState() { return contracts .stream() .filter(contract -> contract.getDate().isAfter(now().minusYears(1))) .collect(Collectors.groupingBy( Contract::getState, Collectors.summingInt(Contract::getPriceInCent))); }
  12. Slide Stream API 26 March 2014 www.consol.de 17 @GET @Path("sales-by-state")

    @Produces(APPLICATION_JSON) public Map<State, Integer> salesByState() { return contracts .stream() .filter(contract -> contract.getDate().isAfter(now().minusYears(1))) .collect(Collectors.groupingBy( Contract::getState, Collectors.summingInt(Contract::getPriceInCent))); } Can we re-use Lambda expressions?
  13. Slide Stream API 26 March 2014 www.consol.de 18 private Predicate<Contract>

    last12months = contract -> contract.getDate().isAfter(now().minusYears(1)); @GET @Path("sales-by-state") @Produces(APPLICATION_JSON) public Map<State, Integer> salesByState() { return contracts .stream() .filter(last12months) .collect(Collectors.groupingBy( Contract::getState, Collectors.summingInt(Contract::getPriceInCent))); }
  14. Slide GET /lambda/api/dashboard/sales-per-month Stream API 26 March 2014 www.consol.de 19

    Task 3: Sales per Month { “2014-03": 5500, “2014-02": 7400 }
  15. Slide Stream API 26 March 2014 www.consol.de 20 @GET @Path("sales-per-month")

    @Produces(APPLICATION_JSON) public Map<String, Integer> salesPerMonth() { return contracts .stream() .filter(last12months) .collect(Collectors.groupingBy( c -> c.getDate().getYear() + "-" + c.getDate().getMonthValue(), Collectors.summingInt(Contract::getPriceInCent))); }
  16. Slide Stream API Stream API 26 March 2014 www.consol.de 21

    Get things done in few lines of code… …really  ?
  17. Slide Stream API 26 March 2014 www.consol.de 22 public int

    totalSales() { return contracts .stream() .mapToInt(Contract::getPriceInCent) .sum(); } public int totalSales() { int resultInCent = 0; for (Contract contract : contracts) { resultInCent += contract.getPriceInCent(); } return resultInCent; } Java 8 Java 7
  18. Slide How To Make Things Faster • Keep Data in

    RAM • Avoid DB Roundtrips • Snappy Lightweight AJAX Front-End • Use Multiple Cores 26 March 2014 www.consol.de 25    ? ?
  19. Slide Use Multiple Cores 26 March 2014 www.consol.de 28 public

    int totalSales() { return contracts .stream() .mapToInt(Contract::getPriceInCent) .sum(); } public int totalSales() { int resultInCent = 0; for (Contract contract : contracts) { resultInCent += contract.getPriceInCent(); } return resultInCent; } Java 8 Java 7
  20. Slide Use Multiple Cores 26 March 2014 www.consol.de 29 public

    int totalSales() { return contracts .stream() .mapToInt(Contract::getPriceInCent) .sum(); } public int totalSales() { int resultInCent = 0; for (Contract contract : contracts) { resultInCent += contract.getPriceInCent(); } return resultInCent; } Do It Yourself synchronized, volatile, Lock, Executor, Callable, … Let the Library Do It For You
  21. Slide Use Multiple Cores 26 March 2014 www.consol.de 30 public

    int totalSales() { return contracts .parallelStream() .mapToInt(Contract::getPriceInCent) .sum(); } public int totalSales() { int resultInCent = 0; for (Contract contract : contracts) { resultInCent += contract.getPriceInCent(); } return resultInCent; } Do It Yourself synchronized, volatile, Lock, Executor, Callable, … Let the Library Do It For You
  22. Slide Use Multiple Cores 26 March 2014 www.consol.de 31 //

    Set of all states in contract data Set<State> states = new HashSet<State>(); contracts .parallelStream() .map(Contract::getState) .forEach(s -> states.add(s)); Thread Safe Operations Not Thread Safe
  23. Slide Use Multiple Cores 26 March 2014 www.consol.de 32 //

    Set of all states in contract data Set<State> states = contracts .parallelStream() .map(Contract::getState) .collect(Collectors.toSet()); Thread Safe Operations Thread Safe 
  24. Slide Use Multiple Cores Under the Hood (internal stream API

    implementation) 26 March 2014 www.consol.de 33
  25. Slide Under the Hood 26 March 2014 www.consol.de 34 (2

    + 8 + 7 + 3) + (3 + 5 + 1 + 7) (2 + 8) + (7 + 3) 7 + 3 2 + 8 3 + 5 1 + 7 (3 + 5) + (1 + 7)
  26. Slide Under the Hood 26 March 2014 www.consol.de 35 if

    (my portion of the work is small enough) do the work directly else split my work into two pieces invoke the two pieces and wait for the results Java 7: Fork/Join Framework
  27. Slide Under the Hood 26 March 2014 www.consol.de 36 public

    class LegacyContractsData implements Iterable<Contract> { private State[] states; private int[] pricesInCent; private LocalDate[] dates; // ... } Can I use this with my legacy data?
  28. Slide Under the Hood Tell the stream API: • how

    to split your data into chunks • how to iterate over a chunk. 26 March 2014 www.consol.de 37 Implement a Spliterator. Can I use this with my legacy data?
  29. Slide Under the Hood 26 March 2014 www.consol.de 38 public

    class MyOwnSpliterator implements Spliterator<Contract> { @Override public Spliterator<Contract> trySplit() { // split data into chunks } @Override public boolean tryAdvance(Consumer<? super Contract> action) { // iterate over a chunk } @Override public long estimateSize() { // optional metadata } @Override public int characteristics() { // optional metadata } }
  30. Slide public static void main(String[] args) { long start =

    System.nanoTime(); for (int i=0; i<10; i++) { dashboard.totalSales(); // sequential version } long duration = System.nanoTime() - start; System.out.println("10 sequential calls took " + duration + "ns."); // ... same with parallel version here. } Use Multiple Cores 26.03.2014 www.consol.de 41 Naive and probably wrong. vm warmup dead code elimination constant folding
  31. Slide Use Multiple Cores 26.03.2014 www.consol.de 42 JMH to the

    Rescue! @Benchmark @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MILLISECONDS) public int myBenchmark() { return dashboard.totalSales(); }
  32. Slide Use Multiple Cores 26.03.2014 www.consol.de 43 10 Contracts: Sequential

    8 times better 10,000 Contracts: Parallel 2 times better
  33. Slide Summary 26 March 2014 www.consol.de 45 Efficient use of

    multi-core processors with lambdas and streams: • Library does it for you • Use thread safe operations • Implement Spliterators • Evaluate your results