传奇版本下载后如何架设传奇_城市表演传奇

您是否听说过这位老太太试图用微波炉擦干被雨水浸湿的狗(假)? 还是坚持认为灯塔拥有通行权的航空母舰指挥官(错误)? 还是鳄鱼如何生活在纽约的下水道中(假)? 还是邮车凭借其联邦身份(也有误)而拥有通行于当地应急车辆(包括警车,消防车和救护车)的通行权? 现在,说实话-即使您对它们的真实性不信服,甚至完全持怀疑态度,您将这些故事转发或转发给他人多少次?

都市传说之所以持续存在,是因为它们之间存在着某种联系,使得它们似乎有足够的理由被重新提及。 不幸的是,城市传说并不仅仅局限于将鳄鱼宝宝冲进马桶的故事。 程序员之间有很多坏建议,例如是什么使Java程序性能良好或较差,以及与鳄鱼故事一样科学准确。 但这似乎很合理,因此它会被重新提醒,而且我们大多数人从不费心去质疑或实验验证这些理论。

本月,我将看几篇有关Java性能调整的一般性陈述,这些陈述具有许多城市传奇的特征。 他们中的一些人实际上有一定的根据,但都没有适当地提升为表演福音的地位。

城市绩效传奇#1:同步真的很慢

是非题:同步方法比等效的非同步方法慢五十倍? Dov Bulka在有关低级性能调整的其他方面不错的书中介绍了这一小瑰宝,并且在许多其他资料中也有重复。 像所有城市传说一样,它实际上有一定的根据。 公平地说,在遥远的某个时候(也许是JDK 1.0的日子),对于Bulka来说,如果您运行了清单1中的testSync ,那么它的运行时间可能比testUnsync testSync了50倍。

即使这曾经是真的,但现在已经不复存在了。 自JDK 1.0起,JVM有了巨大的进步。 同步的实现效率更高,并且JVM有时能够识别出同步实际上并没有保护任何数据,因此可以将其消除。 但更重要的是, 清单1中的微基准从根本上来说是有缺陷的。 首先,微基准测试很少能衡量您认为他们正在衡量的内容。 在存在动态编译的情况下,您不知道JVM决定将字节码转换为本机代码或何时转换,因此您无法真正将苹果与苹果进行比较。

此外,你不知道编译器或JVM被优化掉-一些Java编译将完全优化掉,以电话unsyncMethod ,因为它什么也不做,和其他人也可以优化掉syncMethod ,或在同步syncMethod ,因为它也什么也没做。 您的编译器优化了哪些?在什么情况下? 您不知道,但是几乎可以肯定它扭曲了测量结果。

无论实际数量如何,得出这样的结论:未同步的方法调用的速度比此类基准测试中的同步方法快X倍。 同步可能会给代码块增加恒定的开销,而不会使它减慢恒定的因子。 块中有多少代码将极大地影响清单1计算的“比率”。同步开销(执行空方法的时间的百分比)是没有意义的数字。

一旦开始将实际同步方法的运行时与现代JVM上非同步方法的运行时进行比较,您会发现开销几乎与经常被困扰的“ 50倍”警报器差不多。 轻松阅读Threading系列文章的第1部分“同步不是大敌”(请参阅参考资料 ),以粗略和不科学地衡量同步的开销。 可以肯定的是,无竞争的同步会有一些开销(竞争性同步还有更多开销),但是同步并不是许多人担心的下水道,吃性能的鳄鱼。

清单1.测量同步开销的有缺陷的微基准
public static final int N_ITERATIONS = 10000000;

    public static synchronized void syncMethod() {
    }

    public static void unsyncMethod() {
    }

    public static void testSync() {
        for (int i=0; i<N_ITERATIONS; i++)
            syncMethod();
    }

    public static void testUnsync() {
        for (int i=0; i<N_ITERATIONS; i++)
            unsyncMethod();
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        testSync();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Synchronized took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        testUnsync();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Unsynchronized took " + tElapsed + " ms");
    }

“同步缓慢”的神话是一个非常危险的神话,因为它促使程序员损害其程序的线程安全性,从而避免察觉到的性能危害。 实际上,他们经常认为这样做很聪明。 正是出于对这种神话的恐惧,启发了开发人员和编写者,以推广聪明的,但致命的“双重检查锁定”习惯用法,这种习惯似乎消除了通用代码路径中的同步,但实际上损害了线程安全性。您的代码。 线程安全性问题是代码中的定时炸弹正等着启动,当它们炸开时,它们会在最坏的时间自动关闭-当程序负载很大时。 合理的性能问题是破坏程序的线程安全性的不好原因。 害怕表现神话是一个更糟糕的原因。

城市绩效传奇#2:将类或方法声明为final使它们更快

我在10月的专栏中讨论了这个神话(请参阅参考资料 ),因此在这里我不会对其进行详细介绍。 许多文章建议将类或方法设置为final ,因为这样可使编译器更容易地内联它们,因此应导致更好的性能。 这是一个很好的理论。 太糟糕了,这不是真的。

这个神话比同步神话更有趣,因为没有数据可以支持它- 似乎是合理的(至少同步神话有一个支持它的微基准)。 一定有人决定以这种方式行事,充满信心地讲述了这个故事,故事一旦开始,就流传开了。

就像同步神话一样,这种神话的危险在于,它会导致开发人员为了获得不存在的性能优势而折衷良好的面向对象设计原则。 是否使某个类final定级是一个设计决策,应通过分析该类的功能,如何使用它以及由谁使用,以及是否可以设想扩展该类的方式来进行设计。 制作类final ,因为它是不可变的是一个很好的理由这样做; 做一个复杂的类final ,因为它没有被设计为可扩展也是一个很好的理由。 将班级定为final班级是因为您在某个地方读到它会更快地运行(即使它是正确的)也不是。

城市绩效传奇3:不可变的对象不利于性能

通常使用不可变的对象(例如String )来描述更改的数据-当数据更改时,您将创建一个新对象而不是改变其状态。 可变对象和不可变对象之间的性能折衷比较复杂。 根据对象在程序中的使用方式,您可能会发现不可变对象实际上具有性能上的优势(因为您不必防御性地复制它们)。 另一方面,您可能会发现它们施加了很大的性能损失(因为您正在建模频繁更改的数据并因此创建了新对象)。 再说一遍,您可能无法衡量差异。

“不变对象慢”的神话源于更普遍的性能原理,即创建许多临时对象不利于性能。 尽管创建临时对象肯定需要为分配器和垃圾回收器做额外的工作,但JVM在减轻临时对象创建对性能的影响方面已变得更好。 对象创建对性能的影响虽然肯定是真实的,但在大多数程序中并不像过去或现在被认为那样重要。

考虑一下不可变的StringHolder类和可变的类之间的区别,如清单2所示。在一种情况下,如果您想更改其中包含的字符串,则可以创建一个StringHolder的新实例; 另一方面,您将在现有的StringHolder上调用setter方法来设置包含的字符串。 举一个具体的例子,假设您使用定界符包装了一个字符串。 这两种方法的性能如何比较?

清单2.不可变与可变StringHolder类
// mutable 
	stringHolder.setString("/" + stringHolder.getString() + "/"); 

	// immutable
	stringHolder = new StringHolder("/" + stringHolder.getString() + "/");

如果您认为额外的StringHolder对象创建对净性能产生了很大的影响,那您将错了。 可变方法也可以创建大量对象。 为了执行字符串连接,创建一个StringBuffer对象,这需要创建一个char数组,然后创建一个String对象来描述最终的字符数组。 如果结果字符串长于StringBuffer使用的默认缓冲区大小,则将重新分配内部字符数组,从而创建一个或多个其他对象。 因此,可变方法至少涉及三个对象创建,而不变方法则涉及另外三个。 在性能上可能存在差异,但是在不创建对象与进行大量对象创建之间几乎没有区别。

在这里,与垃圾回收之间也存在大量但难以度量的交互,这也可能会影响性能。 当新对象引用旧对象时,现代代垃圾收集器的性能要好得多,而不是相反。 创建一个新的不可变持有者对象将产生完全相同的配置。 更改现有容器对象以引用新创建的字符串的操作与此相反。

就像前两个神话一样,这鼓励程序员为了性能上的利益而牺牲良好的面向对象设计原则。 与可变对象相比,不可变对象更容易编写,维护和使用,并且不易出错。 您是否应该为了性能而放弃这些好处? 也许可以,但是只有在您确信自己遇到了性能问题后,才知道其原因,并且知道打破一个特定类的不变性将帮助您实现性能目标。 在没有表现出性能问题和明确的性能目标的情况下,您应该在程序正确性方面而不是在更高的性能上犯错。

得到教训

所有这些性能传奇都有几个共同的主题。 所有这些均源于Java技术在早期就提出的性能要求,然后才投入大量精力来改善JVM的性能。 最初提到它们时,其中一些是正确的,但是从那时起,JVM的性能有了很大提高。 虽然我们当然不应该忽略同步和对象创建对性能的影响,但我们不应该不惜一切代价将它们提升为要避免的构造状态。

考虑具体的性能目标进行优化

对于这些神话中的每一个,危险都是相同的:损害良好的设计原则(或更糟糕的是程序的正确性)以获得可疑的性能优势。 优化总是会带来风险,例如破坏已经可用的代码,使代码更加复杂并因此引入更多的潜在错误,限制代码的通用性或可重用性,引入约束或使代码更难以理解和维护。 除非出现明显的性能问题,否则通常最好还是在清晰度,简洁的设计和正确性方面犯错。 在实际需要提高性能的情况下保存优化,并采用将产生可测量差异的优化。

性能建议的保质期短

任何给定技术的性能都不是该技术固有的,它还受其运行环境的影响,并且程序执行环境也在不断变化。 编译器变得更聪明; 处理器变得更快; 图书馆得到更新; 垃圾收集和调度算法的改变; 处理器,高速缓存,主内存和I / O设备的相对速度和成本会随着时间而变化。 如果技术A比十年前的技术B快十倍,那么今天不要对它们的相对性能承担太多。 性能观察仅具有较短的保存期限。 当遇到性能建议时,请问在您接受它之前是否可能已经过时。

鉴于如此多的性能建议会很快变得过时,请更加怀疑您所听到的性能提示的有效性,并在将其应用于工作代码时更加保守。 首先询问更改是否会真正提高应用程序的性能,以及您的应用程序是否根本需要提高性能。

而且,如果您收到一封电子邮件,说明比尔·盖茨将向您转发邮件的每个人给您十美元,请不要将其发送给我。


翻译自: https://www.ibm.com/developerworks/java/library/j-jtp04223/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值