2

在我的应用程序中,我正在生成遵循 CPS 样式的 JavaScript 代码。我“不”使用任何“延续”。没有异步行为,没有暂停和恢复,没有回调。

只是代码遵循了一种延续传递的编程风格。

功能有许多阶段,每个阶段都进行处理并将结果传递给其延续。

我发现 CPS 样式代码的性能很差。以直接样式编写的代码几乎比 CPS 样式代码快 150 倍。

请检查以下代码。
下面的两个代码都等价于

var res = data.store.bookshelf.book.author;

直接样式代码:

var data = { store : { bookshelf : {book : {author:"Douglas Crockford"}}}};
var t1 = new Date().getTime();
for(var i = 0; i < 1000*1000*100; i+=1){      
  var temp0 = data;
  var temp1 = temp0.store;
  var temp2 = temp1.bookshelf;
  var temp3 = temp2.book;
  var temp4 = temp3.author;
  var res = temp4;
}
var t2 = new Date().getTime();
console.log(t2-t1);

上面的代码运行了将近 95 毫秒。

CPS 样式代码:

var data = { store : { bookshelf : {book : {author:"Douglas Crockford"}}}};

// return the variable to the continuation
function cps_VARREF(x,f){
  return f(x);
}
// get the value of the property from the variable and pass it to the continuation
function cps_CHILD(x,child,f){
  return f(x[child]);
}
// simply return the input value, essentially closing the continuation chain
function ret_(x){
  return x;
}

var t1 = new Date().getTime();
for(var i = 0; i < 1000*1000*100; i+=1){
 var res = function(c_){
    return cps_VARREF(data,function(x1){
    return cps_CHILD(x1,"store",function(x2){
    return cps_CHILD(x2,"bookshelf",function(x3){
    return cps_CHILD(x3,"book",function(x4){
    return cps_CHILD(x4,"author",c_);});});});});}(ret_);
}
var t2 = new Date().getTime();
console.log(t2-t1);

上述 CPS 风格的代码运行时间为 15000 毫秒

我能做些什么来改进 CPS 风格的代码吗?或者 JavaScript 天生不适合 CPS 样式的代码?

以上测试是在node.js 0.6.12版本上完成的

有人可以对这个问题有所了解吗?

谢谢,

4

2 回答 2

1

急剧放缓至少有两个潜在原因。首先是您正在用动态查找替换“本机”属性查找。

V8 尽可能优化对象,因此访问属性更快。它不是使用哈希表按名称查找属性,而是跟踪内部“类”,以便它可以从已知地址查找属性。所以data.store只是一个快速的指针比较,以确保对象是预期的类型和索引指针加载。

但是在cps_CHILD函数中,它不能进行优化,因为它不知道将要提前访问什么属性(并且每次调用它都会改变)。动态查找强制 V8 回退到哈希表查找,这比优化的静态查找要慢。

另一个问题是函数调用的开销。每次将嵌套函数传递给下一个函数时,都必须重新创建每个嵌套函数。它们不应该每次都编译,但仍然需要在新的上下文中创建。

于 2012-08-24T23:35:21.073 回答
0

你应该知道有些东西是在运行时解析的,比如你在循环中输入的那些匿名函数。在每个新的“i”上,将再次创建新的“构造函数”及其每个匿名函数的原型。这就是为什么它更慢。这是新的测试。尝试为每个匿名函数定义实际函数,将它们嵌套在循环之外,它应该可以提高程序的性能。

这是代码,它与您的 CPS 样式代码一样深入,但仅在定义函数后

var data = { store : { bookshelf : {book : {author:"Douglas Crockford"}}}};

function getValueFrom(data){
    return data;
}

function getAuthorFrom(data){
    return getValueFrom(data);
}

function getBookFrom(data){
    return getAuthorFrom(data["book"]);
}

function getBookShelfFrom(data){
    return getBookFrom(data["bookshelf"]);
}

function getStoreFrom(data){
    return getBookShelfFrom(data["store"]);
}

function getAuthor(data){
    return getAuthor(data);
}

var t1 = new Date().getTime();
for(var i = 0; i < 1000*1000*100; i+=1){
 var res = getStoreFrom(data);
}
var t2 = new Date().getTime();
console.log(t2-t1);

它的运行速度提高了 4-5 倍。由于 JS 引擎需要寻找函数原型,因此速度仍然较慢,因此可以将其放在堆上执行。在第一种情况下(非 CSP 样式),当您使用点访问属性 JS 引擎时,仅通过键(JS 对象属性)查询哈希以获取其值。它工作得更快,因为它只需要处理内存引用。

于 2012-08-24T23:10:03.417 回答