至少在java中,代理模式有很多开销 - 我不记得确切的数字,但是当包装微小的方法时,代理需要花费50倍于包装方法的时间。例如,这就是为什么java.awt.image.BufferedImage.setRGB
& getRGB
真的慢;包含实际byte[]
的约有三个代理。
为什么 50次?!为什么代理不会加倍?
<小时/> 编辑:=(
正如SO的常见情况一样,我得到了一堆答案,告诉我我的问题是错的。不是。查看BufferedImage或其他一些真正的代理模式,而不是那些微基准测试。事实上,如果你必须对BufferedImage进行大量像素操作并且你知道它的结构,你可以通过手动撤消代理来实现所谓的巨大加速;见this answer。
哦,here's我的50倍来源。正如文章详细说明的那样,代理在包装需要很长时间后没有明显的损失,但如果你用一个小方法包装它们确实会产生很大的痛苦。
答案 0 :(得分:7)
我不知道“50倍”这个数字来自哪里,但这是非常可疑的。可能是特定代理明显慢于它代理的代理,这取决于它们每个人正在做什么,但是从中推断出“代理模式如此之慢”就是采取逻辑上一个非常戏剧性和高度可疑的飞跃。
试试这个:
Thingy.java
:
public class Thingy
{
public int foo(int param1, int param2)
{
return param2 - param1;
}
}
ThingyProxy.java
:
public class ThingyProxy
{
Thingy thingy;
public ThingyProxy()
{
this.thingy = new Thingy();
}
public int foo(int param1, int param2)
{
return this.thingy.foo(param1, param2);
}
}
WithoutProxy.java
:
public class WithoutProxy
{
public static final void main(String[] args)
{
Thingy t;
int sum;
int counter;
int loops;
sum = 0;
t = new Thingy();
for (loops = 0; loops < 300000000; ++loops) {
sum = 0;
for (counter = 0; counter < 100000000; ++counter) {
sum += t.foo(1, 2);
}
if (sum != 100000000) {
System.out.println("ERROR");
return;
}
}
System.exit(0);
}
}
WithProxy.java
:
public class WithProxy
{
public static final void main(String[] args)
{
ThingyProxy t;
int sum;
int counter;
int loops;
sum = 0;
t = new ThingyProxy();
for (loops = 0; loops < 300000000; ++loops) {
sum = 0;
for (counter = 0; counter < 100000000; ++counter) {
sum += t.foo(1, 2);
}
if (sum != 100000000) {
System.out.println("ERROR");
return;
}
}
System.exit(0);
}
}
在我的机器上进行简单的试验:
$ time java WithoutProxy real 0m0.894s user 0m0.900s sys 0m0.000s $ time java WithProxy real 0m0.934s user 0m0.940s sys 0m0.000s $ time java WithoutProxy real 0m0.883s user 0m0.850s sys 0m0.040s $ time java WithProxy real 0m0.937s user 0m0.920s sys 0m0.030s $ time java WithoutProxy real 0m0.898s user 0m0.880s sys 0m0.030s $ time java WithProxy real 0m0.936s user 0m0.950s sys 0m0.000s
稍慢?是。慢50倍?否。
现在,JVM的计时非常困难,如上所述的简单实验必然是可疑的。但我认为可能会出现50倍的差异。
编辑:我应该已经提到,上面有非常非常少量的循环会发布如下数字:
real 0m0.058s user 0m0.040s sys 0m0.020s
...它让您了解环境中的VM启动时间。例如,上面的时间主要不是VM启动,实际执行时间差异只有一微秒,它们主要是执行时间。
答案 1 :(得分:4)
当代码编译成本机代码时,字节数组访问将类似于3个循环指令(只要源和目标数据在高速缓存中很热,未对齐的字节访问不会受到惩罚.YMMV取决于平台)。
添加一个方法调用来存储这四个字节(取决于平台,但是类似的东西)将推送寄存器添加到堆栈,调用指令,数组访问指令,返回指令以及从堆栈中弹出寄存器。将为每个层或代理添加推/调/返回/弹出序列,并且这些指令中的大多数都不会在1个周期内执行。如果编译器无法内联这些方法(这可能很容易发生),那么你将遭受相当大的惩罚。
代理添加了在颜色深度等之间进行转换的功能,增加了额外的开销。
此外,编译器可以进一步优化顺序数组访问(例如,将存储操作转换为多字节访问操作 - 一次最多8位,同时仍然只需要1个周期),其中代理调用使其变得困难。
50x听起来有点高,但并非不合理,具体取决于实际代码。
BufferedImage特别增加了大量开销。虽然代理模式本身可能没有增加可识别的开销,但BufferedImage的使用可能会这样做。请特别注意setRGB()是同步的,在某些情况下可能会产生严重的性能影响。
答案 2 :(得分:2)
我见过的一个地方有所不同的是代码没有做任何事情。 JVM可以检测不执行任何操作的代码可以消除它。但是,使用方法调用可能会混淆此检查,并且不会消除代码。如果你比较这些例子中的时间和方法,你可以得到你想要的任何比例,但是如果你看一下无方法测试的进展情况,你会发现代码已经被淘汰并且不合理地快速进行。例如比每个循环一个时钟周期快得多。
简单的方法是内联的,比如getter和setter。它们根本不会对性能产生任何影响。我非常怀疑50倍的真实节目声称。如果测试得当,我会期待更接近无差异。