我需要在运行时生成特征的实现,然后在特征实例上执行已知方法。在此示例中,我正在运行A
'sa
方法:
import reflect.runtime._, universe._, tools.reflect.ToolBox
package p {
trait A { def a: String }
val tb = currentMirror.mkToolBox()
val d: A = tb.eval(q"""class C extends p.A { def a = "foo" }; new C""").asInstanceOf[A]
println(d.a) // "foo"
}
围绕这个问题有几个问题:
- 用例对性能非常敏感(在数据管理系统中运行生成的查询) -
tb.eval
如果在编译时而不是运行时编译,生成的字节码与 scalac 将生成的相同吗? - 我想缓存生成的类,这样我就不必为已经编译的已知查询重新编译它。我可以获取生成类的字节并将其存储在类加载器中吗?
- 有没有更优雅的方法来做到这一点,可能避免
asInstanceOf
?
更新:添加有关我的用例的详细信息:
我正在研究分布式列式数据存储的查询系统。我们现有的基于 Scala 的查询系统性能良好。我的目标是将传入的 SQL 查询编译为 Scala,以便它可以在现有系统上运行。
我已经构建了一个基于解释的版本,它的运行速度慢了大约 8 倍。我还开始了一个 ASM 版本,部分实现SELECT
,它的性能与 Scala 系统相当(这是预期的,因为它们都产生了几乎相同的字节码)。
性能最重要的方面是运行动态生成的代码,因为该成本是在参与跨集群查询的每台机器上产生的(目前一个集群有 60 台机器,但随着数据集的大小继续增加),并且生成的代码用于扫描数十亿条记录。所以我不太关心使用反射和代码生成来生成字节码,只要它具有合理的性能。
我需要实现的特征是查询接口。实际上,它是一个抽象类,可以更轻松地使用 Java。这是一个非常简化的示例:
abstract class BaseQuery[R <: Result[R]] {
def init(parameters: Option[JSONObject])
def execute(partitionKey: String, subpartitionKey: String, numSubpartitions: Int, page: ColumnSet, referenceData: Map[String, Any]): Option[R]
}
生成一些字节码后,我需要将其打包在一个 jar 中并将其发送到其他节点,以便它们可以在各自的分区上运行查询,然后合并结果(现有查询系统已经支持发送 jar 和合并结果) .
我正在研究 Scala 的 Quasiquotes 支持,目的是让代码生成更容易表达。ASM 非常低级,容易出错,难以调试等。对其他选项开放,但 quasiquotes 看起来不错。此外,我看到Spark SQL 项目正在使用它。