Those results are surprising. Is this an apples-to-apples comparison
with the only difference being a Lambda versus an equivalent anonymous
inner class?
-- Kevin
Tom Schindl wrote:
Hi,
I've written a small sample to see what it gets me to check:
* creation overhead
* memory overhead
* call overhead
I'm not very good at this kind of thing so someone who knows to write
benchmarks might know a lot better - need to check out JMH most likely.
Anyways here are the numbers:
Topic Lambda Subclass
--------------------------------------------------------------
Create10M 372ms (0.00003723) 220ms (0.00002205)
Mem 108byte / instance 84byte / instance
Call-1M*10 42ms (0.0000042) 35ms (0.0000035)
Call-1*1M 11ms (0.0000011) 10ms (0.0000010)
So Lamda is considerable slower 40% and takes 20% more space, call
behavior is fairly the same. I'll try to learn about JMH.
Tom
package hello;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.SimpleObjectProperty;
public class TestMemory {
private static int oneIteration = 1_000_000;
private static int iterationCount = 10;
private static int invokationOverheadCallCount = 1_000_000;
private static boolean testLambda = false;
private static void testLambda(int iterations, List<TestObject>
storage) {
for( int i = 0; i < iterations; i++ ) {
storage.add(new SimpleLambdaBean());
}
}
private static void testSubclass(int iterations, List<TestObject>
storage) {
for( int i = 0; i < iterations; i++ ) {
storage.add(new SimpleSubclassBean());
}
}
public static void main(String[] args) {
System.err.println("Test Creation time");
System.err.println("==================");
{
long timeDiffTotal = 0;
for( int i = 0; i < iterationCount; i++ ) {
System.err.println(" Working for objects: " + (i *
oneIteration) + " - " + ((i+1) * oneIteration) );
System.err.println("
---------------------------------");
long s = System.currentTimeMillis();
if( testLambda ) {
testLambda(oneIteration, new
ArrayList<>());
} else {
testSubclass(oneIteration, new
ArrayList<>());
}
long e = System.currentTimeMillis();
long diff = e - s;
timeDiffTotal+=diff;
System.err.println(" Creation time: " + diff +
"("+diff * 1.0 / oneIteration+")");
System.err.println("
---------------------------------");
}
System.err.println(" Average time: " +
timeDiffTotal * 1.0 / (iterationCount * oneIteration));
}
List<TestObject> target = new
ArrayList<TestObject>(iterationCount * oneIteration);
{
System.err.println("");
System.err.println("Test Creation memory");
System.err.println("==================");
for( int i = 0; i < iterationCount; i++ ) {
System.err.println(" Working for objects: " + (i *
oneIteration) + " - " + ((i+1) * oneIteration) );
System.err.println("
---------------------------------");
if( testLambda ) {
testLambda(oneIteration, target);
} else {
testSubclass(oneIteration, target);
}
long freeDiff =
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.err.println(" Memory: " + freeDiff +
"("+freeDiff * 1.0 / target.size()+")");
System.err.println("
---------------------------------");
}
System.err.println(" Total objects created: " +
target.size());
}
{
System.err.println("");
System.err.println("Test invokation overhead (all then
times)");
System.err.println("=========================================");
long s = System.currentTimeMillis();
for( int i = 0; i < oneIteration; i++ ) {
target.get(i).invalidate();
}
long e = System.currentTimeMillis();
long diff = e - s;
System.err.println(" Total time calls: " + diff);
System.err.println(" Time per call: " + diff *
1.0 / invokationOverheadCallCount);
}
{
System.err.println("");
System.err.println("Test invokation multiple times");
System.err.println("===============================");
long s = System.currentTimeMillis();
for( int i = 0; i < invokationOverheadCallCount; i++ ) {
}
long e = System.currentTimeMillis();
long diff = e - s;
System.err.println(" Total time calls: " + diff);
System.err.println(" Time per call calls: " +
diff * 1.0 / invokationOverheadCallCount);
}
}
public static class LamdaInvalidationProperty<T> extends
SimpleObjectProperty<T> {
private Consumer<LamdaInvalidationProperty<T>> c;
public LamdaInvalidationProperty(Object bean, String name,
Consumer<LamdaInvalidationProperty<T>> c) {
super(bean, name);
this.c = c;
}
@Override
protected void invalidated() {
c.accept(this);
}
}
public interface TestObject {
public void invalidate();
}
public static class SimpleLambdaBean implements TestObject {
private AtomicInteger i = new AtomicInteger();
private ObjectProperty<Object> sample = new
LamdaInvalidationProperty<>(this, "sample", (e) -> {
i.incrementAndGet();
});
public void invalidate() {
sample.setValue(new Object());
}
}
public static class SimpleSubclassBean implements TestObject {
private AtomicInteger i = new AtomicInteger();
private ObjectProperty<Object> sample = new
ObjectPropertyBase<Object>() {
@Override
public Object getBean() {
return SimpleSubclassBean.this;
}
public String getName() {
return "sample";
}
public Object getValue() {
return null;
}
protected void invalidated() {
i.incrementAndGet();
}
};
public void invalidate() {
sample.setValue(new Object());
}
}
}
On 21.03.14 23:26, Kevin Rushforth wrote:
It does seem promising. We'll also need data to show the trade-offs to
help inform whether it is worth making such a massive change.
-- Kevin
Stephen F Northover wrote:
This looks good. I wonder if we should make this (massive) change
before we lambda graphics and controls? Probably doesn't matter.
We'll need a JIRA and someone assigned to it in order to track the work.
Steve
On 2014-03-21 12:53 PM, Tom Schindl wrote:
Hi Richard,
Coming back to this old thread and now that we are using lamdas all over
I guess we could take one more look into that.
I've prototyped an initial version by introducing a new internal type
named InvalidatedSimpleObjectProperty (not the best name ever!) - see
code pasted below.
And now one can write code like this:
public final ObjectProperty<Rectangle2D> viewportProperty() {
if (viewport == null) {
viewport = new InvalidatedSimpleObjectProperty<>(this,
"viewport", (o) -> {
invalidateWidthHeight();
impl_markDirty(DirtyBits.NODE_VIEWPORT);
impl_geomChanged();
} );
}
return viewport;
}
instead of
public final ObjectProperty<Rectangle2D> viewportProperty() {
if (viewport == null) {
viewport = new ObjectPropertyBase<Rectangle2D>() {
@Override
protected void invalidated() {
invalidateWidthHeight();
impl_markDirty(DirtyBits.NODE_VIEWPORT);
impl_geomChanged();
}
@Override
public Object getBean() {
return ImageView.this;
}
@Override
public String getName() {
return "viewport";
}
};
}
return viewport;
}
Which allows us to get rid of most of the ObjectPropertyBase sublcasses.
Tom
package com.sun.javafx.property;
import java.util.function.Consumer;
import javafx.beans.property.SimpleObjectProperty;
public final class InvalidatedSimpleObjectProperty<T> extends
SimpleObjectProperty<T> {
private final Consumer<InvalidatedSimpleObjectProperty<T>>
invalidationConsumer;
/**
* The constructor of {@code ObjectProperty}
*
* @param initialValue
* the initial value of the wrapped value
* @param invalidationConsumer
* the consumer to be called when the bean is
invalidated
*/
public InvalidatedSimpleObjectProperty(T initialValue, final
Consumer<InvalidatedSimpleObjectProperty<T>> invalidationConsumer) {
super(initialValue);
if( invalidationConsumer == null ) {
throw new IllegalArgumentException("Consumer can not be
null");
}
this.invalidationConsumer = invalidationConsumer;
}
/**
* The constructor of {@code ObjectProperty}
*
* @param bean
* the bean of this {@code ObjectProperty}
* @param name
* the name of this {@code ObjectProperty}
* @param invalidationConsumer
* the consumer to be called when the bean is
invalidated
*/
public InvalidatedSimpleObjectProperty(Object bean, String
name, final Consumer<InvalidatedSimpleObjectProperty<T>>
invalidationConsumer) {
super(bean, name);
if( invalidationConsumer == null ) {
throw new IllegalArgumentException("Consumer can not be
null");
}
this.invalidationConsumer = invalidationConsumer;
}
/**
* The constructor of {@code ObjectProperty}
*
* @param bean
* the bean of this {@code ObjectProperty}
* @param name
* the name of this {@code ObjectProperty}
* @param initialValue
* the initial value of the wrapped value
* @param invalidationConsumer
* the consumer to be called when the bean is
invalidated
*/
public InvalidatedSimpleObjectProperty(Object bean, String
name, T initialValue, final
Consumer<InvalidatedSimpleObjectProperty<T>> invalidationConsumer) {
super(bean,name,initialValue);
if( invalidationConsumer == null ) {
throw new IllegalArgumentException("Consumer can not be
null");
}
this.invalidationConsumer = invalidationConsumer;
}
@Override
protected void invalidated() {
invalidationConsumer.accept(this);
}
}
On 22.01.13 10:30, Richard Bair wrote:
Is the Java8 plan still there if not should the current
Simple*Property
subclasses who overload invalidated be changed to PropertyBase?
It is unlikely that we'll be able to do anything major here in Java
8 just because we don't really have Lambda yet that we can play
with, and changing over every property is a big job. Unless we knew
it would be a major win. I would say, if you encounter a Simple*
property that has been subclassed, then we should fix it up as we go
to be a PropertyBase* guy instead.