*PropertyBase vs Simple*Property
Tom Schindl
tom.schindl at bestsolution.at
Mon Mar 24 22:40:15 UTC 2014
Ups there was an error in my test for the last Call line so the numbers
there are:
38 (lambda) vs 32 (subclass)
> 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();
> TestObject o = target.get(0);
> for( int i = 0; i < invokationOverheadCallCount; i++ ) {
> o.invalidate();
> }
> 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 24.03.14 23:36, Tom Schindl wrote:
> The code I run is attached in the mail copy it to your env and run it
> and flip the testLambda from true to false.
>
> I might have been something dumb wrong but this is what I came up with.
>
> Tom
>
> On 24.03.14 23:31, Kevin Rushforth wrote:
>> 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.
>>>>>>>
>>>>>>>
>>>
>>>
>
More information about the openjfx-dev
mailing list