编译Kotlin:JetBrains VS ANTLR VS JavaCC


Kotlin解析速度有多快,这有什么关系? JavaCC还是ANTLR? JetBrains源代码适合吗?

比较,幻想和怀疑。

tl;博士


JetBrains太难拖动了,ANTLR大肆宣传,但出乎意料的慢了,JavaCC还为时过早。

解析具有三个不同实现的简单Kotlin文件:
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8

晴间多云...


我决定从某种方便的语言中使用GLSL构建翻译器。 想法是直接在想法中对着色器进行编程,并获得“免费” IDE支持-语法,调试和单元测试。 原来真的很方便

从那时起,使用Kotlin的想法一直存在-您可以在其中使用名称vec3,它在IDE中更加严格并且更方便。 另外,是炒作。 尽管从内部经理的角度来看,这些都不是足够的原因,但是这个想法回来了很多次,以至于我决定通过实施它来摆脱它。

为什么不使用Java? 没有运算符重载,因此矢量算术的语法将与您在游戏开发人员中经常看到的完全不同

捷脑


来自JetBrains的家伙将他们的编译器代码上传到github 。 如何使用它,您可以在这里这里窥视。

最初,我将其解析器与分析器一起使用,因为要转换为另一种语言,您需要知道变量的类型,而无需显式指定val x = vec3() 。 在这里,阅读器的类型很明显,但是在AST中,很难获得此信息,特别是在右边有另一个变量或函数调用时。

在这里,我很失望。 解析器在原始文件上的首次启动需要3秒(三秒钟)。

Kotlin JetBrains parser
first call elapsed : 3254.482ms
min time in next 10 calls: 70.071ms
min time in next 100 calls: 29.973ms
min time in next 1000 calls: 16.655ms
Whole time for 1111 calls: 40.888756 seconds

这样的时间有以下明显的不便之处:

  1. 因为启动游戏或应用程序需要三秒钟的时间。
  2. 在开发过程中,我使用了热着色器重载,并在更改代码后立即看到了结果。
  3. 我经常重新启动该应用程序,并很高兴它启动足够快(一两秒钟)。

再加上三秒钟来预热解析器-这是不可接受的。 当然,立即清楚的是,在随后的调用中,解析时间降至50ms甚至20ms,这(几乎)消除了表达式中的第二个不便之处。 但是其他两个没有去任何地方。 此外,每个文件50毫秒加上每50个文件2500毫秒(一个着色器为1-2个文件)。 如果是Android怎么办? (这里我们只是在谈论时间。)

值得注意的是JIT的疯狂工作。 一个简单文件的解析时间从70ms减少到16ms。 这意味着,首先,JIT本身会消耗资源,其次,在不同的JVM上的结果可能会非常不同。

为了找出这些数字的来源,有一个选项-在没有分析器的情况下使用其解析器。 毕竟,我只需要安排类型,这可以相对轻松地完成,而JetBrains分析器的功能要复杂得多,并且可以收集更多信息。 然后,启动时间减少了一半(但仍然差不多是一个半秒),并且随后的调用时间已经变得非常有趣-从前十个时间的8ms到上千个位置的0.9ms。

Kotlin JetBrains parser (without analyzer) ()
first call elapsed : 1423.731ms
min time in next 10 calls: 8.275ms
min time in next 100 calls: 2.323ms
min time in next 1000 calls: 0.974ms
Whole time for 1111 calls: 3.6884801 seconds
()
first call elapsed : 1423.731ms
min time in next 10 calls: 8.275ms
min time in next 100 calls: 2.323ms
min time in next 1000 calls: 0.974ms
Whole time for 1111 calls: 3.6884801 seconds

我只需要收集这样的数字。 加载第一个着色器时,第一个启动时间很重要。 这很重要,因为在这里,您无法在着色器加载到后台的同时分散用户的注意力,他只是在等待。 减少运行时对于查看动态本身,JIT的工作方式以及在温暖的应用程序上加载着色器的效率非常重要。

主要看一下JetBrains解析器的主要原因是希望使用其分词器。 但是由于拒绝它成为讨论的选项,因此您可以尝试使用其他解析器。 此外,非JetBrain最有可能变得更小,对环境的要求更低,在项目中支持和包含代码会更容易。

ANTLR


正如预期的那样,JavaCC上没有解析器,但是在炒作ANTLR上有( )。

但是出乎意料的是速度。 相同的3s载入时间(首次通话)和140ms的后续通话时间。 在这里,不仅第一次发射持续了不愉快的长时间,而且这种情况也没有得到纠正。 显然,JetBrains的家伙通过让JIT以这种方式优化代码来做一些魔术。 因为ANTLR并未随时间进行优化。

Kotlin ANTLR parser ()
first call elapsed : 3705.101ms
min time in next 10 calls: 139.596ms
min time in next 100 calls: 138.279ms
min time in next 1000 calls: 137.20099ms
Whole time for 1111 calls: 161.90619 seconds
()
first call elapsed : 3705.101ms
min time in next 10 calls: 139.596ms
min time in next 100 calls: 138.279ms
min time in next 1000 calls: 137.20099ms
Whole time for 1111 calls: 161.90619 seconds

Javacc


通常,我们很惊讶地拒绝ANTLR的服务。 解析时间不必那么长! Kotlin的语法没有宇宙模棱两可的地方,我检查了几乎是空的文件。 因此,是时候揭开旧的JavaCC,收起袖子了,仍然“自己动手做”。

这次,虽然与替代方案相比,结果还是令人期待,但令人意外。

Kotlin JavaCC parser ()
first call elapsed : 19.024ms
min time in next 10 calls: 1.952ms
min time in next 100 calls: 0.379ms
min time in next 1000 calls: 0.114ms
Whole time for 1111 calls: 0.38707677 seconds
()
first call elapsed : 19.024ms
min time in next 10 calls: 1.952ms
min time in next 100 calls: 0.379ms
min time in next 1000 calls: 0.114ms
Whole time for 1111 calls: 0.38707677 seconds

JavaCC解析器的突然优点
当然,我想使用现成的解决方案,而不是编写自己的解析器。 但是现有的方法有很多缺点:

-性能(读取新着色器时的暂停是不可接受的,并且在开始时需要三秒钟的预热)
-巨大的Kotlin运行时,我什至不确定是否可以将解析器打包到最终产品中
-顺便说一下,在使用Groovy的当前解决方案中,同样的麻烦-运行时延

虽然生成的JavaCC解析器是

+在开始和过程中均具有出色的速度
+解析器本身仅几类

结论


JetBrains太难拖动了,ANTLR大肆宣传,但出乎意料的慢了,JavaCC还为时过早。

解析具有三个不同实现的简单Kotlin文件:

1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8
1000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.81000 () JetBrains 3254 16,6 35.3 JetBrains (w/o analyzer) 1423 0,9 35.3 ANTLR 3705 137,2 1.4 JavaCC 19 0,1 0.8

在某个时候,我决定考虑所有依赖项的jar大小。 JetBrains非常出色, 但是ANTLR运行时的大小令人惊讶
更新:最初,我写了15MB,但是正如注释中所建议的那样,如果连接antlr4-runtime而不是antlr4,则大小会下降到预期值。 尽管JavaCC解析器本身比ANTLR小10倍(如果您完全删除所有代码,则解析器本身除外)。
这样的罐子的大小对于手机当然很重要。 但这对台式机也很重要,因为实际上,这意味着可能包含bug的其他代码量,IDE应该对其进行索引,而这些代码确实会影响首次加载的速度和预热的速度。 另外,对于复杂的代码,几乎没有希望翻译成另一种语言。
我不敦促您数以千计,我感谢程序员的时间和便利,但是仍然值得考虑节省费用,因为这会使项目变得笨拙且难以维护。

关于ANTLR和JavaCC的几句话

ANTLR的一个重要特征是语法和代码的分离。 如果不必付那么高的钱,那会很好。 是的,这仅对“语法的串行开发者”重要,对于最终产品而言,它并不那么重要,因为即使是现有的语法,编写代码也必须完成。 另外,如果我们省钱并采用“第三方”语法-可能只是不方便,但仍需要对其进行彻底的理解并将其自身转变为一棵树。 通常,JavaCC当然会混合苍蝇和炸肉排,但这真的很重要吗,那么糟糕吗?

ANTLR的另一个功能是许多目标平台。 但是在这里您可以从另一侧看-JavaCC下的代码非常简单。 它非常简单...广播! 使用您的自定义代码-至少在C#中,至少在JS中。

聚苯乙烯


所有代码都在这里github.com/kravchik/yast

解析的结果是在YastNode上构建的树(实际上,这是一个非常简单的类-具有便利方法和标识符的映射)。 但是YastNode并不是真正的“真空中的球形节点”。 我积极使用的是该类,在此基础上,我收集了一些工具-打字机,几个翻译器和一个优化器/内联器。

JavaCC解析器尚未包含所有语法,只剩下10%的语法,但是似乎它们不会影响性能-我检查了添加规则的速度,并且它没有明显变化。 另外,我已经做的事情远远超出我的需要,只是尝试分享该过程中发现的意外结果。

Source: https://habr.com/ru/post/zh-CN433000/


All Articles