原文链接:Lars Knoll - Evolution of the QML engine, part 1
QML作为一项技术对于Qt的成功变得越来越重要。它允许创建流畅的动画界面,与现今的市场预期相符。有三个主要的特性使得它更适合创建用户界面。首先是声明式语法,使得它非常容易创建用户界面,并且开发者和UI设计者工作在相同的代码基础上。其次,这项技术使原生代码集成变得相对容易,它承担了更多的程序逻辑以及C++所负的重任。最后,选择JavaScript作为QML语言不可分割的一部分,这样使得创建原型更加容易,并且为更广泛的用户提供了新技术。
今天的引擎是相当棒的基石并且满足了大多数用户的需求。当然也有一些我们需要在未来解决的短处和问题。这个系列的博客将概括我们的研究并且呈现我们计划要实现的解决方案。目标是在将来得到一个更好、更灵活、更易维护的QML引擎。
让我从当前引擎所面对的一些问题开始。
当前的QML引擎使用V8 JavaScript引擎来执行属性绑定。每个QML项内部有几个不同的表示。一个面向V8(使用公开的V8 API),一个面向QML引擎,还有一个作为QObject暴露给原生Qt。这里的问题是需要QML引擎来同步这些不同的表示,导致了大量的中间代码和相当高的内存消耗来维持这些不同的表示。
通过JS闭包(closures)完成绑定
使用V8每个绑定需要一个JS函数闭包。这需要把这个表达式重写成闭包,然后让V8再次解析这个重写的表达式。在V8中评估绑定需要调用JS函数,它使用大量的额外开销来模拟执行绑定的上下文环境。因此在我们开发小型的用于QML引擎中的表达式解析器(叫做V4)的时候,它可以计算简单的表达式,这个解释器比通过V8计算快很多倍。
数据类型转换
使用V8计算表达式很慢的一个原因是从Qt到V8或者从V8到Qt数据类型转换带来的额外开销。读写一个Qt属性,例如创建一个QVariant,将被转换为V8::Value。如果这个QVariant是字符串,将会调用字符串数据的复制。为了减弱这些开销引进了各种缓存机制,但是随之而来的是内存使用以及代码复杂性的增加。
QML域规则
QML语法上是项目树。内部项可以隐式查看外部项的属性,形成了一个良好定义的作用域链。不幸的是这个作用域链和传统的JavaScript不同,不能用现有的JavaScript引擎直接实现。直接实现的唯一方法是嵌入慢的不被赞成(deprecated)的with()语句。目前的解决方案包含了一个暴力(intrusive)补丁在V8中,并且仍然需要通过名字来查找所有的QML属性。
QML类型信息的丢弃
QML作为一种语言拥有类型信息。但是JavaScript是完全无类型的。导致了这样一个事实,所有的类型信息在QML编译时被丢弃。保留这些类型信息将使我们更好的优化QML表达式。
iOS和WinRT支持
iOS不允许内存可执行和可写,而这正是现有JS引擎所需的。WinRT根本不允许使内存可执行。这样的情况下,如果不写一个完整的V8解释器后端,就不能在这些平台上使用V8。
和V8上游一起工作
如上所述,我们当前被迫维护一个相当大并且暴力的补丁集在V8之上。根本没有机会把这些标补丁合并到上游,因为V8专注于浏览器应用。
这些不同的问题导致了一个研究项目,大约一年前由Nokia的Roberto Raggi和Aaron Kennedy发起。他们考察了根据QML的需求裁剪JS引擎的的可能性。这个项目代号为v4vm,之后被转移到Digia,Simon、Erik和我重拾这个项目并且继续研究。它作为一个playground项目存在于qt-project.org,已经有好几个月了。
今天我们把这个研究项目集成到了Qt Declarative的一个开发分支,从现在开始我们将在这个分支继续工作。
新的引擎包含了一个完全遵循ECMAScript 5.1的实现,它可以运行于所有Qt支持的平台。它包含了一个JIT,目前可以工作在Linux、Mac和iOS。 目前已经完成了到当前QML引擎的集成,是通过一个兼容的V8 API层实现的,我们计划在未来几周移除它。这个引擎可以运行所有的Qt Quick演示程序(有一些小问题)。
值得注意的一件事是,这个引擎专注于QML使用。这意味着我们期望QML的性能变得比今天的更好。从另一方面讲,纯JS性能并不一定像V8那样好。
然而我们当前的基准数据(benchmark numbers)和纯JS代码一样是非常有希望的,V8基准大约慢于Qt 4.8和Qt Quick 1中JS引擎3倍。我们看到了很大潜力,可以在将来优化。
新引擎的细节和我们未来的计划将在接下来的几篇文章中介绍。您可以在qtdeclarative的wip/v4分支找到代码。随意尝试吧。如果您想参与开发,请在freenode的#qt-v4vm频道联系我们。