110
Shooting the Rapids: Getting the Best from Java 8 Streams Kirk Pepperdine @kcpeppe Maurice Naftalin @mauricenaftalin

Shooting the Rapids: Getting the Best from Java 8 Streams

Embed Size (px)

Citation preview

Page 1: Shooting the Rapids: Getting the Best from Java 8 Streams

Shooting the Rapids: Getting the Best from Java 8

Streams

Kirk Pepperdine@kcpeppe

Maurice Naftalin@mauricenaftalin

Page 2: Shooting the Rapids: Getting the Best from Java 8 Streams

About Kirk

• Specialises in performance tuning• speaks frequently about performance• author of performance tuning workshop

• Co-founder jClarity• performance diagnositic tooling

• Java Champion (since 2006)

Page 3: Shooting the Rapids: Getting the Best from Java 8 Streams

About Kirk

• Specialises in performance tuning• speaks frequently about performance• author of performance tuning workshop

• Co-founder jClarity• performance diagnositic tooling

• Java Champion (since 2006)

Page 4: Shooting the Rapids: Getting the Best from Java 8 Streams

About Maurice

Page 5: Shooting the Rapids: Getting the Best from Java 8 Streams

About Maurice

Page 6: Shooting the Rapids: Getting the Best from Java 8 Streams

About Maurice

Co-author Author

Page 7: Shooting the Rapids: Getting the Best from Java 8 Streams

About Maurice

Co-author Author

JavaChampion

JavaOneRock Star

Page 8: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

Page 9: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

Page 10: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources

Page 11: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons

Page 12: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons• Justifying the Overhead

Page 13: Shooting the Rapids: Getting the Best from Java 8 Streams

Example: Processing GC Logfile

⋮2.869: Application time: 1.0001540 seconds5.342: Application time: 0.0801231 seconds8.382: Application time: 1.1013574 seconds⋮

Page 14: Shooting the Rapids: Getting the Best from Java 8 Streams

Example: Processing GC Logfile

⋮2.869: Application time: 1.0001540 seconds5.342: Application time: 0.0801231 seconds8.382: Application time: 1.1013574 seconds⋮

Page 15: Shooting the Rapids: Getting the Best from Java 8 Streams

sum=2.181635

Example: Processing GC Logfile

⋮2.869: Application time: 1.0001540 seconds5.342: Application time: 0.0801231 seconds8.382: Application time: 1.1013574 seconds⋮

Page 16: Shooting the Rapids: Getting the Best from Java 8 Streams

Example: Processing GC Logfile

⋮2.869: Application time: 1.0001540 seconds5.342: Application time: 0.0801231 seconds8.382: Application time: 1.1013574 seconds⋮

DoubleSummaryStatistics {count=3, sum=2.181635, min=0.080123, average=0.727212, max=1.101357}

Page 17: Shooting the Rapids: Getting the Best from Java 8 Streams

Application time: (\\d+\\.\\d+)

Example: Processing GC Logfile

⋮2.869: Application time: 1.0001540 seconds5.342: Application time: 0.0801231 seconds8.382: Application time: 1.1013574 seconds⋮

Regex:

Page 18: Shooting the Rapids: Getting the Best from Java 8 Streams

Application time: (\\d+\\.\\d+)Pattern stoppedTimePattern = Pattern.compile(" ");

⋮ Matcher matcher = stoppedTimePattern.matcher(logRecord); String value = matcher.group(1);

Example: Processing GC Logfile

Page 19: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing GC Logfile: Old School Code

Pattern stoppedTimePattern = Pattern.compile("Application time: (\\d+\\.\\d+)");

String logRecord; double value = 0; while ( ( logRecord = logFileReader.readLine()) != null) {

Matcher matcher = stoppedTimePattern.matcher(logRecord); if ( matcher.find()) {

value += (Double.parseDouble( matcher.group(1))); }

}

Page 20: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

What is a Lambda?

matchermatcher.find()

matchermatcher.find()

Page 21: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

Predicate<Matcher> matches =

What is a Lambda?

matchermatcher.find()

matchermatcher.find()

Page 22: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

Predicate<Matcher> matches =

What is a Lambda?

matcher

Predicate<Matcher> matches =

matcher.find()matcher

matcher.find()

Page 23: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

Predicate<Matcher> matches =

What is a Lambda?

matcherPredicate<Matcher> matches =

matcher.find()matcher

matcher.find()

Page 24: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

Predicate<Matcher> matches =

What is a Lambda?

matcherPredicate<Matcher> matches =

matcher.find()

->

matchermatcher.find()

Page 25: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

Predicate<Matcher> matches =

What is a Lambda?

matcherPredicate<Matcher> matches = matcher.find()->

matchermatcher.find()

Page 26: Shooting the Rapids: Getting the Best from Java 8 Streams

Predicate<Matcher> matches = new Predicate<Matcher>() { @Override public boolean test(Matcher matcher) { return matcher.find(); }};

Predicate<Matcher> matches =

What is a Lambda?

matcherPredicate<Matcher> matches =

A lambda is a function from arguments to result

matcher.find()->

matchermatcher.find()

Page 27: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

Page 28: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

data source

Page 29: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

start streaming

Page 30: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

map to Matcher

Page 31: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

filter outuninteresting bits

Page 32: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

extract group

Page 33: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics();

map String to Double

Page 34: Shooting the Rapids: Getting the Best from Java 8 Streams

Processing Logfile: Stream Code

DoubleSummaryStatistics summaryStatistics =

logFileReader.lines()

.map(input -> stoppedTimePattern.matcher(input))

.filter(matcher -> matcher.find())

.map(matcher -> matcher.group(1))

.mapToDouble(s -> Double.parseDouble(s))

.summaryStatistics(); aggregate results

Page 35: Shooting the Rapids: Getting the Best from Java 8 Streams

What is a Stream?

• A sequence of values• source and intermediate operations set the stream up lazily:

Stream<String> groupStream = logFileReader.lines()

.map(stoppedTimePattern::matcher)

.filter(Matcher::find)

.map(matcher -> matcher.group(1))

.mapToDouble(Double::parseDouble);

Source

Page 36: Shooting the Rapids: Getting the Best from Java 8 Streams

What is a Stream?

• A sequence of values• source and intermediate operations set the stream up lazily:

Stream<String> groupStream = logFileReader.lines()

.map(stoppedTimePattern::matcher)

.filter(Matcher::find)

.map(matcher -> matcher.group(1))

.mapToDouble(Double::parseDouble);

IntermediateOperations

Page 37: Shooting the Rapids: Getting the Best from Java 8 Streams

What is a Stream?

• The terminal operation pulls the values down the stream:

SummaryStatistics statistics = logFileReader.lines()

.map(stoppedTimePattern::matcher)

.filter(Matcher::find)

.map(matcher -> matcher.group(1))

.mapToDouble(Double::parseDouble)

.summaryStatistics();TerminalOperation

Page 38: Shooting the Rapids: Getting the Best from Java 8 Streams

Visualising Sequential Streams

x2x0 x1 x3x0 x1 x2 x3

Source Map Filter Reduction

Intermediate Operations

Terminal Operation

“Values in Motion”

Page 39: Shooting the Rapids: Getting the Best from Java 8 Streams

Visualising Sequential Streams

x2x0 x1 x3x1 x2 x3 ✔

Source Map Filter Reduction

Intermediate Operations

Terminal Operation

“Values in Motion”

Page 40: Shooting the Rapids: Getting the Best from Java 8 Streams

Visualising Sequential Streams

x2x0 x1 x3 x1x2 x3 ❌✔

Source Map Filter Reduction

Intermediate Operations

Terminal Operation

“Values in Motion”

Page 41: Shooting the Rapids: Getting the Best from Java 8 Streams

Visualising Sequential Streams

x2x0 x1 x3 x1x2x3 ❌✔

Source Map Filter Reduction

Intermediate Operations

Terminal Operation

“Values in Motion”

Page 42: Shooting the Rapids: Getting the Best from Java 8 Streams

Old School: 80200msSequential: 25800ms

(>9m lines, MacBook Pro, Haswell i7, 4 cores, hyperthreaded)

Stream code is faster because operations are fused

How Does That Perform?

Page 43: Shooting the Rapids: Getting the Best from Java 8 Streams

Can We Do Better?

Parallel streams make use of multiple cores• split the data into segments• each segment processed by its own thread

- on its own core – if possible

Page 44: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 45: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 46: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 47: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 48: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 49: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 50: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 51: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 52: Shooting the Rapids: Getting the Best from Java 8 Streams

Splitting the Data

Implemented by a Spliterator:

Page 53: Shooting the Rapids: Getting the Best from Java 8 Streams

x2

Visualizing Parallel Streams

x0x1

x3

x0

x1x2x3

Page 54: Shooting the Rapids: Getting the Best from Java 8 Streams

x2

Visualizing Parallel Streams

x0

x1

x3

x0

x1

x2x3

Page 55: Shooting the Rapids: Getting the Best from Java 8 Streams

x2

Visualizing Parallel Streams

x1

x3

x0

x1

x3

Page 56: Shooting the Rapids: Getting the Best from Java 8 Streams

x2

Visualizing Parallel Streams

x1 y3

x0

x1

x3

Page 57: Shooting the Rapids: Getting the Best from Java 8 Streams

Stream CodeDoubleSummaryStatistics summaryStatistics =

logFileReader.lines().parallel()

.map(stoppedTimePattern::matcher)

.filter(Matcher::find)

.map(matcher -> matcher.group(1))

.mapToDouble(Double::parseDouble)

.summaryStatistics();

Page 58: Shooting the Rapids: Getting the Best from Java 8 Streams

Results of Going Parallel:

• No benefit from using parallel streams while streaming data

Page 59: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

Page 60: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

Page 61: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources

Page 62: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons

Page 63: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons• Justifying the Overhead

Page 64: Shooting the Rapids: Getting the Best from Java 8 Streams

Poorly Splitting Sources

Page 65: Shooting the Rapids: Getting the Best from Java 8 Streams

• Some sources split much worse than others– LinkedList vs. ArrayList

Poorly Splitting Sources

Page 66: Shooting the Rapids: Getting the Best from Java 8 Streams

• Some sources split much worse than others– LinkedList vs. ArrayList

• Streaming I/O is bad. – kills the advantage of going parallel

Poorly Splitting Sources

Page 67: Shooting the Rapids: Getting the Best from Java 8 Streams

• Some sources split much worse than others– LinkedList vs. ArrayList

• Streaming I/O is bad. – kills the advantage of going parallel

Poorly Splitting Sources

Page 68: Shooting the Rapids: Getting the Best from Java 8 Streams

Streaming I/O Bottleneck

x2x0 x1 x3x0 x1 x2 x3

Page 69: Shooting the Rapids: Getting the Best from Java 8 Streams

Streaming I/O Bottleneck

❌x2x1x0 x1 x3

Page 70: Shooting the Rapids: Getting the Best from Java 8 Streams

5.342: … nds

LineSpliterator

2.869: Applicati … seconds \n 8.382: … nds 9.337:App … nds\n \n \n

spliterator coverage

Page 71: Shooting the Rapids: Getting the Best from Java 8 Streams

5.342: … nds

LineSpliterator

2.869: Applicati … seconds \n 8.382: … nds 9.337:App … nds\n \n \n

spliterator coverage

MappedByteBuffer

Page 72: Shooting the Rapids: Getting the Best from Java 8 Streams

5.342: … nds

LineSpliterator

2.869: Applicati … seconds \n 8.382: … nds 9.337:App … nds\n \n \n

spliterator coverage

MappedByteBuffer mid

Page 73: Shooting the Rapids: Getting the Best from Java 8 Streams

5.342: … nds

LineSpliterator

2.869: Applicati … seconds \n 8.382: … nds 9.337:App … nds\n \n \n

spliterator coverage

MappedByteBuffer mid

Page 74: Shooting the Rapids: Getting the Best from Java 8 Streams

5.342: … nds

LineSpliterator

2.869: Applicati … seconds \n 8.382: … nds 9.337:App … nds\n \n \n

spliterator coveragenew spliterator coverage

MappedByteBuffer mid

Page 75: Shooting the Rapids: Getting the Best from Java 8 Streams

5.342: … nds

LineSpliterator

2.869: Applicati … seconds \n 8.382: … nds 9.337:App … nds\n \n \n

spliterator coveragenew spliterator coverage

MappedByteBuffer mid

Included in JDK9 as FileChannelLinesSpliterator

Page 76: Shooting the Rapids: Getting the Best from Java 8 Streams

StreamingIO: 56sSpliterator: 88s

(>9m lines, MacBook Pro, Haswell i7, 4 cores, hyperthreaded)

Stream code is faster because operations are fused

LineSpliterator – results

Page 77: Shooting the Rapids: Getting the Best from Java 8 Streams

When to Use Parallel Streams?

Page 78: Shooting the Rapids: Getting the Best from Java 8 Streams

When to Use Parallel Streams?• Task must be recursively decomposable

– subtasks for each data segment must be independent

Page 79: Shooting the Rapids: Getting the Best from Java 8 Streams

When to Use Parallel Streams?• Task must be recursively decomposable

– subtasks for each data segment must be independent

• Source must be well-splitting

Page 80: Shooting the Rapids: Getting the Best from Java 8 Streams

When to Use Parallel Streams?• Task must be recursively decomposable

– subtasks for each data segment must be independent

• Source must be well-splitting• Enough hardware to support all VM needs

– there may be other business afoot

Page 81: Shooting the Rapids: Getting the Best from Java 8 Streams

When to Use Parallel Streams?• Task must be recursively decomposable

– subtasks for each data segment must be independent

• Source must be well-splitting• Enough hardware to support all VM needs

– there may be other business afoot

• Overhead of splitting must be justified– intermediate operations need to be expensive– and CPU-bound

Page 82: Shooting the Rapids: Getting the Best from Java 8 Streams

When to Use Parallel Streams?• Task must be recursively decomposable

– subtasks for each data segment must be independent

• Source must be well-splitting• Enough hardware to support all VM needs

– there may be other business afoot

• Overhead of splitting must be justified– intermediate operations need to be expensive– and CPU-bound

http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html

Page 83: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

Page 84: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

Page 85: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources

Page 86: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons

Page 87: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons• Justifying the Overhead

Page 88: Shooting the Rapids: Getting the Best from Java 8 Streams

Tragedy of the Commons

Page 89: Shooting the Rapids: Getting the Best from Java 8 Streams

Tragedy of the Commons

You have a finite amount of hardware– it might be in your best interest to grab it all– but if everyone behaves the same way…

Page 90: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

Page 91: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

Page 92: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources

Page 93: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons

Page 94: Shooting the Rapids: Getting the Best from Java 8 Streams

Agenda

• Introduction– lambdas, streams, and a logfile processing problem

• Optimizing stream sources• Tragedy Of The Commons• Justifying the Overhead

Page 95: Shooting the Rapids: Getting the Best from Java 8 Streams

Justifying the Overhead

CPNQ performance model:

C - number of submittersP - number of CPUsN - number of elementsQ - cost of the operation

Page 96: Shooting the Rapids: Getting the Best from Java 8 Streams

Justifying the Overhead

Need to amortize setup costs– N*Q needs to be large– Q can often only be estimated– N often should be >10,000 elements

If P is the number of processors, the formula assumes that intermediate tasks are CPU bound

Page 97: Shooting the Rapids: Getting the Best from Java 8 Streams

Don’t Have Too Many Threads!

• Too many threads cause frequent handoffs• It costs ~80,000 cycles to handoff data between threads• You can do a lot of processing in 80,000 cycles!

Page 98: Shooting the Rapids: Getting the Best from Java 8 Streams

Fork/Join

Page 99: Shooting the Rapids: Getting the Best from Java 8 Streams

Fork/Join• Parallel streams implemented by Fork/Join framework

• added in Java 7, but difficult to code• parallel streams are more usable

Page 100: Shooting the Rapids: Getting the Best from Java 8 Streams

Fork/Join• Parallel streams implemented by Fork/Join framework

• added in Java 7, but difficult to code• parallel streams are more usable

Page 101: Shooting the Rapids: Getting the Best from Java 8 Streams

Fork/Join• Parallel streams implemented by Fork/Join framework

• added in Java 7, but difficult to code• parallel streams are more usable

• Each segment of data is submitted as a ForkJoinTask • ForkJoinTask.invoke() spawns a new task• ForkJoinTask.join() retrieves the result

Page 102: Shooting the Rapids: Getting the Best from Java 8 Streams

Fork/Join• Parallel streams implemented by Fork/Join framework

• added in Java 7, but difficult to code• parallel streams are more usable

• Each segment of data is submitted as a ForkJoinTask • ForkJoinTask.invoke() spawns a new task• ForkJoinTask.join() retrieves the result

• How Fork/Join works and performs is important to your latency picture

Page 103: Shooting the Rapids: Getting the Best from Java 8 Streams

Common Fork/Join Pool

Fork/Join by default uses a common thread pool- default number of worker threads == number of logical cores - 1- (submitting thread is pressed into service)

- can configure the pool via system properties:

- or create our own pool…

java.util.concurrent.ForkJoinPool.common.parallelism java.util.concurrent.ForkJoinPool.common.threadFactory java.util.concurrent.ForkJoinPool.common.exceptionHandler

Page 104: Shooting the Rapids: Getting the Best from Java 8 Streams

Custom Fork/Join Pool

When used inside a ForkJoinPool, the ForkJoinTask.fork() method uses the current pool:

ForkJoinPool ourOwnPool = new ForkJoinPool(10);

ourOwnPool.invoke(() -> stream.parallel(). ⋮

Page 105: Shooting the Rapids: Getting the Best from Java 8 Streams

Don’t Have Too Few Threads!

• Fork/Join pool uses a work queue• If tasks are CPU bound, no use increasing the size of the

thread pool• But if not CPU bound, they are sitting in queue

accumulating dead time• Can make thread pool bigger to reduce dead time• Little’s Law tells us

Number of tasks in the system =Arrival rate * Average service time

Page 106: Shooting the Rapids: Getting the Best from Java 8 Streams

Little’s Law Example

System receives 400 Txs and it takes 100ms to clear a request- Number of tasks in system = 0.100 * 400 = 40

On an 8 core machine with a CPU bound task- implies 32 tasks are sitting in queue accumulating dead time

- Average response time 600 ms of which 500ms is dead time- ~83% of service time is in waiting

Page 107: Shooting the Rapids: Getting the Best from Java 8 Streams

public final V invoke() { ForkJoinPool.common.getMonitor().submitTask(this); int s; if ((s = doInvoke() & DONE_MASK) != NORMAL) reportException(s); ForkJoinPool.common.getMonitor().retireTask(this); return getRawResult();}

ForkJoinPool Observability

ForkJoinPool comes with no visibility- need to instrument ForkJoinTask.invoke()

– gather data from ForkJoinPool to feed into Little’s Law

Page 108: Shooting the Rapids: Getting the Best from Java 8 Streams

Conclusions

Sequential stream performance comparable to imperative codeGoing parallel is worthwhile IF- task is suitable

- data source is suitable

- environment is suitable

Need to monitor JDK to understanding bottlenecks- Fork/Join pool is not well instrumented

Page 109: Shooting the Rapids: Getting the Best from Java 8 Streams

Questions?

Page 110: Shooting the Rapids: Getting the Best from Java 8 Streams

Questions?