手写asm.js - 如何跟踪堆中的javascript对象?

时间:2013-07-10 07:38:32

标签: javascript asm.js

我在Javascript的asm.js子集中编写优先级队列和八叉树,以便从中榨取最后可能的性能。

但是,如何在asm.js函数的heap缓冲区中存储对Javascript对象的引用?

现在,我在堆中的结构必须有一个他们引用的Javascript对象的整数ID,我需要一个经典的Javascript对象作为这些整数和Javascript对象之间的字典。

例如,我有一个asm.js八叉树,它公开了一个 add 函数,如add(x1,y1,z1,x2,y2,z2,object_id),其中object_id是整数。并且find(x1,y1,z1,x2,y2,z2)函数返回边界内所有object_id的列表。这意味着我必须在Javascript中维护object_id的对象字典,以便我可以确定该框中的实际对象; object_ids到对象的映射。

感觉错误。将int转换为字符串以在Javascript世界中进行查找的想法只是错误。在asm.js中编写内部循环数据结构的一个关键点是避免创建垃圾。

(我的目标是Chrome和Firefox一样;我希望asm.js严格的代码在两者上运行得更快。是的,我会进行分析。)

无论你可以在asm.js堆中实现多少属性 - 例如对象的位置和维度 - 你通常也需要将一些Javascript对象与项目关联起来;字符串和webGL对象以及DOM对象等。

有没有更好的方法让asm.js堆包含指向Javascript对象的指针?如果使用整数ID映射,例如,使用数组或对象作为字典是否更好?

4 个答案:

答案 0 :(得分:15)

在几次阅读asm.js规范并在Firefox中尝试之后我同意bks:

  

asm.js程序只能通过数字句柄间接与外部数据交互

然而,这并不构成重大问题。由于asm.js是JavaScript的子集,因此您无法在asm.js中使用大量JavaScript构造,包括:

  1. JavaScript对象
  2. 动态阵列
  3. 高阶函数
  4. 尽管如此,asm.js确实提供了一种使用外部函数接口(FFI)调用JavaScript函数的方法。这是一个非常强大的机制,因为它允许您从asm.js回调JavaScript(允许您创建部分用asm.js编写并部分用JavaScript编写的过程)。

    区分代码的哪些部分可以转换为asm.js并且从使用asm.js中受益非常重要。例如,asm.js非常适合图形处理,因为它需要大量的计算。但是它不适合字符串操作。简单的JavaScript会更好用于此目的。

    回到主题,您面临的问题是您需要从asm.js代码中引用JavaScript对象。由于唯一的方法是使用数字句柄(你不想要的),我只看到另一个解决方案:

    不是从asm.js中引用JavaScript对象,而是从JavaScript中引用asm.js结构。

    这种方法更好的原因有很多:

    1. 由于JavaScript是asm.js的超集,因此您已经可以在JavaScript中使用asm.js结构。
    2. 由于JavaScript比asm.js更强大,因此asm.js结构的行为就像JavaScript对象一样容易。
    3. 通过将asm.js结构导入JavaScript,您的asm.js代码变得更简单,更具凝聚力并且耦合度更低。
    4. 足够的谈话,让我们看一个例子。我们来看Dijkstra's shortest path algorithm。幸运的是,我已经有了一个工作演示(我必须为大学任务实现Dijkstra算法):

      http://jsfiddle.net/3fsMn/

      上面链接的代码完全用普通的JavaScript实现。让我们来看看这些代码的某些部分并将其转换为asm.js(请记住,数据结构将在asm.js中实现,然后导出到JavaScript)。

      从具体的事情开始,这就是我在JavaScript中创建图形的方式:

      var graph = new Graph(6)
          .addEdge(0, 1, 7)
          .addEdge(0, 2, 9)
          .addEdge(0, 3, 14)
          .addEdge(1, 2, 10)
          .addEdge(1, 4, 15)
          .addEdge(2, 3, 2)
          .addEdge(2, 4, 11)
          .addEdge(3, 5, 9)
          .addEdge(4, 5, 6);
      

      我们希望保持相同的界面。因此,要修改的第一件事是Graph构造函数。这就是它目前的实施方式:

      function Graph(v) {
          this.v = --v;
      
          var vertices = new Array(v);
      
          for (var i = 0, e; e = v - i; i++) {
              var edges = new Array(e);
              for (var j = 0; j < e; j++)
                  edges[j] = Infinity;
              vertices[i] = edges;
          }
      
          this.vertices = vertices;
      }
      

      我不打算深入解释所有代码,但需要一般性的理解:

      1. 首先要注意的是,假设我创建了一个由4个顶点组成的图形,那么我只创建一个包含3个顶点的数组。最后一个顶点不是必需的。
      2. 接下来,对于每个顶点,我在两个顶点之间创建一个新数组(表示边)。对于具有4个顶点的图形:
        1. 第一个顶点有3条边。
        2. 第二个顶点有2个边。
        3. 第三个顶点有1个边缘。
        4. 第四个顶点有0 个新边(这就是我们只需要一个包含3个顶点的数组的原因)。
      3. 一般来说,n个顶点的图表有n * (n - 1) / 2条边。所以我们可以用表格格式表示图形如下(下表是上面演示中的图表):

        +-----+-----+-----+-----+-----+-----+
        |     |  f  |  e  |  d  |  c  |  b  |
        +-----+-----+-----+-----+-----+-----+
        |  a  |     |     |  14 |  9  |  7  |
        +-----+-----+-----+-----+-----+-----+
        |  b  |     |  15 |     |  10 |
        +-----+-----+-----+-----+-----+
        |  c  |     |  11 |  2  |
        +-----+-----+-----+-----+
        |  d  |  9  |     |
        +-----+-----+-----+
        |  e  |  6  |
        +-----+-----+
        

        这是我们需要在asm.js模块中实现的数据结构。现在我们知道了它的样子,让我们开始实现它:

        var Graph = (function (constant) {
            function Graph(stdlib, foreign, heap) { /* asm.js module implementation */ }
        
            return function (v) {
                this.v = --v;
                var heap = new ArrayBuffer(4096);
                var doubleArray = this.doubleArray = new Float62Array(heap);
                var graph = this.graph = Graph(window, {}, heap);
                graph.init(v);
        
                var vertices = { length: v };
        
                for (var i = 0, index = 0, e; e = v - i; i++) {
                    var edges = { length: e };
        
                    for (var j = 0; j < e; j++) Object.defineProperty(edges, j, {
                        get: element(index++)
                    });
        
                    Object.defineProperty(vertices, i, {
                        get: constant(edges)
                    });
                }
        
                this.vertices = vertices;
        
                function element(i) {
                    return function () {
                        return doubleArray[i];
                    };
                }
            };
        }(constant));
        

        正如您所看到的,Graph构造函数变得更加复杂。除了vvertices之外,我们还有两个新的公共属性doubleArraygraph,它们是从asm.js公开数据结构和数据操作所必需的。模块分别。

        vertices属性特别是现在实现为对象而不是数组,它使用getter来公开asm.js数据结构。 这是我们从JavaScript中引用asm.js数据结构的方式。

        堆只是一个ArrayBuffer,它可以通过asm.js代码或普通的旧JavaScript来操作。这允许您在asm.js代码和JavaScript之间共享数据结构。在JavaScript方面,您可以将此数据结构包装在一个对象中,并使用getter和setter来动态更新堆。我认为这比使用数字句柄更好。

        结论:由于我已经回答了您的问题,并演示了如何将asm.js数据结构导入JavaScript,我会得出结论,这个答案已经完成。不过,我想留下一个工作演示作为概念证明。然而,这个答案已经变得太大了。我会写一篇关于这个主题的博客文章,并尽快在这里发布一个链接。

        JSFiddle for Dijkstra即将在asm.js中实现的最短算法路径算法。

答案 1 :(得分:9)

当我阅读http://asmjs.org/spec/latest/的asm.js规范和http://asmjs.org/faq.html的FAQ时,简短的回答是你不能在asmjs堆中存储JS对象引用。引用常见问题解答:

  

Q值。 asm.js可以作为托管语言的VM,如JVM或CLR吗?

     

一个。目前,asm.js无法直接访问垃圾收集数据; asm.js程序只能通过数字句柄与外部数据间接交互。在未来的版本中,我们打算基于ES6结构化二进制数据API引入垃圾收集和结构化数据,这将使asm.js成为托管语言的更好目标。

所以你当前存储外部id-to-object映射的方法似乎是当前推荐的解决问题的方法,只要你关心对象实例而不仅仅是它们的内容。否则,我认为您的想法是取消实现存储对象:将每个对象的完整内容存储在优先级队列中的插槽中,并仅在获取时将其转换回真正的JS对象。但这只有在您的对象可以安全地按需重新创建时才有效。

答案 2 :(得分:4)

  

这感觉不对。将int转换为字符串以在Javascript世界中进行查找的想法是错误的。在asm.js中编写内部循环数据结构的一个关键点是避免创建垃圾。

这里不需要将int转换为字符串。你应该有一个将索引映射到JS对象的JS数组,然后使用整数对它进行索引应该在JS引擎中进行优化,直接使用该整数。他们将知道查找表何时是一个数组,以及何时流入的值是整数。

这就是emscripten(在asm.js输出模式和非asm.js输出模式下)处理函数指针之类的东西。您有一个整数ID,并且有一个JS数组将这些ID映射到相关对象。例如,

var FUNCTION_TABLE = [function zero() {}, function one() {}];

后来用

调用

FUNCTION_TABLE[i]();

保持阵列正确优化非常重要,这基本上意味着将其值设置为0并且没有孔。否则,它可以实现为字典而不是快速平面列表。

答案 3 :(得分:0)

可能我还没有完全理解你的问题,但是可以使用C ++标准库中的优先级队列,然后用emscripten编译它来创建一个asm.js javascript模块。

例如以下代码:

#include <queue>
#include <iostream>

class MyClass {
    private:
        int priority;
        int someData;
    public:
        MyClass():priority(0), someData(0){}
        MyClass(int priority, int data):priority(priority), someData(data){}
        int getPriority() const { return this->priority;}
        int getData() const { return this->someData;}
        void setData(int data){ this->someData = data;}
        inline bool operator<(const MyClass & other) const{
            return this->getPriority() < other.getPriority();
        }
};

int main(){

    std::priority_queue<MyClass> q;
    q.push(MyClass(50, 500));
    q.push(MyClass(25, 250));
    q.push(MyClass(75, 750));
    q.push(MyClass(10, 100));

    std::cout << "Popping elements: " << std::endl;
    while(!q.empty()){
        std::cout << q.top().getData() << std::endl;
        q.pop();
    }
    std::cout << "Queue empty" << std::endl;

    return 0;
};

编译如下:

emcc queue.cpp -s ASM_JS=1 -O2 -o queue.js

然后可以用nodejs执行,产生以下输出:

$ nodejs queue.js 
Popping elements: 
750
500
250
100
Queue empty

它也可以编译为创建一个html文件,并在浏览器中加载它:

$ emcc queue.cpp -s ASM_JS=1 -O2 -o queue.html

不知道这是否适合您,但手动编写asmjs代码非常复杂。