Proxy.isProxyClass scalability
Peter Levart
peter.levart at gmail.com
Sun Apr 21 13:39:21 UTC 2013
On 04/21/2013 04:52 AM, Mandy Chung wrote:
> Hi Peter,
>
> I want to give it some more thought and discuss with you next week.
> As for the zero number of interface, I think it's a bug and it should
> throw IAE if the given interfaces array is empty. Thanks for finding
> it and I'll file a separate bug for that since it requires spec
> update/clarification.
I think it's a feature. It's useful, since it forwards Object methods to
InvocationHandler (equals, hashCode, ...). Sometimes that's all you need.
Regards, Peter
>
> Mandy
>
> On 4/20/2013 12:31 AM, Peter Levart wrote:
>> Hi Mandy,
>>
>> I have another idea. Before jumping to implement it, I will first ask
>> what do you think of it. For example:
>>
>> - have an optimal interface names key calculated from interfaces.
>> - visibility of interfaces and other validations are pushed to
>> slow-path (inside ProxyClassFactory.apply)
>> - the proxy Class object returned from WeakCache.get() is
>> post-validated with a check like:
>>
>> for (Class<?> intf : interfaces) {
>> if (!intf.isAssignableFrom(proxyClass)) {
>> throw new IllegalArgumentException();
>> }
>> }
>> // return post-validated proxyClass from getProxyClass0()...
>>
>> I feel that Class.isAssignableFrom(Class) check could be much faster
>> and that the only reason the check can fail is if some interface is
>> not visible from the class loader.
>>
>> Am I correct?
>>
>> Regards, Peter
>>
>>
>>
>> On 04/19/2013 04:36 PM, Peter Levart wrote:
>>> Hi Mandy,
>>>
>>> On 04/19/2013 07:33 AM, Mandy Chung wrote:
>>>>>
>>>>> https://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc/webrev.02/index.html
>>>>>
>>>>> What about package-private in java.lang.reflect? It makes Proxy
>>>>> itself much easier to read. When we decide which way to go, I can
>>>>> remove the interface and only leave a single package-private class...
>>>>>
>>>>
>>>> thanks. Moving it to a single package-private classin
>>>> java.lang.reflectand remove the interface sounds good.
>>>
>>> Right.
>>>
>>>>
>>>> I have merged your patch with the recent TL repo and did some clean
>>>> up while reviewing it. Some comments:
>>>> 1. getProxyClass0 should validate the input interfaces and throw
>>>> IAE if any illegal argument (e.g. interfaces are not visible to the
>>>> given loader) before calling proxyClassCache.get(loader,
>>>> interfaces). I moved back the validation code from
>>>> ProxyClassFactory.apply to getProxyClass0.
>>>
>>> Ops, you're right. There could be a request with interface(s) with
>>> same name(s) but loaded by different loader(s) and such code could
>>> return wrong pre-cached proxy class instead of throwing exception.
>>> Unfortunately, moving validation to slow-path was the cause of major
>>> performance and scalability improvement observed. With validation
>>> moved back to getProxyClass0 (in spite of using two-level
>>> WeakCache), we have the following performance (WeakCache#1):
>>>
>>>
>>> Summary (4 Cores x 2 Threads i7 CPU):
>>>
>>> Test Threads ns/op Original WeakCache#1
>>> ======================= ======= ============== ===========
>>> Proxy_getProxyClass 1 2,403.27 2,174.51
>>> 4 3,039.01 2,555.00
>>> 8 5,193.58 4,273.42
>>>
>>> Proxy_isProxyClassTrue 1 95.02 43.14
>>> 4 2,266.29 43.23
>>> 8 4,782.29 72.06
>>>
>>> Proxy_isProxyClassFalse 1 95.02 1.36
>>> 4 2,186.59 1.36
>>> 8 4,891.15 2.72
>>>
>>> Annotation_equals 1 240.10 195.68
>>> 4 1,864.06 201.41
>>> 8 8,639.20 337.46
>>>
>>> It's a little better than original getProxyClass(), but not much.
>>> The isProxyClass() and consequently Annotation.equals() are far
>>> better though. But as you might have guessed, I kind of solved that
>>> too...
>>>
>>> My first attempt was to optimize the interface validation. Only the
>>> "visibility" part is necessary to be performed on fast-path.
>>> Uniqueness and other things can be performed on slow-path. With
>>> split-validation and hacks like:
>>>
>>> private static final MethodHandle findLoadedClass0MH,
>>> findBootstrapClassMH;
>>> private static final ClassLoader dummyCL = new ClassLoader() {};
>>>
>>> static {
>>> try {
>>> Method method =
>>> ClassLoader.class.getDeclaredMethod("findLoadedClass0", String.class);
>>> method.setAccessible(true);
>>> findLoadedClass0MH =
>>> MethodHandles.lookup().unreflect(method);
>>>
>>> method =
>>> ClassLoader.class.getDeclaredMethod("findBootstrapClass",
>>> String.class);
>>> method.setAccessible(true);
>>> findBootstrapClassMH =
>>> MethodHandles.lookup().unreflect(method);
>>> } catch (NoSuchMethodException e) {
>>> throw (Error) new
>>> NoSuchMethodError(e.getMessage()).initCause(e);
>>> } catch (IllegalAccessException e) {
>>> throw (Error) new
>>> IllegalAccessError(e.getMessage()).initCause(e);
>>> }
>>> }
>>>
>>> static Class<?> findLoadedClass(ClassLoader loader, String name) {
>>> try {
>>> if (VM.isSystemDomainLoader(loader)) {
>>> return (Class<?>)
>>> findBootstrapClassMH.invokeExact(dummyCL, name);
>>> } else {
>>> return (Class<?>)
>>> findLoadedClass0MH.invokeExact(loader, name);
>>> }
>>> } catch (RuntimeException | Error e) {
>>> throw e;
>>> } catch (Throwable t) {
>>> throw new UndeclaredThrowableException(t);
>>> }
>>> }
>>>
>>>
>>> ... using findLoadedClass(loader, intf.getName()) and only doing
>>> Class.forName(intf.getName(), false, loader) if the former returned
>>> null ... I managed to reclaim some performance (WeakCache#1+):
>>>
>>>
>>> Test Threads ns/op Original WeakCache#1
>>> WeakCache#1+
>>> ======================= ======= ============== ===========
>>> ============
>>> Proxy_getProxyClass 1 2,403.27 2,174.51 1,589.36
>>> 4 3,039.01 2,555.00 1,929.11
>>> 8 5,193.58 4,273.42 3,409.77
>>>
>>>
>>> ...but that was still not very satisfactory.
>>>
>>> I modified the KeyFactory to create keys that weakly-reference
>>> interface Class objects and implement hashCode/equals in terms of
>>> comparing those Class objects. With such keys, no false aliasing can
>>> occur and the whole validation can be pushed back to slow-path. I
>>> tried hard to create keys with as little space overhead as possible:
>>>
>>> http://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc-wi/webrev.01/index.html
>>>
>>>
>>> ...but there can be no miracles. The good news is that the
>>> performance is back (WeakCache#2):
>>>
>>>
>>> Summary (4 Cores x 2 Threads i7 CPU):
>>>
>>> Test Threads ns/op Original WeakCache#1
>>> WeakCache#2
>>> ======================= ======= ============== ===========
>>> ===========
>>> Proxy_getProxyClass 1 2,403.27 2,174.51 163.57
>>> 4 3,039.01 2,555.00 211.70
>>> 8 5,193.58 4,273.42 322.14
>>>
>>> Proxy_isProxyClassTrue 1 95.02 43.14 41.23
>>> 4 2,266.29 43.23 42.20
>>> 8 4,782.29 72.06 72.21
>>>
>>> Proxy_isProxyClassFalse 1 95.02 1.36 1.36
>>> 4 2,186.59 1.36 1.36
>>> 8 4,891.15 2.72 2.72
>>>
>>> Annotation_equals 1 240.10 195.68 194.61
>>> 4 1,864.06 201.41 198.81
>>> 8 8,639.20 337.46 342.90
>>>
>>>
>>> ... and the most common usage (proxy class implementing exactly one
>>> interface) uses even less space than with interface-names-key - 16
>>> bytes saved per proxy class vs. 8 bytes saved (32 bit addressing mode):
>>>
>>> --------------------------------------
>>> Original j.l.r.Proxy
>>> 1 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 400 400
>>> 1 1 768 368
>>> 1 2 920 152
>>> 1 3 1072 152
>>> 1 4 1224 152
>>> 1 5 1376 152
>>> 1 6 1528 152
>>> 1 7 1680 152
>>> 1 8 1832 152
>>> 2 9 2152 320
>>> 2 10 2304 152
>>> 2 11 2456 152
>>> 2 12 2672 216
>>> 2 13 2824 152
>>> 2 14 2976 152
>>> 2 15 3128 152
>>> 2 16 3280 152
>>>
>>> --------------------------------------
>>> Patched j.l.r.Proxy
>>> 1 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 240 240
>>> 1 1 792 552
>>> 1 2 928 136
>>> 1 3 1064 136
>>> 1 4 1200 136
>>> 1 5 1336 136
>>> 1 6 1472 136
>>> 1 7 1608 136
>>> 1 8 1744 136
>>> 2 9 2088 344
>>> 2 10 2224 136
>>> 2 11 2360 136
>>> 2 12 2560 200
>>> 2 13 2696 136
>>> 2 14 2832 136
>>> 2 15 2968 136
>>> 2 16 3104 136
>>>
>>>
>>> Did you know, that Proxy.getProxyClass() can generate proxy classes
>>> implementing 0 interfaces? It can. There's exactly one such class
>>> generated per class loader:
>>>
>>>
>>> --------------------------------------
>>> Original j.l.r.Proxy
>>> 0 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 400 400
>>> 1 1 760 360
>>> 2 2 1072 312
>>>
>>> --------------------------------------
>>> Patched j.l.r.Proxy
>>> 0 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 240 240
>>> 1 1 728 488
>>> 2 2 1040 312
>>>
>>>
>>> With 2 or more interfaces implemented by proxy class the space
>>> overhead increases faster than with original Proxy:
>>>
>>>
>>> --------------------------------------
>>> Original j.l.r.Proxy
>>> 2 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 400 400
>>> 1 1 768 368
>>> 1 2 920 152
>>> 1 3 1072 152
>>> 1 4 1224 152
>>> 1 5 1376 152
>>> 1 6 1528 152
>>> 1 7 1680 152
>>> 1 8 1832 152
>>> 2 9 2152 320
>>> 2 10 2304 152
>>> 2 11 2456 152
>>> 2 12 2672 216
>>> 2 13 2824 152
>>> 2 14 2976 152
>>> 2 15 3128 152
>>> 2 16 3280 152
>>>
>>> --------------------------------------
>>> Patched j.l.r.Proxy
>>> 2 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 240 240
>>> 1 1 832 592
>>> 1 2 1008 176
>>> 1 3 1184 176
>>> 1 4 1360 176
>>> 1 5 1536 176
>>> 1 6 1712 176
>>> 1 7 1888 176
>>> 1 8 2064 176
>>> 2 9 2448 384
>>> 2 10 2624 176
>>> 2 11 2800 176
>>> 2 12 3040 240
>>> 2 13 3216 176
>>> 2 14 3392 176
>>> 2 15 3568 176
>>> 2 16 3744 176
>>>
>>> --------------------------------------
>>> Original j.l.r.Proxy
>>> 3 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 400 400
>>> 1 1 776 376
>>> 1 2 936 160
>>> 1 3 1096 160
>>> 1 4 1256 160
>>> 1 5 1416 160
>>> 1 6 1576 160
>>> 1 7 1736 160
>>> 1 8 1896 160
>>> 2 9 2224 328
>>> 2 10 2384 160
>>> 2 11 2544 160
>>> 2 12 2768 224
>>> 2 13 2928 160
>>> 2 14 3088 160
>>> 2 15 3248 160
>>> 2 16 3408 160
>>>
>>> --------------------------------------
>>> Patched j.l.r.Proxy
>>> 3 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 240 240
>>> 1 1 864 624
>>> 1 2 1072 208
>>> 1 3 1280 208
>>> 1 4 1488 208
>>> 1 5 1696 208
>>> 1 6 1904 208
>>> 1 7 2112 208
>>> 1 8 2320 208
>>> 2 9 2736 416
>>> 2 10 2944 208
>>> 2 11 3152 208
>>> 2 12 3424 272
>>> 2 13 3632 208
>>> 2 14 3840 208
>>> 2 15 4048 208
>>> 2 16 4256 208
>>>
>>> --------------------------------------
>>> Original j.l.r.Proxy
>>> 4 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 400 400
>>> 1 1 776 376
>>> 1 2 936 160
>>> 1 3 1096 160
>>> 1 4 1256 160
>>> 1 5 1416 160
>>> 1 6 1576 160
>>> 1 7 1736 160
>>> 1 8 1896 160
>>> 2 9 2224 328
>>> 2 10 2384 160
>>> 2 11 2544 160
>>> 2 12 2768 224
>>> 2 13 2928 160
>>> 2 14 3088 160
>>> 2 15 3248 160
>>> 2 16 3408 160
>>>
>>> --------------------------------------
>>> Patched j.l.r.Proxy
>>> 4 interfaces / proxy class
>>>
>>> class proxy size of delta to
>>> loaders classes caches prev.ln.
>>> -------- -------- -------- --------
>>> 0 0 240 240
>>> 1 1 896 656
>>> 1 2 1136 240
>>> 1 3 1376 240
>>> 1 4 1616 240
>>> 1 5 1856 240
>>> 1 6 2096 240
>>> 1 7 2336 240
>>> 1 8 2576 240
>>> 2 9 3024 448
>>> 2 10 3264 240
>>> 2 11 3504 240
>>> 2 12 3808 304
>>> 2 13 4048 240
>>> 2 14 4288 240
>>> 2 15 4528 240
>>> 2 16 4768 240
>>>
>>>
>>> There's an increase of 8 bytes per proxy class key for each 2
>>> interfaces added to proxy class in original Proxy code, but there's
>>> an increase of 32 bytes per proxy class key for each single
>>> interface added in patched Proxy code.
>>>
>>> I think the most common usage is to implement a single interface and
>>> there is 16 bytes gained for each such usage compared to original
>>> Proxy code.
>>>
>>>> 2. I did some cleanup to restore some original comments to make the
>>>> diffs clearer where the change is.
>>>> 3. I removed the newInstance method which is dead code after my
>>>> previous code. Since we are in this code, I took the chance to
>>>> clean that up and also change a couple for-loop to use for-each.
>>>>
>>>> I have put the merged and slightly modified Proxy.java and webrev at:
>>>> http://cr.openjdk.java.net/~mchung/jdk8/webrevs/7123493/webrev.00/
>>>>
>>>> We will use this bug for your contribution:
>>>> 7123493 : (proxy) Proxy.getProxyClass doesn't scale under high load
>>>
>>> I took j.l.r.Proxy file from your webrev and just changed the
>>> KeyFactory implementation. WeakCache is generic and can be used
>>> unchanged with either implementation of KeyFactory.
>>>
>>>>
>>>> For the weak cache class, since it's for proxy implementation use,
>>>> I suggest to take out the supportContainsValueOperation flagas it
>>>> always keeps the reverse map for isProxyClass lookup.
>>>>
>>>> You can simply call Objects.requireNonNull(param) instead of
>>>> requireNonNull(param, "param-name") since the proxy implementation
>>>> should have made sure the argument is non-null.
>>>>
>>>> Formatting nits:
>>>>
>>>> 68 Object cacheKey = CacheKey.valueOf(
>>>> 69 key,
>>>> 70 refQueue
>>>> 71 );
>>>>
>>>> should be: all in one line or line break on a long-line. Same for
>>>> method signature.
>>>>
>>>> 237 void expungeFrom(
>>>> 238 ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
>>>> 239 ConcurrentMap<?, Boolean> reverseMap
>>>> 240 );
>>>>
>>>> should be:
>>>>
>>>> void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
>>>> ConcurrentMap<?, Boolean> reverseMap);
>>>>
>>>> so that it'll be more consistent with the existing code. I'll do a
>>>> detailed review on the weak cache class as you will finalize the code
>>>> per the decision to go with the two-level weak cache.
>>>
>>> I hope I have addressed all that in above webrev.
>>>
>>> Regards, Peter
>>>
>>>>
>>>> Thanks again for the good work.
>>>>
>>>> Mandy
>>>> [1] http://openjdk.java.net/jeps/161
>>>
>>
>
More information about the core-libs-dev
mailing list