飞镖中功能性风格的性能增益



在Dart中使用纯功能风格(没有副作用)是否应该获得性能提升?天真地,我会期待相反的结果,但我认为可能会有更多的优化机会。

举个例子,想象一个向量类可以这样实现:

class Vec {
  final num x, y, z;
  Vec(this.x, this.y, this.z);
  Vec add(Vec v) => new Vec(x + v.x, y + v.y, z + v.z);
}
final x = new Vec(1,2,3).add(new Vec(4,5,6));

与这样的实现:

class Vec {
  num x, y, z;
  Vec(this.x, this.y, this.z);
  Vec add(Vec v) { x += v.x; y += v.y; z += v.z; return this; }
}
var x = new Vec(1,2,3).add(new Vec(4,5,6));

这创建了少一个CCD_ 1对象。(显然,可以使用add(x,y,z)方法来创建更少的Vec,但我认为添加的向量在现实生活中不是恒定值。)

据我所知,在Javascript虚拟机中,额外的对象创建相对昂贵,而且在不必要的时候你宁愿不创建新对象。如果这是C++,我希望栈上会有很多Vec对象,我希望这两者之间的任何差异都能得到优化。

我认为纯功能风格的主要优化增益是并行性,这可能不适用于Dart VM。

在Dart中,使用运算符就像调用方法一样。隐式操作数转换不会带来性能开销。

我相信虚拟机非常好,它可以打开简单对象的包装并将它们保留在堆栈中,尤其是在所有成员都是最终成员的情况下。因此,对于热代码来说,不应该因为创建了额外的对象而给垃圾收集器带来额外的压力。

但正如Marcin所说的,最好的确定方式是描述这一点。

也许可以看看Tracer基准测试源代码(这是一个简单的光线跟踪器)。我确信这使用了运算符重载,因为它是三维点和向量。这将使您了解javascript与Dart的性能。您甚至可以删除+运算符调用,并用add()方法替换它们,看看是否有很大的区别。(这可能比尝试创建一个微型基准更有用)。

此外——也许你应该更改这个问题的标题——这不是关于纯函数编程的。尽管Dart有很多函数式方法,如map()和fold(),但它不支持纯函数式编程,因为它不支持正确的尾部调用,这意味着纯函数式程序会导致堆栈溢出。

好吧,我继续进行快速测试。基本上,我只是根据每个实现添加了10亿个向量。功能性的花了大约三倍的时间。因此,至少目前看来,对象的创建并没有得到优化。

void main() {
  const N = 1000000000;
  int i = 0;
  var v = new Vec(0,0,0);
  while (i < N * 3) {
    v = v.add(new Vec(i++, i++, i++));
  }
  print('$v');
}

我还尝试了一种具有类似add(num,num,num)而不是add(new Vec(num,num,num))的语义的实现,从而消除了另一个临时对象。这甚至更快,但只是稍微快一点;这让我相信编译器确实设法部分优化了特定的临时对象创建。

最新更新