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

发布于 2024-12-15 05:06:41 字数 204 浏览 2 评论 0原文

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

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

I'd like to know if there are any comparison/studies about the performance of repeatidly calling (in Java) the Method.getAnnotation(Class) and Field.getAnnotation(Class) methods, versus storing (at program start up time) a precomputed Map with this metadata information of the classes and querying it repeatidly later. Which one would provide the best runtime performance?

And this performance would be the same under Java 5, 6 and 7?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

挖鼻大婶 2024-12-22 05:06:41

地图应该是一种更可取的方法。主要问题不仅仅是缓存。而且还改善了多线程争用。在Method.getAnnotation()中,它调用同步私有方法declaredAnnotations()。同步方法对高线程应用程序有不好的影响。

Map should be a more preferable approach. The main issue is not only about caching. But also improve the multi thread contention. In the Method.getAnnotation(), it calls a synchronized private method declaredAnnotations(). Synchronized method have bad impact on highly threaded application.

虐人心 2024-12-22 05:06:41

我知道这是一个相当老的问题,但最新 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 位服务器 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 位服务器 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 也是如此无需关心缓存此信息,因为它只会带来额外的开销。

I know this is some rather old question but the result for more recent JDKs might still be of interest.

I wrote some JMH benchmark to get an idea of the impact caching of annotation information might have:

@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-Bit 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-Bit 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

You can clearly see the effect of annotations being cached by the JDK comparing 1.8 to 13. So with recent JDKs there is no need to care about caching this information as it would only introduce additional overhead.

红焚 2024-12-22 05:06:41

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

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

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

与往常一样,对于此类问题,最好的解决方案是使用您的应用程序在线进行分析/测量,并采用在给定用例中看起来像是胜利的解决方案。

I guess this depends a bit on the JVM implementation. But taking the example of Oracle JVM, it maintains a cache of all the annotations on the method and field instances which is equivalent to the map approach you speak of.

But there is a catch here; since method/field instances are unique to each object, in case you end up creating a lot of objects for a given class, you pretty much lose out on the performance benefit offered. It is in this case a static map of class-name + method-name / class-name + field name to the relevant annotation list trumps the in-built caching approach used.

BTW, how are you pre-computing the map? Is it done on application start-up or some auto-generated code? Have you really confirmed that it is safe to cache the annotation instances in your case?

As always, for such questions, the best solution is to profile/measure with your app on the line and go with the solution which looks like win in the given use case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文