JavaScript:直接代码与CPS样式生成的代码性能比较

时间:2012-08-24 22:24:50

标签: javascript node.js continuation-passing

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

只是代码遵循continuation passing style编程。

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

我发现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上完成

有人可以就这个问题说清楚吗?

谢谢,

2 个答案:

答案 0 :(得分:1)

至少有两个可能导致剧烈放缓的原因。首先,您要使用动态查找替换“本机”属性查找。

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

cps_CHILD函数中,它无法进行优化,因为它不知道将提前访问哪个属性(每次调用它时它都会更改)。动态查找强制V8回退到哈希表查找,这比优化的静态查找慢。

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

答案 1 :(得分:0)

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

这是代码,它在你的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对象属性)查询哈希值以获取其值。它工作得更快,因为它只需要处理内存引用。