多次调用Method/Field.getAnnotation(Class)与在Map中预缓存该数据的性能比较



我想知道是否有任何关于重复调用(在Java中)Method.getAnnotation(Class)Field.getAnnotation(Class)方法的性能的比较/研究,而不是(在程序启动时)存储带有类的元数据信息的预先计算的Map,并在以后重复查询它。哪一个将提供最好的运行时性能?

这个性能在Java 5、6和7下是一样的吗?

Map应该是更可取的方法。主要问题不仅仅是关于缓存。还提高了多线程争用。在method . getannotation()中,它调用一个同步私有方法declaredAnnotations()。

我知道这是一个相当老的问题,但是对于最近的jdk的结果可能仍然很有趣。

我编写了一些JMH基准测试,以了解注释信息缓存可能产生的影响:

@State(Scope.Thread)
public static class StateWithMethodAndHashMap {
    public StateWithMethodAndHashMap() {
        try {
            cache = new HashMap<>();
            method = AnnotationCachingBenchmark.class.getMethod("methodWithAnnotations");
            final Annotation annotation = method.getAnnotation(Deprecated.class);
            cache.put(method, annotation);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    final HashMap<Method, Annotation> cache;
    final Method method;
}
@State(Scope.Thread)
public static class StateWithMethodAndConcurrentHashMap {
    public StateWithMethodAndConcurrentHashMap() {
        try {
            cache = new ConcurrentHashMap<>();
            method = AnnotationCachingBenchmark.class.getMethod("methodWithAnnotations");
            cache.put(method, method.getAnnotation(Deprecated.class));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    final ConcurrentHashMap<Method, Annotation> cache;
    final Method method;
}
@State(Scope.Thread)
public static class StateWithMethod {
    public StateWithMethod() {
        try {
            method = AnnotationCachingBenchmark.class.getMethod("methodWithAnnotations");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    final Method method;
}
@Deprecated
public void methodWithAnnotations() {
}
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void annotationsByReflection(final Blackhole aBh, final StateWithMethod aState) throws Exception {
    aBh.consume(aState.method.isAnnotationPresent(Deprecated.class)
            || aState.method.getClass().isAnnotationPresent(Deprecated.class));
}
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void annotationsByHashMap(final Blackhole aBh, final StateWithMethodAndHashMap aState) throws Exception {
    aBh.consume(aState.cache.get(aState.method));
}
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void annotationsByConcurrentHashMap(final Blackhole aBh, final StateWithMethodAndConcurrentHashMap aState)
        throws Exception {
    aBh.consume(aState.cache.get(aState.method));
}

JDK 1.8.0_172, Java HotSpot(TM) 64位Server VM, 25.172-b11:

Benchmark                                                   Mode    Cnt      Score      Error   Units
AnnotationCachingBenchmark.annotationsByConcurrentHashMap  thrpt      5      0.152 ±    0.009  ops/ns
AnnotationCachingBenchmark.annotationsByHashMap            thrpt      5      0.144 ±    0.005  ops/ns
AnnotationCachingBenchmark.annotationsByReflection         thrpt      5      0.043 ±    0.001  ops/ns
AnnotationCachingBenchmark.annotationsByConcurrentHashMap   avgt      5      6.610 ±    0.094   ns/op
AnnotationCachingBenchmark.annotationsByHashMap             avgt      5      6.963 ±    0.414   ns/op
AnnotationCachingBenchmark.annotationsByReflection          avgt      5     23.248 ±    0.339   ns/op

JDK 13, OpenJDK 64位Server VM, 13+33:

Benchmark                                                   Mode    Cnt       Score      Error   Units
AnnotationCachingBenchmark.annotationsByConcurrentHashMap  thrpt      5       0.128 ±    0.027  ops/ns
AnnotationCachingBenchmark.annotationsByHashMap            thrpt      5       0.136 ±    0.031  ops/ns
AnnotationCachingBenchmark.annotationsByReflection         thrpt      5       0.139 ±    0.010  ops/ns
AnnotationCachingBenchmark.annotationsByConcurrentHashMap   avgt      5       7.335 ±    1.067   ns/op
AnnotationCachingBenchmark.annotationsByHashMap             avgt      5       6.634 ±    0.184   ns/op
AnnotationCachingBenchmark.annotationsByReflection          avgt      5       7.234 ±    0.567   ns/op

比较1.8和13,你可以清楚地看到注释被JDK缓存的效果。因此,对于最新的jdk,不需要关心缓存这些信息,因为它只会引入额外的开销。

我想这取决于JVM的实现。但是以Oracle JVM为例,它在方法和字段实例上维护了所有注释的缓存,这相当于你所说的map方法。

但是这里有一个陷阱;由于方法/字段实例对于每个对象都是唯一的,因此如果您最终为一个给定的类创建了许多对象,那么您将失去所提供的性能优势。在这种情况下,类名+方法名/类名+字段名到相关注释列表的静态映射胜过所使用的内置缓存方法。

顺便说一句,你是如何预先计算地图的?它是在应用程序启动时还是在一些自动生成的代码上完成的?您是否真的确认在您的案例中缓存注释实例是安全的?

对于这样的问题,最好的解决方案是对你的应用进行配置/测量,并使用在给定用例中看起来成功的解决方案。

相关内容

  • 没有找到相关文章

最新更新