Transcript slides
Java Lambda Puzzlers
Peter Lawrey
CEO of Higher Frequency Trading
JAX London 2015
Peter Lawrey
Java Developer/Consultant for investment banks, hedge fund and
trading firms for 7 years.
Most answers for Java and JVM on stackoverflow.com
Founder of the Performance Java User’s Group.
Architect of Chronicle Software
Chronicle Software
Help companies migrate to high performance Java code.
First large Java 8 project for client in production Dec 2014.
Sponsor open source projects https://github.com/OpenHFT
Licensed solutions Chronicle-Enterprise and Chronicle-Fix
Offer one week proof of concept workshops, advanced Java
training, consulting and bespoke development.
Chronicle Software
FIX – sub-micro second FIX
Engine.
Enterprise – Monitoring,
Traffic Shaping, Security.
Journal – Custom Data Store, Key-Queue
Engine – Customisable Data Fabric, Reactive Live Queries.
Queue – Persist every event
Map – Persisted Key-Value
Network – Remote access
Wire – YAML, Binary YAML,
JSON, CSV, Raw data.
Threads – Low latency
Bytes – 64-bit off heap native
+ memory mapped files
Core – Low level access to OS and JVM
Agenda
•
•
•
•
•
•
Reading and Writing lambdas.
How many objects does this create?
Capturing vs non-capturing lambdas.
How do you transform imperative code into the Stream API?
Mixing Imperative & Functional
Q & A.
Reading and Writing lambdas
Lambdas use type inference. This means the context alters the
type of the expression used. It even alters the implied type of a
lambda.
In Java we are used to types being very verbose, but with lambdas
we can get more magic.
Puzzler: Summing a field
Imperative Style.
double sum = 0.0;
for (Trade t : getResults())
sum += t.getRequirement();
Functional Style
double sum = getResults().stream()
.mapToDouble(Trade::getRequirement)
.sum();
But what about summing BigDecimal?
Summing a field with BigDecimal
Reduce has three parts.
• The starting value e.g. empty list, 0 for add or 1 for multiplying.
• How to accumulate each input with the previous value.
• How to accumulate those results.
BigDecimal sum = getResults().parallelStream()
.reduce(BigDecimal.ZERO, // starting point for each thread
(bd, t) -> bd.add(t.getRequirement()), // in each thread.
BigDecimal::add); // combine the results from each thread.
But what about summing BigDecimal?
Summing a field with BigDecimal (simplified)
Even though you can use parallel thread easily, you probably
shouldn’t. Also try not to do too much in each step.
BigDecimal sum = getResults().stream()
.map(Trade::getRequirement)
.reduce(BigDecimal.ZERO,
BigDecimal::add,
BigDecimal::add);
Puzzler: lambda factories
Lambdas make it easy to create factories. Even factories for
constants.
IntSupplier num = () -> 1;
DoubleSupplier rnd = Math::random;
LongSupplier now = System::currentTimeMillis;
Supplier<String> status = () -> "Users: " + getUserCount();
Supplier<Map<String,String>> mapSupplier = TreeMap::new;
This can be great way to inject code into your library. But can you
work out a type for this lambda?
greater = a -> b -> -a >- b
Puzzler: lambda factories
In this example, a lambda returns a lambda.
IntFunction<IntPredicate> lessThan = a -> b -> -a >- b;
System.out.println(lessThan.apply(1).test(2));
While you can curry functions this way in Java, it’s more likely to be
confusing than useful. Where ever I have used this I have ended up
refactoring this out.
Puzzler: Type magic
ExecutorService es = Executors.newCachedThreadPool();
es.submit(() -> {
// Unhandled Exception: java.lang.IOException
Files.lines(Paths.get("data.text")).forEach(System.out::println);
});
}
ExecutorService es = Executors.newCachedThreadPool();
es.submit(() -> {
// No error
Files.lines(Paths.get("data.text")).forEach(System.out::println);
return null;
});
Why does adding return null; suppress an error about a
checked exception?
Puzzler: Type magic
ExecutorService es = Executors.newCachedThreadPool();
es.submit(() -> {
// Unhandled Exception: java.lang.IOException
Files.lines(Paths.get("data.text")).forEach(System.out::println);
});
}
ExecutorService es = Executors.newCachedThreadPool();
es.submit(() -> {
// No error
Files.lines(Paths.get("data.text")).forEach(System.out::println);
return null;
});
Why does adding return null; suppress an error about a
checked exception?
Anti Puzzler: How many object does this create?
A common question is; How many Objects/Strings does the
following program create? This sort of question has limited value
as
• Your JVM is likely to create far more object than you imagine possible the
first time.
• You JVM can optimise your code and create far less objects than you might
imagine once it is optimised.
Anti Puzzler: How many object does this create?
Let us consider this simple program and see how many objects it
creates.
a)
20 objects
b)
200 objects
c) 2,000 objects
d) 20,000 objects
public class HowManyObjects {
public static void main(String... args)
throws IOException {
IntStream.of(1, 2, 3, 4)
.forEach(System.out::println);
// so we can get a heap dump.
System.in.read();
}
}
jmap -histo 4600 | head -20
num
#instances
#bytes class name
---------------------------------------------1:
1328
1623296 [I
2:
7016
578904 [C
3:
803
279424 [B
4:
2538
142128 jdk.internal.org.objectweb.asm.Item
5:
4684
112416 java.lang.String
6:
147
82800 [Ljdk.internal.org.objectweb.asm.Item;
7:
692
79024 java.lang.Class
8:
1249
58976 [Ljava.lang.Object;
9:
1290
39768 [Ljava.lang.Class;
10:
776
31040 java.lang.invoke.MethodType
11:
493
27608 java.lang.invoke.MemberName
12:
776
24832 java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry
13:
106
23744 jdk.internal.org.objectweb.asm.MethodWriter
14:
676
16224 java.lang.StringBuilder
15:
167
14696 java.lang.reflect.Method
16:
209
13376 jdk.internal.org.objectweb.asm.Label
17:
405
12960 jdk.internal.org.objectweb.asm.Type
18:
74
12432 jdk.internal.org.objectweb.asm.ClassWriter
19:
211
11816 jdk.internal.org.objectweb.asm.AnnotationWriter
20:
465
11160 jdk.internal.org.objectweb.asm.ByteVector
21:
446
10704 java.lang.StringBuffer
How do Lambdas help?
Lambdas are like anonymous inner classes, however they are
assigned to static variables if they don’t capture anything.
public static Runnable helloWorld() {
return () -> System.out.println("Hello World");
}
public static Consumer<String> printMe() {
// may create a new object each time = Garbage.
return System.out::println;
}
public static Consumer<String> printMe2() {
return x -> System.out.println(x);
}
How does Java 8 help? Lambdas
When you call new on an anonymous inner classes, a new object
is always created. Non capturing lambdas can be cached.
Runnable r1 = helloWorld();
Runnable r2 = helloWorld();
System.out.println(r1 == r2); // prints true
Consumer<String> c1 = printMe();
Consumer<String> c2 = printMe();
System.out.println(c1 == c2); // prints false
Consumer<String> c3 = printMe2();
Consumer<String> c4 = printMe2();
System.out.println(c3 == c4); // prints true
Serialization
Lambdas capture less scope. This means it doesn’t capture this
unless it has to, but it can capture things you don’t expect.
Lambdas capture less scope. If you use this.a the value of a is
copied.
Note: if you use the :: notation, it will capture the left operand if it
is a variable.
interface SerializableConsumer<T> extends Consumer<T>, Serializable {
}
// throws java.io.NotSerializableException: java.io.PrintStream
public SerializableConsumer<String> printMe() {
return System.out::println;
}
public SerializableConsumer<String> printMe2() {
return x -> System.out.println(x);
}
public SerializableConsumer<String> printMe3() {
// throws java.io.NotSerializableException: A
return new SerializableConsumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
}
Why Serialize a Lambda?
Lambdas are designed to reduce boiler plate, and when you
have a distributed system, they can be a powerful addition.
public static long incrby(MapView<String, Long> map, String key, long toAdd) {
return map.syncUpdateKey(key, v -> v + toAdd, v -> v);
}
The two lambdas are serialized on the client to be executed on
the server.
This example is from the RedisEmulator in Chronicle-Engine.
Why Serialize a Lambda?
The lambda m-> { is serialized and executed on the server.
public static Set<String> keys(MapView<String, ?> map, String pattern) {
return map.applyTo(m -> {
Pattern compile = Pattern.compile(pattern);
return m.keySet().stream()
.filter(k -> compile.matcher(k).matches())
.collect(Collectors.toSet());
});
}
Why Serialize a Lambda?
The filter/map lambdas are serialized.
The subscribe lambda is executed asynchronously on the client.
// print userId which have a usageCounter > 10
// each time it is incremented (asynchronously)
userMap.entrySet().query()
.filter(e -> e.getValue().usageCounter > 10)
.map(e -> e.getKey())
.subscribe(System.out::println);
Escape Analysis
Escape Analysis can
- Determine an object doesn’t escape a method so it can be
placed on the stack.
- Determine an object doesn’t escape a method so it doesn’t
need to be synchronized.
Escape Analysis
Escape Analysis works with inlining. After inlining, the JIT can see
all the places an object is used. If it doesn’t escape the method it
doesn’t need to be created and can be unpacked on the stack.
This works for class objects, but not arrays currently.
After “unpacking” on to the stack the object might be optimised
away. E.g. say all the fields are set using local variables anyway.
Escape Analysis
As of Java 8 update 60, the JITed code generated is still not as
efficient as code written to no need these optimisation, however
the JIT is getting closer to optimal.
How does Java 8 help? Escape Analysis
To parameters which control in-lining and the maximum method
size for performing escape analysis are
-XX:MaxBCEAEstimateSize=150
-XX:FreqInlineSize=325
For our software I favour
-XX:MaxBCEAEstimateSize=450
-XX:FreqInlineSize=425
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Puzzler: What type is this?
greater = a -> b -> a > b
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Puzzler: What type is this?
IntFunction<IntPredicate> greater = a -> b -> a > b;
System.out.println(greater.apply(1).test(2)); // false
A low latency API which uses Lambdas
Chronicle Wire is a single API which supports multiple formats.
You decide what data you want to read/write and independently
you can chose the format. E.g. YAML, JSON, Binary YAML, XML.
Using lambdas helped to simplify the API.
A low latency API which uses Lambdas
Wire Format
Bytes
99.9 %tile 99.99 %tile 99.999 %tile
worst
JSONWire
100*
3.11
5.56
10.6
36.9
Jackson
100
4.95
8.3
1,400
1,500
Jackson + Chronicle-Bytes
100*
2.87
10.1
1,300
1,400
BSON
96
19.8
1,430
1,400
1,600
BSON + Chronicle-Bytes
96*
7.47
15.1
1,400
11,600
BOON Json
100
20.7
32.5
11,000
69,000
Timings are in micro-seconds with JMH.
"price":1234,"longInt":1234567890,"smallInt":123,"flag":true,"text":"Hello World!","side":"Sell"
* Data was read/written to native memory.
A resizable buffer and a Wire format
// Bytes which wraps a ByteBuffer which is resized as needed.
Bytes<ByteBuffer> bytes = Bytes.elasticByteBuffer();
// YAML based wire format
Wire wire = new TextWire(bytes);
// or a binary YAML based wire format
Bytes<ByteBuffer> bytes2 = Bytes.elasticByteBuffer();
Wire wire2 = new BinaryWire(bytes2);
// or just data, no meta data.
Bytes<ByteBuffer> bytes3 = Bytes.elasticByteBuffer();
Wire wire3 = new RawWire(bytes3);
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Low latency API using Lambdas (Wire)
To write a message
wire.write(() -> "message").text(message)
.write(() -> "number").int64(number)
.write(() -> "timeUnit").asEnum(timeUnit)
.write(() -> "price").float64(price);
To read a message
wire.read(() -> "message").text(this, (o, s) -> o.message = s)
.read(() -> "number").int64(this, (o, i) -> o.number = i)
.read(() -> "timeUnit").asEnum(TimeUnit.class, this, (o, e) -> o.timeUnit = e)
.read(() -> "price").float64(this, (o, d) -> o.price = d);
message: Hello World number: 1234567890 code: SECONDS price: 10.5
A resizable buffer and a Wire format
In the YAML based TextWire
message: Hello World
number: 1234567890
code: SECONDS
price: 10.5
Binary YAML Wire
00000000
00000010
00000020
00000030
C7
6F
C4
69
6D
72
63
63
65
6C
6F
65
73
64
64
90
73
C6
65
00
61
6E
E7
00
67
75
53
28
65
6D
45
41
EB 48 65 6C 6C 6F 20 57 ·message ·Hello W
62 65 72 A3 D2 02 96 49 orld·num ber····I
43 4F 4E 44 53 C5 70 72 ·code·SE CONDS·pr
ice···(A
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Lambdas and Junit tests
To read the data
wire.read(() -> "message").text(this, (o, s) -> o.message = s)
.read(() -> "number").int64(this, (o, i) -> o.number = i)
.read(() -> "timeUnit").asEnum(TimeUnit.class, this, (o, e) -> o.timeUnit = e)
.read(() -> "price").float64(this, (o, d) -> o.price = d);
To check the data without a data structure
wire.read(() -> "message").text("Hello World", Assert::assertEquals)
.read(() -> "number").int64(1234567890L, Assert::assertEquals)
.read(() -> "timeUnit").asEnum(TimeUnit.class, TimeUnit.SECONDS,Assert::assertEquals)
.read(() -> "price").float64(10.5, (o, d) -> assertEquals(o, d, 0));
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Interchanging Enums and Lambdas
Enums and lambdas can both implement an interface.
Wherever you have used a non capturing lambda you can also use
an enum.
enum Field implements WireKey {
message, number, timeUnit, price;
}
@Override
public void writeMarshallable(WireOut wire) {
wire.write(Field.message).text(message)
.write(Field.number).int64(number)
.write(Field.timeUnit).asEnum(timeUnit)
.write(Field.price).float64(price);
}
message: Hello World number: 1234567890 code: SECONDS price: 10.5
When to use Enums
Enums have a number of benefits.
• They are easier to debug.
• The serialize much more efficiently.
• Its easier to manage a class of pre-defined enums to implement
your code, than lambdas which could be any where
Under https://github.com/OpenHFT/Chronicle-Engine search for
MapFunction and MapUpdater
message: Hello World number: 1234567890 code: SECONDS price: 10.5
When to use Lambdas
Lambdas have a number of benefits.
• They are simpler to write
• They support generics better
• They can capture values.
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Anonymous inner classes to lambdas
Use your IDE. I can transform from
IntFunction<IntPredicate> greater = new IntFunction<IntPredicate>() {
@Override
public IntPredicate apply(int a) {
return new IntPredicate() {
@Override
public boolean test(int b) {
return a > b;
}
};
}
};
to
IntFunction<IntPredicate> greater = a -> b -> a > b;
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Where can I try this out?
The source for these micro-benchmarks are test are available
https://github.com/OpenHFT/Chronicle-Wire
Chronicle Engine with live subscriptions
https://github.com/OpenHFT/Chronicle-Engine
Mixing Imperative and Functional code
Imperative Style.
double sum = 0.0;
for (Trade t : getResults())
sum += t.getRequirement();
Functional Style
double sum = getResults().stream()
.mapToDouble(Trade::getRequirement)
.sum();
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Mixing Imperative and Functional code
Reasons to not mix imperative and functional code.
- Oracle says you should do it.
- Most developers of functional languages will tell you not to do it.
- When we reviewed cases where code was confusion or possibly in
error, most of the time this was due to mixing imperative and
functional coding.
list.stream()
.filter(x -> x.isBad())
.forEach(list.remove());
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Mixing Imperative and Functional code
Reasons you will end up doing this anyway.
- Java has no language support for pure function, nor are they
planned.
- There is no code inspection tool I know of to help you inforce this.
- There are times when using pure functional code is just far more
complicated, possibly less efficient.
In short, avoid mixing styles if you can, and expecially avoid mixing
styles by accident.
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Mixing Imperative and Functional code
Reasons you will end up doing this anyway.
- Java has no language support for identifying pure function or
enforcing pure functional coding, nor is any planned.
- There is no code inspection tool I know of to help you inforce this,
though there probably should be and will be.
- There are times when using pure functional code is just far more
complicated, possibly less efficient.
In short, avoid mixing styles if you can, and expecially avoid mixing
styles by accident.
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Puzzler: compute two
map.computeIfAbsent(Key.Hello, s -> {
map.computeIfAbsent(Key.Hello, t -> 1);
return 2;
});
enum Key {Hello}
message: Hello World number: 1234567890 code: SECONDS price: 10.5
Puzzler: compute two
HashMap: {Hello=2}
WeakHashMap: {Hello=2}
TreeMap: {Hello=2}
IdentityHashMap: {Hello=2}
EnumMap: {Hello=2}
Hashtable: {Hello=2, Hello=1}
LinkedHashMap: {Hello=1, Hello=2}
ConcurrentSkipListMap: {Hello=1}
ConcurrentHashMap: (Never returns)
Q&A
Peter Lawrey
@ChronicleUG
http://chronicle.software
@PeterLawrey
http://vanillajava.blogspot.com
IntStream.range(0, 128).parallel().forEach(System::exit);