[YANGTOOLS-177] Optimize generated range checks Created: 30/May/14  Updated: 10/Apr/22  Due: 31/Jul/14  Resolved: 01/Aug/14

Status: Resolved
Project: yangtools
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Improvement
Reporter: Robert Varga Assignee: Ladislav Borak
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Operating System: All
Platform: All


Issue Links:
Duplicate
is duplicated by YANGTOOLS-186 Odd use of number constructors in gen... Resolved

 Description   

Profiling cbench I have encountered performance overhead when constructing FlowCookie class – 20528ms for 105187 invocations. Of that, 15894ms was spent in Range.closed() invocations.

Looking at the generated code:

public FlowCookie(BigInteger _value) {
if (_value != null) {
boolean isValidRange = false;
List<Range<BigInteger>> rangeConstraints = new ArrayList<>();
rangeConstraints.add(Range.closed(new BigInteger("0"), new BigInteger("18446744073709551615")));
for (Range<BigInteger> r : rangeConstraints) {
if (r.contains(_value))

{ isValidRange = true; }

}
if (!isValidRange)

{ throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", _value, rangeConstraints)); }

}
this._value = _value;
}

It is rather obvious we can do better:

1) those range constraints should be lazily generated, but retained as static fields (double-checked loading)

2) BigInteger("0") should be replaced with BigInteger.ZERO (also ONE and TEN)

3) For values less that Long.MAX_VALUE, we should use BigInteger.valueOf(long)

4) rangeConstraints should end up being an ImmutableList



 Comments   
Comment by Martin Vitez [ 11/Jun/14 ]

Proposed patch:

https://git.opendaylight.org/gerrit/#/c/7892/

Comment by Rob Adams [ 08/Jul/14 ]

It looks like the result of the code here is still incorrect. This is attempting to do a double-checked lock pattern in an incorrect way with the Java memory model. See http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Here's the incorrect code:

public static List<Range<BigInteger>> length() {
if (_length == null) {
synchronized (org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.Name.class) {
if (_length == null)

{ ImmutableList.Builder<Range<BigInteger>> builder = ImmutableList.builder(); builder.add(Range.closed(BigInteger.ONE, BigInteger.valueOf(256L))); _length = builder.build(); }

}
}
return _length;
}

In this case the (easy) correct fix is to use the static singleton pattern by declaring the _length field as a static variable in a subclass with a static initializer. It will be automatically initialized on the first access just as the code here is attempting to do.

Comment by Robert Varga [ 08/Jul/14 ]

Agreed, that double-checked locking is my mistake (and now I have to hunt for all the paces I proliferated it). Not sure what an inner class will cost us in terms of memory overhead. We may need to do some profiling and tradeoff juggling.

Comment by Rob Adams [ 08/Jul/14 ]

Given that this is a static variable the additional memory overhead is irrelevant. If you're doing this on an instance variable just setting it as volatile should fix your code.

Comment by Robert Varga [ 08/Jul/14 ]

So we have the following options:

1) do proper locking by protecting the entire check in a synchronized block
2) make _length volatile
3) inner holder class
4) allocate eagerly at class load time

I would opt for 3), except it may enlarge our PermGen footprint. I suspect we are usually very close to allocating an object when we touch the class, so 4) may actually make sense, too.

Opinions?

Comment by Robert Varga [ 08/Jul/14 ]

Well, the holder is a class, which needs to be loaded into pergen and be left there. While it is insignificant overhead compared to objects, given that we have thousands of classes, that overhead may pile up.

Comment by Rob Adams [ 08/Jul/14 ]

Oh I thought you'd done the lazy initialization because you were benchmarking this. If not, I'd say just move the initialization to a static block pending some evidence that lazy loading is useful.

Comment by Robert Varga [ 09/Jul/14 ]

Thinking about this a bit more, we do not need any locking at all. The reasoning is the following:

  • we are materializing immutable state, which is based solely on class bytecode
  • the encapsulating object is not referenced by individual instances

There are no ill side-effects from threads seeing different instances of the state – since it is immutable, it is bound to behave the same. Since they are not referenced by instances, we are not running the risk of leaking multiple instances to the heap in general. Resolution of which instance will actually survive is amortized.

Does this sounds correct, or am I missing something?

Comment by Dana Kutenicsova [ 25/Jul/14 ]

+1 on removing double-check for null (it creates Findbug issue).

Comment by Robert Varga [ 26/Jul/14 ]

So let's lose the lazy initialization and perform it on class load, using normal constant field. That should cut the complexity.

Comment by Ladislav Borak [ 30/Jul/14 ]

proposed patch: https://git.opendaylight.org/gerrit/#/c/9483/

Generated at Wed Feb 07 20:52:29 UTC 2024 using Jira 8.20.10#820010-sha1:ace47f9899e9ee25d7157d59aa17ab06aee30d3d.