Java-如何克服自动生成代码中的最大方法大小

Java-如何克服自动生成代码中的最大方法大小,java,jvm,code-generation,auto-generate,Java,Jvm,Code Generation,Auto Generate,我有一个不寻常的要求:我的应用程序从一个很长的脚本(用动态类型语言编写)自动生成Java代码。剧本太长了,我打了 该脚本只包含关于基元类型的简单指令(除了数学函数外,不调用其他函数)。它可能看起来像: ... a = b * c + sin(d) ... if a>10 then e = a * 2 else e = a * abs(b) end ... 。。。转换为: ... double a = b * c + Math.sin(d); ... double e; if(

我有一个不寻常的要求:我的应用程序从一个很长的脚本(用动态类型语言编写)自动生成Java代码。剧本太长了,我打了

该脚本只包含关于基元类型的简单指令(除了数学函数外,不调用其他函数)。它可能看起来像:

...
a = b * c + sin(d)
...
if a>10 then
   e = a * 2
else
   e = a * abs(b)
end
...
。。。转换为:

...
double a = b * c + Math.sin(d);
...
double e;
if(a>10){
   e = a * 2;
}else{
   e = a * Math.abs(b);
}
...

我克服方法大小限制的第一个想法是:

  • 将所有局部变量转换为字段
  • 在单独的方法中,每100行分割代码(如果是if/else块,则需要更长的时间)
比如:

class AutoGenerated {

   double a,b,c,d,e,....;

   void run1(){
      ...
      a = b * c + sin(d);
      ...
      run2();
   }

   void run2(){
      ...
      if(a>10){
          e = a * 2;
      }else{
          e = a * Math.abs(b);
      }
      ...
      run3();
   }

   ...
}
你知道还有其他更有效的方法吗?请注意,我需要代码以尽可能快的速度运行,因为它将在长循环中执行。我不能求助于编译到C,因为互操作性也是一个问题


我也希望有人能给我提供一些库的指针,对我有所帮助。

我很想写一个解释器或者一个内嵌编译器。您甚至可以获得一些速度提升,因为大部分生成的代码库都会更小,更容易缓存

  • 将所有局部变量转换为字段
那不会有丝毫影响。方法大小==代码大小。与局部变量无关,局部变量只影响调用帧的大小

  • 在单独的方法中,每100行分割代码(如果是if/else块,则需要更长的时间)
这是您唯一的选择,而不是完全不同的实施策略


代码生成器的问题在于它们生成代码。

只要代码没有通过JIT优化,将局部变量转换为字段实际上可能会对性能产生负面影响(有关更多信息,请参阅)。但我看到,根据所涉及的变量,可能几乎没有其他可行的选择


编译和方法大小可能有额外的限制。Peter Lawrey在评论中提到“…默认情况下不会编译大小超过8 KB的方法”-我不知道这一点,但他通常知道他在说什么,所以你应该在这里深入挖掘一下。此外,我们可能想看看,看看哪些进一步的限制和设置可能与您相关。我主要认为

-XX:MaxInlineSize=35
:要内联的方法的最大字节码大小

可能是需要记住的事情

(事实上,调用如此多大小为
MaxInlineSize
的方法,以至于内联所有这些调用将超过包含方法的65k字节大小,这对于内联过程的健壮性和边缘情况测试来说可能是一个非常糟糕的测试案例…)


您为这些方法绘制了一个“伸缩”调用方案:

void run1(){
   ...
   run2();
}

void run2(){
   ...
   run3();
}
这也可能会导致问题:考虑到这些方法中有超过650种(在最佳情况下),这至少会导致非常深度堆栈,并且实际上可能会再次导致
堆栈溢出错误
,具体取决于某些方法。您可能必须通过相应地设置
-Xss
参数来增加堆栈大小


实际的问题描述有点模糊,没有关于要生成的代码的进一步信息(也没有关于您需要多少局部变量、可能必须转换为实例变量等的问题),我建议如下:

  • 如果可能,创建许多小方法(考虑到
    MaxInlineSize
  • 尝试重用这些小方法(如果可以通过合理的努力从输入中检测到这种可重用性)
  • 按顺序调用这些方法,如中所示

    以避免堆栈大小出现问题



然而,如果您添加更多的示例或细节,您可能会给出更集中的建议。这甚至可能是一个“完整”的例子——不一定涉及数千行代码,但显示了出现在那里的实际模式。

我们在其中一个项目中使用了类似的方法,尽管其他人提到了它的缺点。我们从单个启动器调用多个生成的方法,如@Marco13。我们实际上(相当精确地)计算生成字节码的大小,并且只有在达到限制时才开始一个新方法。我们翻译成Java代码的数学公式作为AstTree提供,我们有一个特殊的访问者,它计算每个表达式的字节码长度。对于这样简单的程序,它在Java版本和不同的编译器之间非常稳定。因此,我们不会创建超出必要范围的方法。在我们的例子中,很难直接发出字节码,但是您可以尝试使用ASM或类似的库为您的语言发出字节码(当然,通过这种方式,ASM将为您计算字节码长度)

我们通常将数据变量存储在single
double[]
数组中(不需要其他类型),并将其作为参数传递。这样,您就不需要大量的字段(有时我们有数千个变量)。另一方面,与索引高于127的字段访问相比,本地数组访问可能需要更多字节码字节

另一个问题是池大小不变。在自动生成的代码中通常有许多双常量。如果您声明了许多字段和/或方法,它们的名称也采用常量池条目。所以有可能达到类常量池限制。有时我们点击它并生成嵌套类来克服这个问题


其他人也建议调整JVM选项。仔细使用这些建议,因为它们不仅会影响这个自动生成的类,还会影响其他所有类(我假设在您的情况下,其他代码也会在同一个JVM中执行)。

如果您担心效率,您应该注意
void run()
{
    run0();
    run1();
    ...
    run2000();
}