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

Posted

技术标签:

【中文标题】手写 asm.js - 你如何跟踪堆中的 javascript 对象?【英文标题】:handwriting asm.js - how can you track javascript objects in the heap? 【发布时间】:2013-07-10 07:38:31 【问题描述】:

我在 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_ids 的对象字典,以便确定该框中的实际对象; object_ids 到对象的映射。

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

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

无论您可以在 asm.js 堆中实现多少属性(例如对象的位置和尺寸),您通常也需要将一些 Javascript 对象与该项目相关联;字符串和 webGL 对象和 DOM 对象等等。

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

【问题讨论】:

你能发布一些代码吗?给我一些切实的思考食物后,我的大脑会更好地工作。 @AaditMShah 代码已添加 太棒了,但我需要的远不止这些。也许您可以将我链接到显示完整代码的要点或小提琴?我有一种感觉,您可以通过模拟 Self-like 对象(即具有用于获取/设置值的消息传递的对象)来解决此问题。但是,我需要知道您的代码结构以建议如何实现它,包括您如何使用外部函数接口 (FFI) 与 JavaScript 进行适当的交互。 downvote:你的问题很有趣,但解释很难理解,一点代码会很有用。 【参考方案1】:

在多次阅读 asm.js 规范并在 Firefox 中进行试验后,我同意 bks:

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

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

    JavaScript 对象 动态数组 高阶函数

尽管如此,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 结构。

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

    由于 JavaScript 是 asm.js 的超集,您已经可以在 JavaScript 中按原样使用 asm.js 结构。 由于 JavaScript 比 asm.js 更强大,因此更容易使 asm.js 结构的行为类似于 JavaScript 对象。 通过将 asm.js 结构导入 JavaScript,您的 asm.js 代码会变得更简单、更有凝聚力且耦合度更低。

废话不多说,我们来看一个例子。让我们以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;

我不会费心去深入解释所有代码,但需要大致了解:

    首先要注意的是,假设我正在创建一个由 4 个顶点组成的图形,那么我只创建一个包含 3 个顶点的数组。最后一个顶点不是必需的。 接下来,对于每个顶点,我在两个顶点之间创建一个新数组(表示边)。对于具有 4 个顶点的图:
      第一个顶点有 3 条边。 第二个顶点有 2 个边。 第三个顶点有 1 个边。 第四个顶点有 0 个 边(这就是我们只需要一个包含 3 个顶点的数组的原因)。

通常,n 顶点的图具有n * (n - 1) / 2 边。所以我们可以用表格的形式来表示图表如下(下表是上面demo中的图表):

+-----+-----+-----+-----+-----+-----+
|     |  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,因此我认为这个答案是完整的。尽管如此,我还是想留下一个工作演示作为概念证明。然而,这个答案已经变得太大了。我会写一篇关于这个主题的博文,并尽快在此处发布一个链接。

用于 Dijkstra 在 asm.js 中实现的最短算法路径算法的 JSFiddle 即将推出。

【讨论】:

这是一个非常好的答案。但是你不能总是颠倒谁存储了什么。问题中给出的示例数据结构似乎都不能被如此颠倒,似乎? @Will我还在学习asm.js所以我不能真正评论是否每个数据结构都可以用这种方式实现,但我还得看看一个不能这样的数据结构被倒置。因此,问题中提到的数据结构仍有可能是可逆的。但是,我需要查看实际代码才能得出明确的结论。 想象一下你的八叉树中的对象有 VBO。您将如何将该对象引用存储在您的 asm.js 堆中? @Will 我必须重温一下我的八叉树和 VBO。也许发布一些代码?这对帮助我理解您的问题大有帮助。 @trusktr 我已经有一段时间没有写博客了。我将再次开始写作,我将从这个概念证明开始并发布一个链接。给我一个星期。【参考方案2】:

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

问。 asm.js 是否可以用作托管语言(如 JVM 或 CLR)的 VM?

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

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

【讨论】:

那么使用整数索引作为对象属性是进行映射的最佳方式吗?数组的性能对比如何? @Will 我认为数组也使用字符串作为对象属性。比如array[0]array["0"]一样,都是把整数转换成字符串来访问数组项。它就像一个对象(数组从对象扩展)。我不认为使用 Array 可以解决这个问题,除非 JS 引擎实现对此进行了优化,但这不是规范(我知道的)。【参考方案3】:

这感觉不对。将 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 开始并且没有空洞。否则,它可以实现为字典而不是快速平面列表。

【讨论】:

您说“用整数索引它应该在 JS 引擎中进行优化”,但我不认为在所有引擎中都能保证。您知道哪些引擎实际上进行了优化吗?【参考方案4】:

可能我还没有完全理解你的问题,但是可以使用 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 代码相当复杂。

【讨论】:

优先级队列中如何包含 Javascript 对象而不是整数? 而不是在纯 javascript 中设计 JS 对象,而是将它们设计为 C++ 类(它也适用于 C 结构)。在上面的代码中,优先级队列是排序类MyClass的实例,而不仅仅是整数,排序函数是C++中的默认值less,这就是为什么我用函数operator&lt;重载 我不想移植 C++ 游戏。我试图通过将最热门的数据结构放入 asm.js 来加速 Javascript 游戏。但最终我仍然希望我的八叉树包含 Javascript 世界中存在的东西,例如GameUnit 类型。 嗨,在这两个链接中,您可以找到有关如何完成此操作的更多信息,据我了解,asmjs 的主要目的正是如此。这是一个关于如何interact with asm code from regular javascript 和a simpler example 的示例。但是,如第二个链接所示,asmjs 旨在作为编译目标,而不是手写。无论如何,我认为您不能在 asmjs 堆中存储常规 JS 对象,因为 asmjs 必须知道字段的类型。 @Will 请提供一些代码,以便我更好地理解您要做什么。真的没那么难。只需复制和粘贴。顺便说一句,您可以编辑 SO 问题。

以上是关于手写 asm.js - 你如何跟踪堆中的 javascript 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中查看堆中的内容?

如何查找堆中的对象数

教你如何使用Java手写一个基于数组实现的队列

一道经典面试题:字符串在Java中如何通过“引用”传递

使用 asm.js 将依赖于外部库的代码转换为 javascript

如何检查 Firefox 是不是使用 asm.js 代码?