Hi Claes,

On 01/24/2017 03:34 PM, Claes Redestad wrote:
Hi Peter,

thanks for the thorough examination of this issue.

Thanks for looking into this change...

After going through
the patch I do like what I see, but I have a few comments:

AnnotationInvocationHandler:
in invoke, cleaning up and replacing the explicit AssertionError should
be fine, but perhaps convert it to an assert that the number of
arguments is 1 when methodName is "equals" and 0 otherwise?

In the following part of code:

    public Object invoke(Object proxy, Method method, Object[] args) {

        String memberName = method.getName(); // guaranteed interned String
        Class<?> dtype = method.getDeclaringClass();

        if (dtype == Object.class ||  // equals/hashCode/toString
            dtype == Annotation.class // annotationType
            ) {
            if (memberName == "equals") return equalsImpl(proxy, args[0]);
            if (memberName == "hashCode") return hashCodeImpl();
            if (memberName == "annotationType") return type;
            if (memberName == "toString") return toStringImpl();
            throw new AssertionError("Invalid method: " + method);
        }

...in the if statement, when method's declaring class is either Object or Annotation, we have just a limited number of methods that are possible candidates and we can identify them just by their names (Method::getName returns interned string, so identity comparison is possible). For example: if method's name is "equals" and declaring class is either Object or Annotation, then we know that this method has a signature of Object::equals - we don't need to check - this is part of language spec.

The "throw new AssertionError("Invalid method: " + method)" is an unreachable statement until some new method is added to either Object or Annotation and at that time, if it ever comes to that, this code should fail and should be revised...

The assert you are talking about is meaningful only if inserted as 1st statement inside the if statement. Will add it.


Adding @Stable is fine if that has a performance impact, but I think
we might preserve clarity of intent if you kept the fields as final and
kept using Unsafe to deserialize properly.

Ok, will make them final but keep @Stable.


AnnotationSupport:
I dislike that this code was already catching Throwable, and that
you're now copying that approach.  Could the new the catch clause mimic
the one that previously wrapped the entire method?

I think we can do it even better than that. See new webrev...


AnnotationType:
accessibleMembers might not need to be volatile.

AnnotationType.accessibleMembers() may be called concurrently from multiple threads (it is used by annotation's equals method when passed a foreign annotation and by AnnotationSupport.getValueArray() helper method used to access repeatable annotations in a foreign container annotation). What this method returns is a HashMap object which is not tolerable to unsafe publication (unlike String, for example). Volatile field is needed for safe publication of the HashMap instance.


validateAnnotationMethod:
the block: label and break block seems unnecessary. I'd not worry
about optimizing for such invalid cases and simply run through all the
checks and set valid = false multiple times if so be it.

I changed the method to a boolean-returning isValidAnnotationMethod() with multiple exit paths and moved throwing of AnnotationFormatError into AnnotationType constructor.

Here's all that applied together with comment from Chris:

http://cr.openjdk.java.net/~plevart/jdk9-dev/8029019_Annotations.optimization/webrev.02/

plus more:

AnnotationInvocationHandler.equalsImpl()

- should check the number of values in both annotations and not the lengths of hash tables (hash table lengths are rounded to power of 2) - exception handling when a foreign annotation throws an unchecked exception from it's member method - propagate it instead of returning "false" from equals

AnnotationInvocationHandler.asOneOfUs()

- enclose the Proxy.getInvocationHandler() in doPrivileged() if needed otherwise we can get SecurityException

AnnotationInvocationHandler.readObject()

- don't modify the Map instance read from stream as it might be shared or unmodifiable - clone it and modify it if necessary.

AnnotationParser:

- skip annotations for types that are not annotation types any more and propagate AnnotationFormatError for invalid annotation types.

AnnotationSupport.getValueArray()

- exception handling cleanup.

I just noticed this last thing will need another webrev and some discussion as handling of "oneOfUs" vs. "foreign" annotation is different currently. Should a RuntimeException (other than IncompleteAnnotationException) be propagated or wrapped with AnnotationFormatError? RuntimeException(s) are exceptions thrown by various ExceptionProxy(s) when retrieving annotation values that are somehow invalid.

Here's the diff between webrev.01 and 02 (without changes to the test):

http://cr.openjdk.java.net/~plevart/jdk9-dev/8029019_Annotations.optimization/webrev.01to02/


Regards, Peter



Thanks!

/Claes

On 2017-01-17 15:10, Peter Levart wrote:
Hi,

This is a preview of a patch that addresses the following issue:

    https://bugs.openjdk.java.net/browse/JDK-8029019

But it is also an attempt to clean-up failure reporting for invalid
annotation types. Here's the 1st webrev:

http://cr.openjdk.java.net/~plevart/jdk9-dev/8029019_Annotations.optimization/webrev.01/


With this patch applied, running the following benchmark:

http://cr.openjdk.java.net/~plevart/jdk9-dev/8029019_Annotations.optimization/AnnotationsBench.java


Produces the following results:

Original:

Benchmark                                 Mode  Cnt Score Error  Units
AnnotationsBench.a1_PrimitiveMember avgt 10 20.790 ± 1.317 ns/op AnnotationsBench.a2_ObjectMember avgt 10 21.550 ± 0.580 ns/op AnnotationsBench.a3_PrimitiveArrayMember avgt 10 28.314 ± 1.477 ns/op AnnotationsBench.a4_ObjectArrayMember avgt 10 27.094 ± 0.574 ns/op AnnotationsBench.a5_AnnotationType avgt 10 13.047 ± 0.983 ns/op
AnnotationsBench.a6_HashCode              avgt   10 158.001 ± 9.347
ns/op
AnnotationsBench.a7_Equals                avgt   10 663.921 ± 27.256
ns/op
AnnotationsBench.a8_ToString              avgt   10 1772.901 ± 107.355
ns/op

Patched:

Benchmark                                 Mode  Cnt Score Error  Units
AnnotationsBench.a1_PrimitiveMember       avgt   10 8.547 ± 0.100  ns/op
AnnotationsBench.a2_ObjectMember          avgt   10 8.352 ± 0.340  ns/op
AnnotationsBench.a3_PrimitiveArrayMember avgt 10 17.526 ± 0.696 ns/op AnnotationsBench.a4_ObjectArrayMember avgt 10 15.639 ± 0.116 ns/op
AnnotationsBench.a5_AnnotationType        avgt   10 4.692 ± 0.154  ns/op
AnnotationsBench.a6_HashCode avgt 10 142.954 ± 7.460 ns/op AnnotationsBench.a7_Equals avgt 10 184.535 ± 0.917 ns/op
AnnotationsBench.a8_ToString              avgt   10 1806.685 ± 96.370
ns/op


Allocation rates are also reduced:

Original:

AnnotationsBench.a1_PrimitiveMember:·gc.alloc.rate.norm avgt 10
16.000 ±   0.001    B/op
AnnotationsBench.a2_ObjectMember:·gc.alloc.rate.norm avgt 10    16.000
±   0.001    B/op
AnnotationsBench.a3_PrimitiveArrayMember:·gc.alloc.rate.norm avgt
10    64.000 ±   0.001    B/op
AnnotationsBench.a4_ObjectArrayMember:·gc.alloc.rate.norm avgt 10
48.000 ±   0.001    B/op
AnnotationsBench.a5_AnnotationType:·gc.alloc.rate.norm avgt   10
16.000 ±   0.001    B/op
AnnotationsBench.a6_HashCode:·gc.alloc.rate.norm avgt   10 128.000 ±
0.001    B/op
AnnotationsBench.a7_Equals:·gc.alloc.rate.norm avgt   10 120.001 ±
0.001    B/op
AnnotationsBench.a8_ToString:·gc.alloc.rate.norm avgt   10 5200.002 ±
0.001    B/op

Patched:

AnnotationsBench.a1_PrimitiveMember:·gc.alloc.rate.norm avgt 10    ≈
10⁻⁵              B/op
AnnotationsBench.a2_ObjectMember:·gc.alloc.rate.norm avgt 10    ≈
10⁻⁵              B/op
AnnotationsBench.a3_PrimitiveArrayMember:·gc.alloc.rate.norm avgt
10    48.000 ±   0.001    B/op
AnnotationsBench.a4_ObjectArrayMember:·gc.alloc.rate.norm avgt 10
32.000 ±   0.001    B/op
AnnotationsBench.a5_AnnotationType:·gc.alloc.rate.norm avgt 10    ≈
10⁻⁵              B/op
AnnotationsBench.a6_HashCode:·gc.alloc.rate.norm avgt   10 64.000 ±
0.001    B/op
AnnotationsBench.a7_Equals:·gc.alloc.rate.norm avgt   10 24.000 ±
0.001    B/op
AnnotationsBench.a8_ToString:·gc.alloc.rate.norm avgt   10 5136.002 ±
0.001    B/op


As for the failure reporting: requesting an annotation for invalid
annotation type now always produces AnnotationFormatError. Previously,
some invalid annotations were simply skipped, some produced
AnnotationFormatError and for some of them, AnnotationFormatError was
raised only when evaluating Annotation's equals() method.

I would like to discuss this failure behavior more in-depth.

Regards, Peter


Reply via email to