是否有一个Java字节码优化器可以删除无用的GOTO?

是否有一个Java字节码优化器可以删除无用的GOTO?,java,bytecode,goto,jvm-hotspot,optimization,Java,Bytecode,Goto,Jvm Hotspot,Optimization,问题:我有一个方法可以编译超过8000字节的Java字节码。HotSpot有一个神奇的限制,使超过8000字节的方法不能使用JIT。(是的,有一个巨大的方法是合理的。这是一个标记器循环。)该方法在一个库中,我不想要求库的用户必须配置HotSpot来停用魔法限制 观察:反编译字节码表明EclipseJava编译器生成了大量无意义的GOTO。(javac甚至更糟)也就是说,有些goto只能通过跳转来访问。显然,跳转到goto的跳转应该直接跳转到goto跳转的地方,并且goto应该被消除 问:是否有一

问题:我有一个方法可以编译超过8000字节的Java字节码。HotSpot有一个神奇的限制,使超过8000字节的方法不能使用JIT。(是的,有一个巨大的方法是合理的。这是一个标记器循环。)该方法在一个库中,我不想要求库的用户必须配置HotSpot来停用魔法限制

观察:反编译字节码表明EclipseJava编译器生成了大量无意义的GOTO。(javac甚至更糟)也就是说,有些goto只能通过跳转来访问。显然,跳转到goto的跳转应该直接跳转到goto跳转的地方,并且goto应该被消除

问:是否有一个用于Java5类文件的字节码优化器,它可以展平无意义的跳转链,然后删除不必要的GOTO

编辑:我的意思是像这样的模式:

8698:   goto    8548
8701:   goto    0
显然,第二次转到只能通过跳到8701才能到达,这也可能是直接跳到0

在第二次调查中,这种可疑模式更为常见:

4257:   if_icmpne   4263
4260:   goto    8704
4263:   aload_0

显然,我们希望编译器将“不相等”比较转换为“相等”比较,跳转到8704并消除goto。

一种方法编译到8000字节以上?有人懂那个密码吗?是可测试的吗?尝试将其拆分为多个(私有?)方法,使用有意义的全名,而不是与优化器争论


好吧,也许有一些案例是合法的大型方法。但是很抱歉,问题中没有提示。

我感觉到了你的痛苦。我曾经不得不编写一个解析器,它有大约5kloc的if(str.equals(…)代码。我按照parse1、parse2等的思路分了几个方法。如果parse1没有得到解析的答案,那么就调用parse2,等等。这不一定是最佳实践,但它确实可以满足您的需要。

如果您不使用调试符号(即javac中的-g标志)进行编译,会有什么不同吗?这可能会使该方法降到魔法极限以下。

是否不可能将该方法重构为子方法?不管怎样,现代JIT都将这些调用内联。

如果它是一个标记器循环,那么最好使用一组映射和一点反射(视情况而定)来实现它吗

因此,您可以将令牌匹配项存储在一个结构中,该结构将它们映射到有关该令牌的语法和实现相关函数的方法的数据。可以在整个结构上优化查找,从而避免大循环

这就带来了保持数据和实现同步的问题,但是您可以使用doclet或注释从代码库生成数据


在不知道您的大方法具体做什么的情况下,我们仅限于尝试以您认为最好的方式对其进行优化(而且显然无论如何这是不可能的)。

如果您在类上运行字节码收缩器/模糊器,您的性能会提高吗?e、 g.,yguard,proguard

也许您可以使用asm编写类文件后处理器,因为您的用例非常具体


即使你删除了所有无意义的GOTO,这是否会使你处于神奇的极限之下?

我以前听说过的一系列提及和,以及许多其他做各种事情的人。

一些架构对相对分支可以走多远有限制(因为它们将地址保存在8或16位寄存器中)因此,他们通常通过使用完整程序计数器大小的相对分支到非相对分支来解决这个问题。JVM是这样的吗?你的意思是标签只能从跳转中访问吗?在这种情况下,给JVM一个提示的运行时注释似乎很好……但我不认为存在这样的事情(而且快速的google不会发现任何东西。)有趣的问题。为什么JIT不适用于超过神奇极限的方法?有没有一个很好的工程原因呢?我想我对相对跳跃的看法是错误的-我只是看了一下,if_cmpne和goto都接受16位带符号的整数。听起来他在写一个解析器-标记器循环变大实际上是非常合理的,而且把它放在一起实际上更可读。(我曾有人强迫我放弃这种方法,因为它超过n行,下个月他们抱怨说,因为它更难遵循…)是的,我理解代码。与递归下降法相比,它更接近规范。是的,它可以通过大量的单元测试进行测试。顺便说一句:如果您不知道,您已经用parse1、parse2等方法实现了“责任链”模式(这对这个问题不是一件坏事,也不是一件好事;只是想提一提……),我以前使用多个方法,而不是循环中的一个巨大开关。但是,大型开关结构更符合规范,并且在JIT启动时速度更快。您可以始终执行较小的开关(),默认情况下使用较小的开关调用另一个方法,依此类推,直到涵盖所有较大的开关情况。这可能不如一个大的开关桌好,但它可以工作。在我的例子中,我不能使用switch,因为它对字符串不起作用。是的,我考虑过使用两个不同的switch循环,并在这两个循环之间传递局部变量。这是丑陋的,但是,我想保持整个结构,这样就很容易机械地编译整个东西到GOOFULC++,甚至更少的开关。(是的,我有不寻常的要求。:-)如果您以前没有手工编写过解析器,有时扫描器(lexer/tokenizer)可能会变大,但当您将其拆分时,可读性可能会下降。当然,我没有看过他的代码,但我已经看过了……我已经分割了实际的标记器操作,这些操作可以在不影响控制流的情况下分割出来。“控制结构本身是巨大的。”斯科特,我以前亲手写过一个解析器。如果它过于复杂(因为任何特定原因),这可能是一个合理的时间来考虑。