FizzBuzz record edition

forax at univ-mlv.fr forax at univ-mlv.fr
Wed Sep 2 20:06:11 UTC 2020


> De: "John Rose" <john.r.rose at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "amber-dev" <amber-dev at openjdk.java.net>
> Envoyé: Mercredi 2 Septembre 2020 03:46:54
> Objet: Re: FizzBuzz record edition

> On Sep 1, 2020, at 12:59 PM, Remi Forax < [ mailto:forax at univ-mlv.fr |
> forax at univ-mlv.fr ] > wrote:

>> There are two tricks,
>> - the Stream in Item.create() uses a <Item>map(s -> s) to see a Stream<Special>
>> as a Stream<Iteam>

> I wonder how the type-changing `id` works out in the JIT.
> Does it optimize away, or does each value get moved from one
> iteration-control-block to the next? That might be a job for
> stream customization.

First, i said something wrong, map() is called on an Optional, the result of findFirst(), not a Stream. 

To answer to your question, on my laptop, Item.create() is not JITed because the main() only print the first one hundred values. 
If i increase the value to 1000, i get this inline trace 

274 3 java.util.ImmutableCollections$ListN::get (7 bytes) made not entrant 
@ 3 java.util.Collection::stream (11 bytes) inline 
@ 1 java.util.List::spliterator (23 bytes) inline 
@ 12 java.util.AbstractList$RandomAccessSpliterator::<init> (82 bytes) callee is too large 
@ 19 java.util.Spliterators::spliterator (16 bytes) inline 
@ 5 java.util.Objects::requireNonNull (14 bytes) inline 
@ 12 java.util.Spliterators$IteratorSpliterator::<init> (39 bytes) callee is too large 
@ 7 java.util.stream.StreamSupport::stream (19 bytes) callee uses too much stack 
@ 9 java.lang.invoke.Invokers$Holder::linkToTargetMethod (9 bytes) force inline by annotation 
@ 5 java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial (21 bytes) force inline by annotation 
@ 1 java.lang.invoke.DirectMethodHandle::allocateInstance (16 bytes) inline 
@ 12 jdk.internal.misc.Unsafe::allocateInstance (0 bytes) native method 
@ 6 java.lang.invoke.DirectMethodHandle::constructorMethod (10 bytes) inline 
@ 16 RecordFizzBuzz$Item$$Lambda$17/0x0000000800b96a38::<init> (10 bytes) inline 
@ 1 java.lang.Object::<init> (1 bytes) inline 
@ 14 java.util.stream.ReferencePipeline::filter (22 bytes) callee uses too much stack 
@ 19 java.util.stream.ReferencePipeline::findFirst (12 bytes) inline 
@ 2 java.util.stream.FindOps::makeRef (14 bytes) inline 
@ 5 java.util.stream.AbstractPipeline::evaluate (94 bytes) callee is too large 
@ 24 java.lang.invoke.Invokers$Holder::linkToTargetMethod (8 bytes) force inline by annotation 
@ 4 java.lang.invoke.LambdaForm$MH/0x0000000800b94040::invoke (8 bytes) force inline by annotation 
@ 29 java.util.Optional::map (30 bytes) inline 
@ 1 java.util.Objects::requireNonNull(14 bytes) inline 
@ 6 java.util.Optional::isPresent (13 bytes) inline 
@ 12 java.util.Optional::empty (6 bytes) inline 
@ 21 RecordFizzBuzz$Item$$Lambda$18/0x0000000800b96c88::apply (8 bytes) inline 
@ 4 RecordFizzBuzz$Item::lambda$create$1 (2 bytes) inline 
@ 26 java.util.Optional::ofNullable (19 bytes) inline 
@ 15 java.util.Optional::<init> (10 bytes) inline 
@ 1 java.lang.Object::<init> (1 bytes) inline 
@ 33 java.lang.invoke.Invokers$Holder::linkToTargetMethod (9 bytes) force inline by annotation 
@ 5 java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial451 
(21 bytes) force inline by annotation 
@ 1 java.lang.invoke.DirectMethodHandle::allocateInstance (16 bytes) inline 
@ 12 jdk.internal.misc.Unsafe::allocateInstance (0 bytes) native method 
@ 6 java.lang.invoke.DirectMethodHandle::constructorMethod (10 bytes) inline 
@ 16 RecordFizzBuzz$Item$$Lambda$19/0x0000000800b98040::<init> (10 bytes) inline 
@ 1 java.lang.Object::<init> (1 bytes) inline 
@ 38 java.util.Optional::orElseGet (21 bytes) inline 
@ 15 java.util.function.Supplier::get (0 bytes) not inlineable 

I can see that the call to map() is fully inlined, but the stream part is not inlined because ListN (the implementation returned by List.of()) doesn't override the methods spliterator() or stream() so it uses the Spliterator based on an iterator which seems to have a constructor too big. 

>> - the result of Special.values() is stored in an immutable List otherwise each
>> call to values() create a new array.

> (If we had target typing we could have values() return the right
> thing in all enums, for callers that knew to ask for the right thing.
> Then deprecate the old values() call, and downgrade to synthetic
> for link-compatibility. I can dream.)

You means target typing + specialization so the array returned is an array of Iteam and not an array of Special. 
It won't help here because i first need to call filter() which uses the field divisor of Special. 

In fact, recently Tagir comes with an idea to implement what you want already using the way varargs work as a class witness. 

static <T> T[] values(T... alwaysEmpty) { 
if (alwaysEmpty.length != 0) { 
throw ... 
} 
// here you can use alwaysEmpty.getClass().getComponentType() to create the array with the right class 
} 

>> Everything works nicely in both IntelliJ 2020 and the upcoming Eclipse 2020.9
>> (4.17).

>> enjoy,

> Very enjoyable!

Rémi 


More information about the amber-dev mailing list