【问题标题】:Fastest way to call a javascript function (or method) via a lookup table of function names?通过函数名称查找表调用 javascript 函数(或方法)的最快方法是什么?
【发布时间】:2015-04-30 00:58:19
【问题描述】:

我正在用 JavaScript 模拟一个 8 位微处理器。我已将每个操作码函数名称存储在一个数组中,并调用与从我的虚拟内存读取的操作码相关的 256 个函数中的每一个,如下所示:

this.OP[opcode] = 'this.LDAA()';
eval(this.OP[opcode]);

我最近更改了我的代码以摆脱 eval(),如下所示:

this.OP[opcode] = 'LDAA';
this[this.OP[opcode]]();

在 Mac Safari 中,以上两者之间没有明显的速度差异,这让我感到惊讶。我认为后者会更快,但我的虚拟时钟速度对于两者来说大致相同(目前峰值为 4MHz)。

由于与使用 eval() 相比,使用索引方法调用似乎没有速度损失,我希望也更新我的虚拟内存系统,但我对要使用的语法有一个心理障碍。

写一个字节我有:

RAM = {
    write : [],
    setup : function() {
        this.write[addr] = "this.simpleWrite(addr,byte)";
    },

    writeByte : function(addr,byte) { 
        eval(this.write[addr]);
    },

    simpleWrite : function(addr,byte) { 
        this.memory[addr] = byte;
    },
};

RAM.writeByte( someAddress, someValue );

我正在使用这种索引的间接方法,因此我可以将设备映射到地址范围并根据需要放置断点和观察点,所有这些都拦截内存读取和写入 - 最大限度地提高性能。

关于如何在保持数据吞吐量的同时丢失 eval 的任何建议?

我想将外部方法映射到间接数组中,并且能够传递参数(要写入的值)。因此,无论哪个虚拟硬件使用公共接口访问虚拟内存,其他虚拟硬件都可以拦截该进程并在必要时监视或更改值。

【问题讨论】:

  • 如果您正好有 256 个操作码,那么使用操作码索引数组以获取函数的 256 个函数引用(不是函数名)的数组应该是最快的。在某些 JS 引擎中,解释器可以很好地优化仅被视为纯数组的数组,这比对象键查找要好得多。与所有与性能相关的问题一样,您必须在相关浏览器中进行测试才能确定。使用eval() 不可能是最快的。
  • 你的setupaddr的值是多少???
  • addr 是 write[] 数组的任何有效索引(实际上是 0 到 65535)。我将查找函数名称和引用之间的区别,如果我的意思正确,那是个好主意,谢谢。
  • @jfriend00 - 我没有 256 个函数,因为有些是重复的,NOP 等,但我可以轻松地将其扩展为 256,然后有一个由索引调用的匿名函数数组 - 我喜欢这样的声音,谢谢。

标签: javascript performance indexed indirection jump-table


【解决方案1】:

把我的评论变成一个答案,因为这听起来像你要做什么:

如果您正好有 256 个操作码,那么使用操作码索引数组以获取函数的 256 个函数引用(不是函数名称)的数组应该是最快的。如果您在操作码中有任何漏洞(例如,没有使用所有 256 个操作码),您只需为您不使用的操作码填写一个虚拟函数引用。如果您有任何常用函数(提供多个操作码的函数),只需多次使用相同的函数引用即可。这个想法是你想要一个 256 元素的数组,你可以使用操作码直接索引它以获取要执行的函数。

在某些 JS 引擎中,解释器可以很好地优化仅被视为纯数组的数组,这比对象键查找要好得多。

与所有与性能相关的问题一样,您必须在相关浏览器中进行测试才能确定。使用eval() 不可能是最快的,因为它必须首先从头开始解析代码然后执行它。

【讨论】:

  • 使用 eval 尝试过,名称存储在数组中的方法,正如您所建议的,将函数作为数组元素直接索引,Mac Safari 中只有 5% 的差异。平均而言,作为数组元素的函数以 3.7MHz 运行,作为数组元素的函数名称以 3.5MHz 运行。但是函数名称方法确实在 4.2MHz 处达到最高峰值(与 4.0 相比)。通过移除所有 cmets,我获得了一些额外的 MHz。所以答案是无论使用哪种方法,Safari 的编译器都非常高效。
  • 这显然是正确答案,其他所有回复似乎都非常疯狂。我要补充一点,Function.prototype.bind 对此也很方便——如果您没有要绑定的参数,只需传递对函数的引用,如果可以重用函数,则绑定一些基本参数。这是一个将数组用作操作码映射的模拟器:github.com/jkoudys/remu/blob/…
【解决方案2】:

你试过了吗:

setup : function() {
    this.write[addr] = "simpleWrite";
},

writeByte : function(addr,byte) { 
    this[this.write[addr]](addr,byte);
},

或者看看这个: stackoverflow.com/questions/1986896/what-is-the-difference-between-call-and-apply

theFunction.apply(valueForThis, arrayOfArgs)

theFunction.call(valueForThis, arg1, arg2, ...)

【讨论】:

  • 这让我开始走上正轨,我稍后再试,谢谢。
  • 问题是writeByte()不能通过外部方法间接。我认为 this.write[] 不是一个函数名数组,而是一个函数数组。然后映射一个新设备将包括替换数组中的索引函数定义。实施需要时间,但我会尝试 - 谢谢你让我想得更清楚。
【解决方案3】:

使用问题中详述的评估方法的替代方法:

RAM = {
    memory : [],
    write : [],
    setup : function() {
        for (var addr = start; addr < length; ++addr) {
            this.write[addr] = 
                function(a) { 
                    function(byte) { 
                        RAM.memory[a] = byte 
                    }
                }(addr);
            }
        }
    }
RAM.setup();
RAM.write[validAddress](byteValue);

这比使用初始评估方法(在 Mac Safari 中)快约 5%。它也更整洁并消除了 eval 的使用。 for...循环中函数定义的复杂性是由于在 setup 函数本地创建的变量 addr 的范围。如果直接使用,则指向此变量的指针将传递给内部函数,并且当循环完成时,所有数组元素将具有相同的“地址”,即循环结束时 addr 的值(开始 + 长度)。因此,我通过外部函数在循环的每次迭代中创建了一个新的局部变量 (a),它是一个常量,并且在每个内部函数中使用该值(通过指针)。

还请注意,我使用对象名称 (RAM) 而不是 .this 访问内存数组,因为其他对象可能会间接访问内部方法并导致 .this 引用调用对象。命名对象直接解决了这个问题。

有关在循环中创建函数的更详细示例和说明,请参阅callbacks in loops

【讨论】:

    猜你喜欢
    • 2011-11-23
    • 2018-04-24
    • 2016-09-19
    • 2011-04-14
    • 1970-01-01
    • 2011-12-03
    • 2016-04-28
    • 1970-01-01
    • 2011-07-13
    相关资源
    最近更新 更多