Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance optimization for primitive Dynamic***Property classes #369

Merged
merged 12 commits into from
Aug 19, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,41 @@
*
*/
public class DynamicBooleanProperty extends PropertyWrapper<Boolean> {

protected volatile boolean primitiveValue;

public DynamicBooleanProperty(String propName, boolean defaultValue) {
super(propName, Boolean.valueOf(defaultValue));

// Set the initial value of the cached primitive value.
this.primitiveValue = chooseValue();

// Add a callback to update the cached primitive value when the property is changed.
this.prop.addCallback(new Runnable() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding this callback here, you could just override either of the propertyChanged methods and then update the PropertyWrapper class to not add this class to the SUBCLASSES_WITH_NO_CALLBACK map. This would be cleaner and probably result in less overall code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is a much better way of doing it.
@howardyuan - i've made this change to the PR. Does this satisfy your concerns around my previous usage of the callbacks?

@Override
public void run() {
primitiveValue = chooseValue();
}
});
}

/**
* Get the current value from the underlying DynamicProperty
*
* @return
*/
private boolean chooseValue() {
Boolean propValue = this.prop == null ? null : this.prop.getBoolean();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null check for the prop field is unnecessary.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could be just the previous body of the get method: return prop.getBoolean(defaultValue).booleanValue(). The boxing and unboxing that will occur when the property value changes will be off the hot-path and a minor overhead for clearer code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, all of the overhead does seem to be caused by this. I've added another benchmark comparison to the CachedPropertiesPerfTest to illustrate. eg:

Original DynamiceBooleanProperty totalled 7087 ms.
New DynamicBooleanProperty totalled 1252 ms.
New DynamicBooleanProperty with old get() totalled 7129 ms.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't suggesting reverting the change to the get method, which is what your new test is effectively doing, but rather to put the old code from the get method into the new chooseValue method. There it would be execute just once per property value change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see what you mean now. Sorry for the confusion. I'll do that now, thanks.

return propValue == null ? defaultValue : propValue.booleanValue();
}

/**
* Get the current cached value.
*
* @return
*/
public boolean get() {
return prop.getBoolean(defaultValue).booleanValue();
return primitiveValue;
}
@Override
public Boolean getValue() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,41 @@
*
*/
public class DynamicDoubleProperty extends PropertyWrapper<Double> {

protected volatile double primitiveValue;

public DynamicDoubleProperty(String propName, double defaultValue) {
super(propName, Double.valueOf(defaultValue));

// Set the initial value of the cached primitive value.
this.primitiveValue = chooseValue();

// Add a callback to update the cached primitive value when the property is changed.
this.prop.addCallback(new Runnable() {
@Override
public void run() {
primitiveValue = chooseValue();
}
});
}

/**
* Get the current value from the underlying DynamicProperty
*
* @return
*/
private double chooseValue() {
Double propValue = this.prop == null ? null : this.prop.getDouble(defaultValue);
return propValue == null ? defaultValue : propValue.doubleValue();
}

/**
* Get the current cached value.
*
* @return
*/
public double get() {
return prop.getDouble(defaultValue).doubleValue();
return primitiveValue;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,45 @@
*
*/
public class DynamicFloatProperty extends PropertyWrapper<Float> {

protected volatile float primitiveValue;

public DynamicFloatProperty(String propName, float defaultValue) {
super(propName, Float.valueOf(defaultValue));

// Set the initial value of the cached primitive value.
this.primitiveValue = chooseValue();

// Add a callback to update the cached primitive value when the property is changed.
this.prop.addCallback(new Runnable() {
@Override
public void run() {
primitiveValue = chooseValue();
}
});
}

/**
* Get the current value from the underlying DynamicProperty
*
* @return
*/
private float chooseValue() {
Float propValue = this.prop == null ? null : this.prop.getFloat(defaultValue);
return propValue == null ? defaultValue : propValue.floatValue();
}

/**
* Get the current cached value.
*
* @return
*/
public float get() {
return prop.getFloat(defaultValue).floatValue();
return primitiveValue;
}

@Override
public Float getValue() {
// TODO Auto-generated method stub
return get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package com.netflix.config;

import com.netflix.config.PropertyWrapper;

/**
* A dynamic property whose value is an integer
* <p>Use APIs in {@link DynamicPropertyFactory} to create instance of this class.
Expand All @@ -25,15 +23,41 @@
*
*/
public class DynamicIntProperty extends PropertyWrapper<Integer> {

protected volatile int primitiveValue;

public DynamicIntProperty(String propName, int defaultValue) {
super(propName, Integer.valueOf(defaultValue));

// Set the initial value of the cached primitive value.
this.primitiveValue = chooseValue();

// Add a callback to update the cached primitive value when the property is changed.
this.prop.addCallback(new Runnable() {
@Override
public void run() {
primitiveValue = chooseValue();
}
});
}

/**
* Get the current value from the underlying DynamicProperty
*
* @return
*/
private int chooseValue() {
Integer propValue = this.prop == null ? null : this.prop.getInteger(defaultValue);
return propValue == null ? defaultValue : propValue.intValue();
}

/**
* Get the current cached value.
*
* @return
*/
public int get() {
return prop.getInteger(defaultValue).intValue();
return primitiveValue;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,45 @@
*
*/
public class DynamicLongProperty extends PropertyWrapper<Long> {

protected volatile long primitiveValue;

public DynamicLongProperty(String propName, long defaultValue) {
super(propName, Long.valueOf(defaultValue));

// Set the initial value of the cached primitive value.
this.primitiveValue = chooseValue();

// Add a callback to update the cached primitive value when the property is changed.
this.prop.addCallback(new Runnable() {
@Override
public void run() {
primitiveValue = chooseValue();
}
});
}

/**
* Get the current value from the underlying DynamicProperty
*
* @return
*/
private long chooseValue() {
Long propValue = this.prop == null ? null : this.prop.getLong(defaultValue);
return propValue == null ? defaultValue : propValue.longValue();
}

/**
* Get the current cached value.
*
* @return
*/
public long get() {
return prop.getLong(defaultValue).longValue();
return primitiveValue;
}

@Override
public Long getValue() {
// TODO Auto-generated method stub
return get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.netflix.config;

/**
* Simple benchmark impl to compare the caching of primitive boolean value in DynamicBooleanProperty with
* the original implementation.
*
* @author Mike Smith
* Date: 11/25/15
*/
public class CachedPropertiesPerfTest
{
public static void main(String[] args)
{
try {
long loopCount = 4000000000l;

// Run twice.
runTest(loopCount);
runTest(loopCount);
}
catch(Exception e) {
e.printStackTrace();
}
}

private static void runTest(long loopCount)
{
// Warmup and then run benchmark.
runNewDynamicBooleanPropertyTest(10000);
long durationPrimitive = runNewDynamicBooleanPropertyTest(loopCount);

// Warmup and then run benchmark.
runOldDynamicBooleanPropertyTest(10000);
long durationOriginal = runOldDynamicBooleanPropertyTest(loopCount);

System.out.println("#####################");
System.out.println("Original DynamiceBooleanProperty totalled " + durationOriginal + " ms.");
System.out.println("New DynamicBooleanProperty totalled " + durationPrimitive + " ms.");
}

private static long runOldDynamicBooleanPropertyTest(long loopCount)
{
OriginalDynamicBooleanProperty prop = new OriginalDynamicBooleanProperty("zuul.test.cachedprops.original", true);

long startTime = System.currentTimeMillis();

for (long i=0; i<loopCount; i++) {
prop.get();
}

return System.currentTimeMillis() - startTime;
}

private static long runNewDynamicBooleanPropertyTest(long loopCount)
{
DynamicBooleanProperty prop =
new DynamicBooleanProperty("zuul.test.cachedprops.new", true);

long startTime = System.currentTimeMillis();

for (long i=0; i<loopCount; i++) {
prop.get();
}

return System.currentTimeMillis() - startTime;
}

/**
* This is a copy of the DynamicBooleanProperty class from before I made the performance optimization to it.
* It's here so I can still do a back-to-back comparison of performance.
*/
static class OriginalDynamicBooleanProperty extends PropertyWrapper<Boolean> {

public OriginalDynamicBooleanProperty(String propName, boolean defaultValue) {
super(propName, Boolean.valueOf(defaultValue));
}

public boolean get() {
return prop.getBoolean(defaultValue).booleanValue();
}

@Override
public Boolean getValue() {
return get();
}
}
}