是什么让这个脚本与其他方法相比如此高效?
作者对该算法进行了多项优化。其中每一个都需要对如何使用底层机制(例如 Javascript、CPU、寄存器、缓存、视频卡等)有相当深入的了解。我认为他正在做两个关键的优化(其余的只是锦上添花):
在您明确询问缓冲后,我将很快讨论缓冲。他使用的整数数学有两个性能优势:整数加法比字符串操作更便宜,而且它使用更少的内存。
我不知道 JavaScript 和 Web 浏览器如何在浏览器中处理将整数转换为显示字形的过程,因此与字符串相比,将整数传递给 document.write 可能会受到惩罚。但是,他正在执行 (1,000,000 / 1000) 次 document.write 调用,而不是 1,000,000 - 1,000 次整数加法。这意味着他要执行大约 3 个数量级的操作来形成消息,而不是他将消息发送到显示器。因此,将整数与字符串发送到 document.write 的惩罚必须超过 3 个数量级,从而抵消了操作整数的性能优势。
为什么缓冲会加快速度?
具体工作原理取决于您使用的平台、硬件和实现方式。无论如何,这一切都是为了平衡您的算法与您的瓶颈诱导资源。
例如,在文件 I/O 的情况下,缓冲区很有用,因为它利用了旋转磁盘一次只能写入一定数量的事实。给它的工作太少,它不会使用随着磁盘旋转而通过主轴头部下方的所有可用位。给它太多,您的应用程序将不得不等待(或进入睡眠状态)而磁盘完成您的写入 - 可以花费时间准备下一条记录以进行写入!决定文件 I/O 理想缓冲区大小的一些关键因素包括:扇区大小、文件系统块大小、交错、磁头数、旋转速度和面密度等。
在 CPU 的情况下,一切都是为了保持管道满载。如果你给 CPU 的工作太少,它会在等待你执行任务时花时间旋转 NO OP。如果你给 CPU 太多,你可能不会将请求分发到其他资源,例如磁盘或显卡,这些资源可以并行执行。这意味着稍后 CPU 将不得不等待这些返回而无事可做。在 CPU 中进行缓冲的主要因素是使您需要的一切(对于 CPU)尽可能靠近 FPU/ALU。在典型的架构中,这是(按接近度递减的顺序):寄存器、L1 缓存、L2 缓存、L3 缓存、RAM。
如果要在屏幕上写入一百万个数字,那就是用视频卡在屏幕上绘制多边形。像这样想。假设对于添加的每个新数字,显卡必须执行 100,000,000 次操作才能在屏幕上绘制多边形。在一个极端情况下,如果一次在页面上输入 1 个数字,然后让您的显卡将其写出来,然后您为 1,000,000 个数字执行此操作,则显卡将必须执行 10^14 次操作 - 100 万亿次操作!在另一个极端,如果你把全部 100 万个数字一次性全部发送到显卡上,只需要 100,000,000 次操作。最佳点是在中间的某个地方。如果您一次执行一次,CPU 会执行一个工作单元,并在 GPU 更新显示时等待很长时间。如果你先写整个 1M 的项目字符串,那么 GPU 什么都不做,而 CPU 却在不停地运转。
如何确定要使用的缓冲区大小?
除非您的目标是具有特定算法的非常具体且定义明确的平台(例如,为互联网路由编写数据包路由),否则您通常无法通过数学方式确定这一点。通常,您会凭经验找到它。猜一个值,试一试,记录结果,然后选择另一个。您可以根据您正在管理的瓶颈,对从哪里开始以及要调查的范围做出一些有根据的猜测。
这里有没有人有什么技巧可以进一步优化这个脚本?
我不知道这是否可行,我还没有测试过,但是缓冲区大小通常是 2 的倍数,因为计算机的底层固定是二进制的,而字长通常是倍数两个(但并非总是如此!)。例如,64 字节比 60 字节更可能是最佳的,而 1024 比 1000 字节更可能是最佳的。此问题特有的瓶颈之一是迄今为止大多数浏览器(Google Chrome 是我第一个例外)知道)让javascript在与其他网页渲染机制相同的线程中连续运行。这意味着 javascript 做了一些填充缓冲区的工作,然后等待很长时间,直到 document.write 调用返回。如果 javascript 作为单独的进程异步运行,就像在 chrome 中一样,您可能会获得显着的加速。这当然是攻击瓶颈的来源而不是使用它的算法,但有时这是最好的选择。
没有我想要的那么简洁,但希望这是一个很好的起点。缓冲是计算中各种性能问题的重要概念。充分了解代码使用的底层机制(硬件和软件)对于避免或解决性能问题非常有用。