I'm writing some code that calls Field.set
and Field.get
many many thousands of times. Obviously this is very slow because of the reflection.
我正在编写一些调用Field.set和Field.get的代码数千次。显然,由于反射,这是非常缓慢的。
I want to see if I can improve performance using MethodHandles
in Java7. So far here's what I have:
我想看看我是否可以使用Java7中的MethodHandles来提高性能。到目前为止,这就是我所拥有的:
Instead of field.set(pojo, value)
, I'm doing:
而不是field.set(pojo,value),我正在做:
private static final Map<Field, MethodHandle> setHandles = new HashMap<>();
MethodHandle mh = setHandles.get(field);
if (mh == null) {
mh = lookup.unreflectSetter(field);
setHandles.put(field, mh);
}
mh.invoke(pojo, value);
However, this doesn't seem to perform better than the Field.set call using reflection. Am I doing something wrong here?
但是,这似乎没有比使用反射的Field.set调用更好。我在这里做错了吗?
I read that using invokeExact
could be faster but when I tried using that I got a java.lang.invoke.WrongMethodTypeException
我读到使用invokeExact可能会更快,但是当我尝试使用它时,我得到了一个java.lang.invoke.WrongMethodTypeException
Has anyone successfully been able to optimize repeated calls to Field.set or Field.get?
有没有人成功地优化了对Field.set或Field.get的重复调用?
4 个解决方案
#1
56
2015-06-01: Updated to reflect @JoeC's comment about another case when handles are static. Also updated to latest JMH and re-ran on modern hardware. The conclusion stays almost the same.
2015-06-01:更新以反映@ JoeC关于句柄是静态的另一个案例的评论。还更新到最新的JMH并在现代硬件上重新运行。结论几乎保持不变。
Please do proper benchmarking, it is arguably not that hard with JMH. Once you do that, the answer becomes obvious. It can also showcase the proper use of invokeExact
(requires target/source 1.7 to compile and run):
请做适当的基准测试,可以说与JMH并不那么难。一旦你这样做,答案就变得很明显了。它还可以展示invokeExact的正确使用(需要target / source 1.7来编译和运行):
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MHOpto {
private int value = 42;
private static final Field static_reflective;
private static final MethodHandle static_unreflect;
private static final MethodHandle static_mh;
private static Field reflective;
private static MethodHandle unreflect;
private static MethodHandle mh;
// We would normally use @Setup, but we need to initialize "static final" fields here...
static {
try {
reflective = MHOpto.class.getDeclaredField("value");
unreflect = MethodHandles.lookup().unreflectGetter(reflective);
mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
static_reflective = reflective;
static_unreflect = unreflect;
static_mh = mh;
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}
@Benchmark
public int plain() {
return value;
}
@Benchmark
public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) reflective.get(this);
}
@Benchmark
public int dynamic_unreflect_invoke() throws Throwable {
return (int) unreflect.invoke(this);
}
@Benchmark
public int dynamic_unreflect_invokeExact() throws Throwable {
return (int) unreflect.invokeExact(this);
}
@Benchmark
public int dynamic_mh_invoke() throws Throwable {
return (int) mh.invoke(this);
}
@Benchmark
public int dynamic_mh_invokeExact() throws Throwable {
return (int) mh.invokeExact(this);
}
@Benchmark
public int static_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) static_reflective.get(this);
}
@Benchmark
public int static_unreflect_invoke() throws Throwable {
return (int) static_unreflect.invoke(this);
}
@Benchmark
public int static_unreflect_invokeExact() throws Throwable {
return (int) static_unreflect.invokeExact(this);
}
@Benchmark
public int static_mh_invoke() throws Throwable {
return (int) static_mh.invoke(this);
}
@Benchmark
public int static_mh_invokeExact() throws Throwable {
return (int) static_mh.invokeExact(this);
}
}
On 1x4x2 i7-4790K, JDK 8u40, Linux x86_64 it yields:
在1x4x2 i7-4790K,JDK 8u40,Linux x86_64上它产生:
Benchmark Mode Cnt Score Error Units
MHOpto.dynamic_mh_invoke avgt 25 4.393 ± 0.003 ns/op
MHOpto.dynamic_mh_invokeExact avgt 25 4.394 ± 0.007 ns/op
MHOpto.dynamic_reflect avgt 25 5.230 ± 0.020 ns/op
MHOpto.dynamic_unreflect_invoke avgt 25 4.404 ± 0.023 ns/op
MHOpto.dynamic_unreflect_invokeExact avgt 25 4.397 ± 0.014 ns/op
MHOpto.plain avgt 25 1.858 ± 0.002 ns/op
MHOpto.static_mh_invoke avgt 25 1.862 ± 0.015 ns/op
MHOpto.static_mh_invokeExact avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_reflect avgt 25 4.274 ± 0.011 ns/op
MHOpto.static_unreflect_invoke avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_unreflect_invokeExact avgt 25 1.858 ± 0.002 ns/op
...which suggests MH are really much faster than Reflection in this particular case (this is because the access checks against the private field is done at lookup time, and not at the invocation time). dynamic_*
cases simulate the case when the MethodHandles
and/or Fields
are not statically known, e.g. pulled from Map<String, MethodHandle>
or something like it. Conversely, static_*
cases are those where the invokers are statically known.
...这表明在这种特殊情况下MH实际上比Reflection快得多(这是因为对私有字段的访问检查是在查找时完成的,而不是在调用时完成的)。 dynamic_ *个案模拟MethodHandles和/或Fields不是静态已知的情况,例如从Map
Notice the reflective performance is on par with MethodHandles in dynamic_*
cases, this is because reflection is heavily optimized further in JDK 8 (because really, you don't need the access check to read your own fields), so the answer may be "just" switching to JDK 8 ;)
请注意,反射性能与dynamic_ *情况下的MethodHandles相同,这是因为在JDK 8中进一步大大优化了反射(因为实际上,您不需要访问检查来读取您自己的字段),因此答案可能是“只是“切换到JDK 8;)
static_*
cases are even faster, because the MethoHandles.invoke
calls are aggressively inlined. This eliminates part of the type checking in MH cases. But, in reflection cases, there are still quick checks present, and therefore, it lags behind.
static_ * case甚至更快,因为MethoHandles.invoke调用是积极内联的。这消除了MH情况下的部分类型检查。但是,在反思案例中,仍然存在快速检查,因此,它落后了。
#2
12
Update: since some people started a pointless discussion about “how to benchmark” I will emphasize the solution to your problem contained in my answer, now right at the beginning:
更新:由于有些人开始就“如何进行基准测试”进行毫无意义的讨论,我会强调我的答案中包含的问题的解决方案,现在就在开头:
You can use invokeExact
even in your reflective context where you don’t have the exact type signature by converting the MethodHandle
using asType
to a handle taking Object
as arguments. In environments affected by the performance difference between invoke
and invokeExact
, using invokeExact
on such a converting handle is still way faster than using invoke
on a direct method handle.
您可以使用invokeExact,即使在您没有确切类型签名的反射上下文中,也可以使用asType将MethodHandle转换为将Object作为参数的句柄。在受invoke和invokeExact之间性能差异影响的环境中,在这样的转换句柄上使用invokeExact仍然比在直接方法句柄上使用invoke更快。
Original answer:
The problem is indeed that you are not using invokeExact
. Below is a little benchmark program showing the results of different ways of incrementing an int
field. Using invoke
instead of invokeExact
leads to a performance drop below the speed of Reflection.
问题确实是你没有使用invokeExact。下面是一个小的基准程序,显示了增加int字段的不同方法的结果。使用invoke而不是invokeExact会导致性能下降到反射速度以下。
You receive the WrongMethodTypeException
because the MethodHandle
is strongly typed. It expects an exact invocation signature matching type type of the field and owner. But you can use the handle to create a new MethodHandle
wrapping the necessary type conversions. Using invokeExact
on that handle using a generic signature (i.e. (Object,Object)Object
) will be still way more efficient than using invoke
with a dynamic type conversion.
您收到WrongMethodTypeException,因为MethodHandle是强类型的。它期望精确的调用签名匹配字段和所有者的类型类型。但是您可以使用句柄创建一个包含必要类型转换的新MethodHandle。使用通用签名(即(对象,对象)对象)在该句柄上使用invokeExact仍然比使用动态类型转换的调用更有效。
The results on my machine using 1.7.0_40 were:
我的机器使用1.7.0_40的结果是:
direct : 27,415ns reflection : 1088,462ns method handle : 7133,221ns mh invokeExact: 60,928ns generic mh : 68,025ns
and using a -server
JVM yields to a baffling
并使用-server JVM产生令人困惑的结果
direct : 26,953ns reflection : 629,161ns method handle : 1513,226ns mh invokeExact: 22,325ns generic mh : 43,608ns
I don’t think that it has much real life relevance seeing a MethodHandle
being faster than a direct operation but it proves that MethodHandle
s are not slow on Java7.
我不认为它看起来比直接操作更快,但它证明MethodHandles在Java7上并不慢。
And the generic MethodHandle
will still outperform Reflection (whilst using invoke
does not).
通用的MethodHandle仍然优于Reflection(虽然使用invoke不会)。
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class FieldMethodHandle
{
public static void main(String[] args)
{
final int warmup=1_000_000, iterations=1_000_000;
for(int i=0; i<warmup; i++)
{
incDirect();
incByReflection();
incByDirectHandle();
incByDirectHandleExact();
incByGeneric();
}
long direct=0, refl=0, handle=0, invokeExact=0, genericH=0;
for(int i=0; i<iterations; i++)
{
final long t0=System.nanoTime();
incDirect();
final long t1=System.nanoTime();
incByReflection();
final long t2=System.nanoTime();
incByDirectHandle();
final long t3=System.nanoTime();
incByDirectHandleExact();
final long t4=System.nanoTime();
incByGeneric();
final long t5=System.nanoTime();
direct+=t1-t0;
refl+=t2-t1;
handle+=t3-t2;
invokeExact+=t4-t3;
genericH+=t5-t4;
}
final int result = VALUE.value;
// check (use) the value to avoid over-optimizations
if(result != (warmup+iterations)*5) throw new AssertionError();
double r=1D/iterations;
System.out.printf("%-14s:\t%8.3fns%n", "direct", direct*r);
System.out.printf("%-14s:\t%8.3fns%n", "reflection", refl*r);
System.out.printf("%-14s:\t%8.3fns%n", "method handle", handle*r);
System.out.printf("%-14s:\t%8.3fns%n", "mh invokeExact", invokeExact*r);
System.out.printf("%-14s:\t%8.3fns%n", "generic mh", genericH*r);
}
static class MyValueHolder
{
int value;
}
static final MyValueHolder VALUE=new MyValueHolder();
static final MethodHandles.Lookup LOOKUP=MethodHandles.lookup();
static final MethodHandle DIRECT_GET_MH, DIRECT_SET_MH;
static final MethodHandle GENERIC_GET_MH, GENERIC_SET_MH;
static final Field REFLECTION;
static
{
try
{
REFLECTION = MyValueHolder.class.getDeclaredField("value");
DIRECT_GET_MH = LOOKUP.unreflectGetter(REFLECTION);
DIRECT_SET_MH = LOOKUP.unreflectSetter(REFLECTION);
GENERIC_GET_MH = DIRECT_GET_MH.asType(DIRECT_GET_MH.type().generic());
GENERIC_SET_MH = DIRECT_SET_MH.asType(DIRECT_SET_MH.type().generic());
}
catch(NoSuchFieldException | IllegalAccessException ex)
{
throw new ExceptionInInitializerError(ex);
}
}
static void incDirect()
{
VALUE.value++;
}
static void incByReflection()
{
try
{
REFLECTION.setInt(VALUE, REFLECTION.getInt(VALUE)+1);
}
catch(IllegalAccessException ex)
{
throw new AssertionError(ex);
}
}
static void incByDirectHandle()
{
try
{
Object target=VALUE;
Object o=GENERIC_GET_MH.invoke(target);
o=((Integer)o)+1;
DIRECT_SET_MH.invoke(target, o);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
static void incByDirectHandleExact()
{
try
{
DIRECT_SET_MH.invokeExact(VALUE, (int)DIRECT_GET_MH.invokeExact(VALUE)+1);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
static void incByGeneric()
{
try
{
Object target=VALUE;
Object o=GENERIC_GET_MH.invokeExact(target);
o=((Integer)o)+1;
o=GENERIC_SET_MH.invokeExact(target, o);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
}
#3
2
EDIT thanks to holger I noticed that I really should have used invokeExact, so I decided to remove the stuff about other jdks and use invokeExact only... using -server or not still does not really make a difference for me though
编辑感谢holger我注意到我真的应该使用invokeExact,所以我决定删除关于其他jdks的东西并且只使用invokeExact ...使用-server或者仍然没有真正对我有所作为虽然
The main difference between using reflection and using MethodHandles is that for reflection you have a security check for every call, in case of MethodHandles, only for the creation of the handle.
使用反射和使用MethodHandles之间的主要区别在于,对于反射,您对每个调用都进行了安全检查,对于MethodHandles,仅用于创建句柄。
If you look at this
如果你看这个
class Test {
public Object someField;
public static void main(String[] args) throws Exception {
Test t = new Test();
Field field = Test.class.getDeclaredField("someField");
Object value = new Object();
for (int outer=0; outer<50; outer++) {
long start = System.nanoTime();
for (int i=0; i<100000000; i++) {
field.set(t, value);
}
long time = (System.nanoTime()-start)/1000000;
System.out.println("it took "+time+"ms");
}
}
}
Then I get on my computer times 45000ms on jdk7u40 (jdk8 and pre 7u25 perform much better though)
然后我在jdk7u40(jdk8和pre 7u25上表现得好得多)上我的电脑时间45000ms
Now let's look at the same program using handles
现在让我们看看使用句柄的相同程序
class Test {
public Object someField;
public static void main(String[] args) throws Throwable {
Test t = new Test();
Field field = Test.class.getDeclaredField("someField");
MethodHandle mh = MethodHandles.lookup().unreflectSetter(field);
Object value = new Object();
for (int outer=0; outer<50; outer++) {
long start = System.nanoTime();
for (int i=0; i<100000000; i++) {
mh.invokeExact(t, value);
}
long time = (System.nanoTime()-start)/1000000;
System.out.println("it took "+time+"ms");
}
}
}
7u40 says roughly 1288ms. So I can confirm Holger's 30 times on 7u40. On 7u06 this code handles would be slower because reflection was several times faster and on jdk8 everything is new again.
7u40表示大约1288毫秒。所以我可以在7u40确认Holger的30次。在7u06上,这个代码句柄会慢一些,因为反射速度要快几倍,而在jdk8上,一切都是新的。
As for why you didn't see an improvement... difficult to say. What I did was microbenchmarking. That doesn't tell anything about a real application at all. But using those results I would assume you either use an old jdk version, or you don't reuse the handle often enough. Because while executing a handle can be faster, the creation of the handle can cost much more then the creation of a Field.
至于为什么你没有看到改善...很难说。我做的是微基准测试。这根本没有说明真正的应用程序。但是使用这些结果我会假设您使用旧的jdk版本,或者您不经常重用该句柄。因为虽然执行句柄可以更快,但是创建句柄可能比创建字段花费更多。
Now the biggest problem point... I did see you want this for google appengine... And I must say, you can test locally as much as you want, what counts in the end is what the performance of the application on the google site will be. Afaik they use a modified OpenJDK, but what version with what modification they don't say. With Jdk7 being that unstable you could be unlucky or not. Maybe they added special code for reflection, then all bets are off anyway. And even ignoring that... maybe the payment model changed again, but usually you want to avoid datastore access by caching because it costs. If that still holds, is it then realistic that any handle will be called let's say 10.000 times on average?
现在最大的问题点...我确实看到你想要这个谷歌appengine ...而且我必须说,你可以在本地测试你想要的,最重要的是什么应用程序在谷歌上的性能网站将。 Afaik他们使用修改过的OpenJDK,但他们没有说什么修改版本。由于Jdk7不稳定,你可能不幸或不幸。也许他们添加了特殊的反射代码,然后所有的赌注都是关闭的。甚至忽略了......也许支付模式再次改变,但通常你想通过缓存避免数据存储访问,因为它的成本。如果仍然存在,那么现在是否可以调用任何句柄让我们平均说出10.000倍?
#4
2
There's a catch 22 for MethodHandles in JDK 7 and 8 (I haven't tested JDK 9 or higher yet): A MethodHandle is fast (as fast as direct access) if it is in a static field. Otherwise they are as slow as reflection. If your framework reflects over n getter or setters, where is n is unknown at compile time, then MethodHandles are probably useless to you.
在JDK 7和8中有一个针对MethodHandles的捕获22(我还没有测试过JDK 9或更高版本):如果MethodHandle位于静态字段中,它的速度很快(与直接访问一样快)。否则它们和反射一样慢。如果你的框架反映了n getter或setter,那么在编译时n是未知的,那么MethodHandles对你来说可能是无用的。
I wrote an article that benchmarked all the different approaches to speed up reflection.
我写了一篇文章,对所有不同方法进行了基准测试,以加速反思。
Use LambdaMetafactory (or more exotic approaches such as code generation) to speed up calling getters and setters. Here's the gist for a getter (for a setter use a BiConsumer
):
使用LambdaMetafactory(或更奇特的方法,如代码生成)来加速调用getter和setter。这是getter的要点(对于一个使用BiConsumer的setter):
public final class MyAccessor {
private final Function getterFunction;
public MyAccessor() {
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class)),
MethodType.methodType(String.class, Person.class));
getterFunction = (Function) site.getTarget().invokeExact();
}
public Object executeGetter(Object bean) {
return getterFunction.apply(bean);
}
}
#1
56
2015-06-01: Updated to reflect @JoeC's comment about another case when handles are static. Also updated to latest JMH and re-ran on modern hardware. The conclusion stays almost the same.
2015-06-01:更新以反映@ JoeC关于句柄是静态的另一个案例的评论。还更新到最新的JMH并在现代硬件上重新运行。结论几乎保持不变。
Please do proper benchmarking, it is arguably not that hard with JMH. Once you do that, the answer becomes obvious. It can also showcase the proper use of invokeExact
(requires target/source 1.7 to compile and run):
请做适当的基准测试,可以说与JMH并不那么难。一旦你这样做,答案就变得很明显了。它还可以展示invokeExact的正确使用(需要target / source 1.7来编译和运行):
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MHOpto {
private int value = 42;
private static final Field static_reflective;
private static final MethodHandle static_unreflect;
private static final MethodHandle static_mh;
private static Field reflective;
private static MethodHandle unreflect;
private static MethodHandle mh;
// We would normally use @Setup, but we need to initialize "static final" fields here...
static {
try {
reflective = MHOpto.class.getDeclaredField("value");
unreflect = MethodHandles.lookup().unreflectGetter(reflective);
mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
static_reflective = reflective;
static_unreflect = unreflect;
static_mh = mh;
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}
@Benchmark
public int plain() {
return value;
}
@Benchmark
public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) reflective.get(this);
}
@Benchmark
public int dynamic_unreflect_invoke() throws Throwable {
return (int) unreflect.invoke(this);
}
@Benchmark
public int dynamic_unreflect_invokeExact() throws Throwable {
return (int) unreflect.invokeExact(this);
}
@Benchmark
public int dynamic_mh_invoke() throws Throwable {
return (int) mh.invoke(this);
}
@Benchmark
public int dynamic_mh_invokeExact() throws Throwable {
return (int) mh.invokeExact(this);
}
@Benchmark
public int static_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) static_reflective.get(this);
}
@Benchmark
public int static_unreflect_invoke() throws Throwable {
return (int) static_unreflect.invoke(this);
}
@Benchmark
public int static_unreflect_invokeExact() throws Throwable {
return (int) static_unreflect.invokeExact(this);
}
@Benchmark
public int static_mh_invoke() throws Throwable {
return (int) static_mh.invoke(this);
}
@Benchmark
public int static_mh_invokeExact() throws Throwable {
return (int) static_mh.invokeExact(this);
}
}
On 1x4x2 i7-4790K, JDK 8u40, Linux x86_64 it yields:
在1x4x2 i7-4790K,JDK 8u40,Linux x86_64上它产生:
Benchmark Mode Cnt Score Error Units
MHOpto.dynamic_mh_invoke avgt 25 4.393 ± 0.003 ns/op
MHOpto.dynamic_mh_invokeExact avgt 25 4.394 ± 0.007 ns/op
MHOpto.dynamic_reflect avgt 25 5.230 ± 0.020 ns/op
MHOpto.dynamic_unreflect_invoke avgt 25 4.404 ± 0.023 ns/op
MHOpto.dynamic_unreflect_invokeExact avgt 25 4.397 ± 0.014 ns/op
MHOpto.plain avgt 25 1.858 ± 0.002 ns/op
MHOpto.static_mh_invoke avgt 25 1.862 ± 0.015 ns/op
MHOpto.static_mh_invokeExact avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_reflect avgt 25 4.274 ± 0.011 ns/op
MHOpto.static_unreflect_invoke avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_unreflect_invokeExact avgt 25 1.858 ± 0.002 ns/op
...which suggests MH are really much faster than Reflection in this particular case (this is because the access checks against the private field is done at lookup time, and not at the invocation time). dynamic_*
cases simulate the case when the MethodHandles
and/or Fields
are not statically known, e.g. pulled from Map<String, MethodHandle>
or something like it. Conversely, static_*
cases are those where the invokers are statically known.
...这表明在这种特殊情况下MH实际上比Reflection快得多(这是因为对私有字段的访问检查是在查找时完成的,而不是在调用时完成的)。 dynamic_ *个案模拟MethodHandles和/或Fields不是静态已知的情况,例如从Map
Notice the reflective performance is on par with MethodHandles in dynamic_*
cases, this is because reflection is heavily optimized further in JDK 8 (because really, you don't need the access check to read your own fields), so the answer may be "just" switching to JDK 8 ;)
请注意,反射性能与dynamic_ *情况下的MethodHandles相同,这是因为在JDK 8中进一步大大优化了反射(因为实际上,您不需要访问检查来读取您自己的字段),因此答案可能是“只是“切换到JDK 8;)
static_*
cases are even faster, because the MethoHandles.invoke
calls are aggressively inlined. This eliminates part of the type checking in MH cases. But, in reflection cases, there are still quick checks present, and therefore, it lags behind.
static_ * case甚至更快,因为MethoHandles.invoke调用是积极内联的。这消除了MH情况下的部分类型检查。但是,在反思案例中,仍然存在快速检查,因此,它落后了。
#2
12
Update: since some people started a pointless discussion about “how to benchmark” I will emphasize the solution to your problem contained in my answer, now right at the beginning:
更新:由于有些人开始就“如何进行基准测试”进行毫无意义的讨论,我会强调我的答案中包含的问题的解决方案,现在就在开头:
You can use invokeExact
even in your reflective context where you don’t have the exact type signature by converting the MethodHandle
using asType
to a handle taking Object
as arguments. In environments affected by the performance difference between invoke
and invokeExact
, using invokeExact
on such a converting handle is still way faster than using invoke
on a direct method handle.
您可以使用invokeExact,即使在您没有确切类型签名的反射上下文中,也可以使用asType将MethodHandle转换为将Object作为参数的句柄。在受invoke和invokeExact之间性能差异影响的环境中,在这样的转换句柄上使用invokeExact仍然比在直接方法句柄上使用invoke更快。
Original answer:
The problem is indeed that you are not using invokeExact
. Below is a little benchmark program showing the results of different ways of incrementing an int
field. Using invoke
instead of invokeExact
leads to a performance drop below the speed of Reflection.
问题确实是你没有使用invokeExact。下面是一个小的基准程序,显示了增加int字段的不同方法的结果。使用invoke而不是invokeExact会导致性能下降到反射速度以下。
You receive the WrongMethodTypeException
because the MethodHandle
is strongly typed. It expects an exact invocation signature matching type type of the field and owner. But you can use the handle to create a new MethodHandle
wrapping the necessary type conversions. Using invokeExact
on that handle using a generic signature (i.e. (Object,Object)Object
) will be still way more efficient than using invoke
with a dynamic type conversion.
您收到WrongMethodTypeException,因为MethodHandle是强类型的。它期望精确的调用签名匹配字段和所有者的类型类型。但是您可以使用句柄创建一个包含必要类型转换的新MethodHandle。使用通用签名(即(对象,对象)对象)在该句柄上使用invokeExact仍然比使用动态类型转换的调用更有效。
The results on my machine using 1.7.0_40 were:
我的机器使用1.7.0_40的结果是:
direct : 27,415ns reflection : 1088,462ns method handle : 7133,221ns mh invokeExact: 60,928ns generic mh : 68,025ns
and using a -server
JVM yields to a baffling
并使用-server JVM产生令人困惑的结果
direct : 26,953ns reflection : 629,161ns method handle : 1513,226ns mh invokeExact: 22,325ns generic mh : 43,608ns
I don’t think that it has much real life relevance seeing a MethodHandle
being faster than a direct operation but it proves that MethodHandle
s are not slow on Java7.
我不认为它看起来比直接操作更快,但它证明MethodHandles在Java7上并不慢。
And the generic MethodHandle
will still outperform Reflection (whilst using invoke
does not).
通用的MethodHandle仍然优于Reflection(虽然使用invoke不会)。
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class FieldMethodHandle
{
public static void main(String[] args)
{
final int warmup=1_000_000, iterations=1_000_000;
for(int i=0; i<warmup; i++)
{
incDirect();
incByReflection();
incByDirectHandle();
incByDirectHandleExact();
incByGeneric();
}
long direct=0, refl=0, handle=0, invokeExact=0, genericH=0;
for(int i=0; i<iterations; i++)
{
final long t0=System.nanoTime();
incDirect();
final long t1=System.nanoTime();
incByReflection();
final long t2=System.nanoTime();
incByDirectHandle();
final long t3=System.nanoTime();
incByDirectHandleExact();
final long t4=System.nanoTime();
incByGeneric();
final long t5=System.nanoTime();
direct+=t1-t0;
refl+=t2-t1;
handle+=t3-t2;
invokeExact+=t4-t3;
genericH+=t5-t4;
}
final int result = VALUE.value;
// check (use) the value to avoid over-optimizations
if(result != (warmup+iterations)*5) throw new AssertionError();
double r=1D/iterations;
System.out.printf("%-14s:\t%8.3fns%n", "direct", direct*r);
System.out.printf("%-14s:\t%8.3fns%n", "reflection", refl*r);
System.out.printf("%-14s:\t%8.3fns%n", "method handle", handle*r);
System.out.printf("%-14s:\t%8.3fns%n", "mh invokeExact", invokeExact*r);
System.out.printf("%-14s:\t%8.3fns%n", "generic mh", genericH*r);
}
static class MyValueHolder
{
int value;
}
static final MyValueHolder VALUE=new MyValueHolder();
static final MethodHandles.Lookup LOOKUP=MethodHandles.lookup();
static final MethodHandle DIRECT_GET_MH, DIRECT_SET_MH;
static final MethodHandle GENERIC_GET_MH, GENERIC_SET_MH;
static final Field REFLECTION;
static
{
try
{
REFLECTION = MyValueHolder.class.getDeclaredField("value");
DIRECT_GET_MH = LOOKUP.unreflectGetter(REFLECTION);
DIRECT_SET_MH = LOOKUP.unreflectSetter(REFLECTION);
GENERIC_GET_MH = DIRECT_GET_MH.asType(DIRECT_GET_MH.type().generic());
GENERIC_SET_MH = DIRECT_SET_MH.asType(DIRECT_SET_MH.type().generic());
}
catch(NoSuchFieldException | IllegalAccessException ex)
{
throw new ExceptionInInitializerError(ex);
}
}
static void incDirect()
{
VALUE.value++;
}
static void incByReflection()
{
try
{
REFLECTION.setInt(VALUE, REFLECTION.getInt(VALUE)+1);
}
catch(IllegalAccessException ex)
{
throw new AssertionError(ex);
}
}
static void incByDirectHandle()
{
try
{
Object target=VALUE;
Object o=GENERIC_GET_MH.invoke(target);
o=((Integer)o)+1;
DIRECT_SET_MH.invoke(target, o);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
static void incByDirectHandleExact()
{
try
{
DIRECT_SET_MH.invokeExact(VALUE, (int)DIRECT_GET_MH.invokeExact(VALUE)+1);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
static void incByGeneric()
{
try
{
Object target=VALUE;
Object o=GENERIC_GET_MH.invokeExact(target);
o=((Integer)o)+1;
o=GENERIC_SET_MH.invokeExact(target, o);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
}
#3
2
EDIT thanks to holger I noticed that I really should have used invokeExact, so I decided to remove the stuff about other jdks and use invokeExact only... using -server or not still does not really make a difference for me though
编辑感谢holger我注意到我真的应该使用invokeExact,所以我决定删除关于其他jdks的东西并且只使用invokeExact ...使用-server或者仍然没有真正对我有所作为虽然
The main difference between using reflection and using MethodHandles is that for reflection you have a security check for every call, in case of MethodHandles, only for the creation of the handle.
使用反射和使用MethodHandles之间的主要区别在于,对于反射,您对每个调用都进行了安全检查,对于MethodHandles,仅用于创建句柄。
If you look at this
如果你看这个
class Test {
public Object someField;
public static void main(String[] args) throws Exception {
Test t = new Test();
Field field = Test.class.getDeclaredField("someField");
Object value = new Object();
for (int outer=0; outer<50; outer++) {
long start = System.nanoTime();
for (int i=0; i<100000000; i++) {
field.set(t, value);
}
long time = (System.nanoTime()-start)/1000000;
System.out.println("it took "+time+"ms");
}
}
}
Then I get on my computer times 45000ms on jdk7u40 (jdk8 and pre 7u25 perform much better though)
然后我在jdk7u40(jdk8和pre 7u25上表现得好得多)上我的电脑时间45000ms
Now let's look at the same program using handles
现在让我们看看使用句柄的相同程序
class Test {
public Object someField;
public static void main(String[] args) throws Throwable {
Test t = new Test();
Field field = Test.class.getDeclaredField("someField");
MethodHandle mh = MethodHandles.lookup().unreflectSetter(field);
Object value = new Object();
for (int outer=0; outer<50; outer++) {
long start = System.nanoTime();
for (int i=0; i<100000000; i++) {
mh.invokeExact(t, value);
}
long time = (System.nanoTime()-start)/1000000;
System.out.println("it took "+time+"ms");
}
}
}
7u40 says roughly 1288ms. So I can confirm Holger's 30 times on 7u40. On 7u06 this code handles would be slower because reflection was several times faster and on jdk8 everything is new again.
7u40表示大约1288毫秒。所以我可以在7u40确认Holger的30次。在7u06上,这个代码句柄会慢一些,因为反射速度要快几倍,而在jdk8上,一切都是新的。
As for why you didn't see an improvement... difficult to say. What I did was microbenchmarking. That doesn't tell anything about a real application at all. But using those results I would assume you either use an old jdk version, or you don't reuse the handle often enough. Because while executing a handle can be faster, the creation of the handle can cost much more then the creation of a Field.
至于为什么你没有看到改善...很难说。我做的是微基准测试。这根本没有说明真正的应用程序。但是使用这些结果我会假设您使用旧的jdk版本,或者您不经常重用该句柄。因为虽然执行句柄可以更快,但是创建句柄可能比创建字段花费更多。
Now the biggest problem point... I did see you want this for google appengine... And I must say, you can test locally as much as you want, what counts in the end is what the performance of the application on the google site will be. Afaik they use a modified OpenJDK, but what version with what modification they don't say. With Jdk7 being that unstable you could be unlucky or not. Maybe they added special code for reflection, then all bets are off anyway. And even ignoring that... maybe the payment model changed again, but usually you want to avoid datastore access by caching because it costs. If that still holds, is it then realistic that any handle will be called let's say 10.000 times on average?
现在最大的问题点...我确实看到你想要这个谷歌appengine ...而且我必须说,你可以在本地测试你想要的,最重要的是什么应用程序在谷歌上的性能网站将。 Afaik他们使用修改过的OpenJDK,但他们没有说什么修改版本。由于Jdk7不稳定,你可能不幸或不幸。也许他们添加了特殊的反射代码,然后所有的赌注都是关闭的。甚至忽略了......也许支付模式再次改变,但通常你想通过缓存避免数据存储访问,因为它的成本。如果仍然存在,那么现在是否可以调用任何句柄让我们平均说出10.000倍?
#4
2
There's a catch 22 for MethodHandles in JDK 7 and 8 (I haven't tested JDK 9 or higher yet): A MethodHandle is fast (as fast as direct access) if it is in a static field. Otherwise they are as slow as reflection. If your framework reflects over n getter or setters, where is n is unknown at compile time, then MethodHandles are probably useless to you.
在JDK 7和8中有一个针对MethodHandles的捕获22(我还没有测试过JDK 9或更高版本):如果MethodHandle位于静态字段中,它的速度很快(与直接访问一样快)。否则它们和反射一样慢。如果你的框架反映了n getter或setter,那么在编译时n是未知的,那么MethodHandles对你来说可能是无用的。
I wrote an article that benchmarked all the different approaches to speed up reflection.
我写了一篇文章,对所有不同方法进行了基准测试,以加速反思。
Use LambdaMetafactory (or more exotic approaches such as code generation) to speed up calling getters and setters. Here's the gist for a getter (for a setter use a BiConsumer
):
使用LambdaMetafactory(或更奇特的方法,如代码生成)来加速调用getter和setter。这是getter的要点(对于一个使用BiConsumer的setter):
public final class MyAccessor {
private final Function getterFunction;
public MyAccessor() {
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class)),
MethodType.methodType(String.class, Person.class));
getterFunction = (Function) site.getTarget().invokeExact();
}
public Object executeGetter(Object bean) {
return getterFunction.apply(bean);
}
}