我正在使用Javascript实现图灵机(将其视为虚拟机)。我正在研究一个尽可能高效地执行计算的例程(从一开始这不是项目的重点)。
是的,除非遇到性能问题,否则我不应考虑优化。但是我正在做的事情的性质(大多数非平凡的程序具有非常低效的渐近运行时)意味着将始终从优化中获得一些东西。我想尽我所能每秒获得尽可能多的指令(合理)。
如果我是用C ++编程的话,解决方案很清楚。做一些时间。 gprof
。 -O3
等等。我将研究我期望代码运行的架构,并且可能还会查看正在生成的程序集。
不能用javascript做到这一点。我的第一直觉是将内循环中的操作减少到数组查找。在我看来,如果解释器能够将其转换为(希望很短的)一系列整数运算,那么我将能够利用CPU缓存性能。
图灵机非常简单。它实际上是最简单的计算公式(!):它具有有限数量的状态,双向无限磁带,可以在任一方向上移动一个单元的磁带头,并且可以读取和写入单个字符到录影带。
程序在转换函数中编码,该转换函数获取被读取的状态和字符,并且该信息提供要写入的字符,移动头部的方向和新状态。
这是每一步的逻辑:
// states is an array of arrays of triplets and is the transition func
var trans = states[state][alph_index[tape[pos]]];
tape[cur_tape_pos] = trans[0]; // write
cur_tape_pos += trans[1]; // move
state = trans[2]; // state update
该过程在循环中发生。我似乎很清楚磁带是一个数组。我想存储(附加)值到数组的末尾至少是一个使用Javascript数组的摊销的常量时间操作。更不清楚的是,附加到阵列的前面也会有很好的性能,所以我可能想要使用两个阵列,一个向左延伸,一个向右延伸。
问题是,在naive实现中会在内部循环中插入条件语句。我不喜欢那样。无论如何必须已经有条件检查来检查状态是否处于暂停状态。所以也许它不会那么糟糕。
还有一个潜在的优化可以通过将索引存储在字母表而不是磁带上的字母值本身来消除索引到alph_index
。
但主要问题是这个。我还可以采取哪些其他措施来加快速度?有可能让它更快吗?我不知道执行的哪个组件会成为瓶颈(CPU或I / O,还是别的什么?)而且我不知道如何找到它。使用Javascript我也可以使用哈希表,但似乎数组总是更快。
也许我过早地寻求建议。随着我的进步,我会回来编辑性能数字。
作为阅读我的问题的奖励,我将提供我的项目的实时在制品版本的链接:http://stevenlu.net/tm.html
到目前为止,它的操作是操纵填充了代表磁带的div
的{{1}}。它还对字符串执行大量操作,并且还对元素进行大量复制,这些元素在图灵机的实际计算中是完全不必要的。但即便如此,它也能取得不错的表现。我的机器花了大约一分钟来计算600,000左右的步数(5 ^ 4 = 625),即每秒10,000步。这并不是那么糟糕,但我知道我可以通过一些较低级别的编程实现每秒超过一百万。
关于前一代CPU的benchmark perf here,我看到每核心大约10,000 MIPS。因此,我估计如果我可以在运行50 Dhrystone迭代所花费的时间内运行一次我的内循环(即使我不知道这些综合基准实际上做了什么,这似乎很简单的C实现),禁止内存带宽限制,我在一个线程上每秒有2亿次迭代。我的600k步计算将在3ms内完成!!
好吧,如果我能让我的5 ^ 4计算在没有浏览器向我报告它已经挂断的情况下运行,我会非常高兴...
更新
通过更高效的javascript实现完成算法,计算spans
,花费58202209步,计算时间为6173毫秒。那是每秒940万步。比我原来的DOM依赖方法增加了近1,000倍。
原始9^4 = 6561
计算(即使不滚动磁带也需要大约30秒)现在在84毫秒内完成。
答案 0 :(得分:2)
如果你想同时拥有+ ve和-ve整数索引,为什么不使用普通对象呢?你使用任何特殊的数组方法?数组方法大多是通用的,因此可以使用其他本机对象调用(可能不适用于宿主对象,但我认为这不是问题)。
在javascript中,数组只是对象,所以我不明白为什么访问数组[i]应该比object [i]更快或更慢(当然它可能在不同的实现上)。你只需要保留自己的长度属性(可能是正长度和负长度)。
如果您提供一些示例代码,无论性能如何,您都可以获得更具体的建议。
答案 1 :(得分:2)
使用Javascript我也可以使用哈希表,但似乎 像数组一样会更快。
您可能认为JavaScript中的“数组”查找工作方式如下:
[ 1, 2, 3, 4, 5 ]
|------>x
给定索引2,你只需计算:
int operator[] (int index) {
return start_of_data + (sizeof(data_item) * index);
}
这样您的查询只会获得O(1)
时间。
不是这样,至少在传统上,在JavaScript中。通常,“Array”是带有数字键的哈希映射。因此,每次进行查找时,您实际上都是通过哈希函数运行索引(仅作为键)。所以,这个:
a[1]
更像是:
a["1"]
这个,你正在运行一些(可能是不错的)哈希函数来尝试使桶分布规则并最小化冲突。这对我来说听起来很昂贵,但我不知道优化实现是如何的(因为哈希映射查找是分摊的常量时间,但它可能仍然没有那么高效,取决于你遇到多少碰撞以及你遇到的事情一)。
幸运的是,一些(如果不是大多数)现代JavaScript解释器在他们跟踪代码之后了解如何使用密集和稀疏集合,以查看您是否像稀疏或密集阵列一样使用它。仔细检查您期望使用的环境。
我可以采取哪些其他措施来加快速度?是吗 有可能让它更快吗?
我的一个想法是使用typed arrays更有机会获得恒定的时间查找速度。这有助于Fabrice Bellard将整个Linux内核移植到JavaScript /浏览器中(jslinux.org)。
如果我是用C ++编程的话,解决方案很清楚。做一些时间。
如果您打算在浏览器中运行(看起来像你这样做),我推荐使用jsperf.com,因为它们有一个非常好的Java计时器(更精细的任何处理时间的JavaScript,IIRC )或简单地使用node.js或Rhino以及其他命令行分析工具(如果您确实需要,可以在此环境中模拟DOM。)。