在Javascript中实现Eratosthenes的页面分段筛选

时间:2016-09-03 23:12:46

标签: javascript primes sieve-of-eratosthenes sieve

我最近读到了关于Eratosthenes分段筛的更快实现的真实大数字。

以下是相同的实现:

function sieve(low, high) {

    var primeArray = [], ll = Math.sqrt(low), output = [];

    for (var i = 0; i < high; i++) {
        primeArray[i] = true;
    }

    for (var i = 2; i <= ll; i++) {
        if (primeArray[i]) {
            for (var j = i * i; j < high; j += i) {
                primeArray[j] = false;
            }
        }
    }

    for (var i = 2; i < ll; i++) {
        if(primeArray[i])
        {
            var segmentStart = Math.floor(low/i) * i;

            for(var j = segmentStart; j <= high; j+=i)
            {
                primeArray[j] = false;
            }
        }
    }

    for(var i = low; i <= high; i++)
    {
        if(primeArray[i])
        {
            output.push(i);
        }
    }

    return output;
};

我似乎无法弄清楚我哪里弄错了。 可能一直在工作太久了。

例如:

sieve(4,10)应该返回[5,7] 但它正在返回[5,7,9]

2 个答案:

答案 0 :(得分:2)

这将my previous answer扩展为增加了承诺的内容,但每个答案限制的30,000个字符中没有空格:

第4.5a章-全轮分解的基础

上一答案中第3章的Eratosthenes版本的非最大车轮分解页面分段筛被写为素数生成器,其输出被递归反馈为基数素数输入的输入;尽管这是非常优雅且可扩展的,但在接下来的工作中,我又退回到了更强制的代码样式,以便读者可以更轻松地理解“最大车轮分解”的核心概念。在以后的第4.5b章中,我将把在下面的示例中开发的概念重新组合为Prime Generator样式,并添加一些额外的改进,这些改进不会使它在数十亿的较小范围内变得更快,但是将使该概念在没有速度损失的大部分高达数万亿到数百或数千万亿;然后,Prime Generator格式对于使程序随着范围的扩大而变得更有用。

以下示例的主要额外改进是在各种查找表(LUT)中,这些表用于有效地处理车轮模残数,生成了特殊的起始地址LUT,该地址非常简单地允许一个人为每个车轮计算出剔除起始地址模余数位平面给出了整个结构中第一个剔除对象的起始地址轮索引和第一个模数余数位平面索引,以及使用这些的Sieve Buffer复合数表示剔除函数。

此示例基于一个210号圆周轮,该轮使用两个,三个,五个和七个小的素数,因为这似乎达到了阵列大小和位平面数的效率“最佳点”,但是实验表明,通过将23的圆周数增加到车轮的下一个11,可以将性能再提高5%。之所以没有这样做,是因为初始化时间会大大增加,并且很难为较小的“十亿”范围计时,因为那时只有大约四个分段可以到达该点,而粒度成为问题。

请注意,第一个筛分的数字是23,它是轮毂素数和预剔除素数之后的第一个素数;通过使用此方法,我们避免了处理从“一个”开始的数组的问题,也避免了必须消除某些必须由某些算法重新添加的恢复车轮素数的问题。

基本上,对于每个页面段剔除扫描,都有一个起始循环,该起始循环用段内第一个剔除地址的车轮索引和模余数索引填充起始地址数组,每个基数均小于平方根页段中表示的最大数量的最大值,然后使用此起始地址数组依次依次筛选每个模余数位平面(其中的48个),并对每个位平面扫描所有基本素数,并从段起始地址计算出适当的偏移量通过使用乘数和WHLSTARTS LUT中的偏移量来确定每个基本素数。这是通过将基本质数的车轮索引乘以查找乘数并添加查找偏移量以获得给定模余数位平面的车轮索引起始位置来完成的。从那里开始,每位平面剔除就像第三章中的奇数位平面剔除一样。每个位平面执行48次此操作,但是对于16 Kb字节缓冲区(每个位平面),每页段的有效范围是131072乘以210轮跨度或每个页段的27,525,120个数字。

与第3章仅赔率的筛子相比,使用此筛子可减少内存使用量(该段的有效范围与105的比例之比)为48或不到一半,但这是因为每个段具有所有48位平面,完整的筛网缓冲区是48位平面的16 KB乘以768 KB(兆字节的四分之三)。但是,使用这种大小的Sieve缓冲区只有在达到约160亿时才有效,下一章中的下一个示例将使缓冲区的大小适应较大的范围,从而使最大范围的缓冲区增大到大约100 MB。对于多线程语言(不是JavaScript),这是每个线程的要求。

其他存储要求是存储32位值的基本素数数组,该32位值表示基本素数的车轮索引及其模余索引(如上所述,对于模地址计算是必需的);对于十亿的范围,大约有3600个基本素数乘以四个字节,每个大约为14,000个字节,而其他起始地址数组的大小相同。这些数组作为要筛选的最大值的平方根增长,因此对于基本质数,增长到约5761455(每次乘以四个字节)不到一亿,而筛选到10 ^ 16(一万亿)或每个23 MB。尽管每个线程只需要一半的内存,但是它比扩展的Sieve缓冲区本身所需的内存小得多。

使用“复合”筛子对以下示例进行进一步的改进,其中筛子缓冲器是从较大的砂轮图案中预填充的,从中可获得11、13、17和19的质数因子被淘汰;消除的范围比这更大,这是不切实际的,因为保存的预剔除模式从每个模位平面仅约64千字节增长到48个模数残差数平面中每个模数位平面的约二十倍,即大约一个半兆字节,或约六十个仅仅通过增加23的质数来增加兆字节-再次,这仅占百分之几的性能就在内存和初始化方面付出了巨大的代价。请注意,可以在所有线程之间共享该数组。

实施时,WHLPTRN阵列约为64千乘以48个模数位平面约为3兆字节,这不是那么大,并且其固定大小不会随着筛分范围的增加而变化;就访问速度和初始化时间而言,这是一个相当可行的大小。

这些“最大车轮分解”改进将用于筛选十亿范围的复合数剔除操作循环总数从第三章“仅赔率”示例中的约十亿次操作减少到该“组合”中的约25亿次操作。筛选,目的是尝试使每个剔除操作的CPU时钟周期数保持相同,从而使速度提高四倍。

上述JavaScript示例的实现方式如下:

"use strict";

const LIMIT =  1000000000;

const WHLPRMS = new Uint32Array([2,3,5,7,11,13,17,19]);
const FRSTSVPRM = 23;
const WHLODDCRC = 105 | 0;
const WHLHITS = 48 | 0;
const WHLODDGAPS = new Uint8Array([
  3, 1, 3, 2, 1, 2, 3, 3, 1, 3, 2, 1, 3, 2, 3, 4,
  2, 1, 2, 1, 2, 4, 3, 2, 3, 1, 2, 3, 1, 3, 3, 2,
  1, 2, 3, 1, 3, 2, 1, 2, 1, 5, 1, 5, 1, 2, 1, 2 ]);
const RESIDUES = new Uint32Array([
  23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
  73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 121, 127,
  131, 137, 139, 143, 149, 151, 157, 163, 167, 169, 173, 179,
  181, 187, 191, 193, 197, 199, 209, 211, 221, 223, 227, 229, 233 ]);
const WHLNDXS = new Uint8Array([
  0, 0, 0, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 6, 6,
  7, 7, 7, 8, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 13,
  14, 14, 14, 15, 15, 15, 15, 16, 16, 17, 18, 18, 19, 20, 20,
  21, 21, 21, 21, 22, 22, 22, 23, 23, 24, 24, 24, 25, 26, 26,
  27, 27, 27, 28, 29, 29, 29, 30, 30, 30, 31, 31, 32, 33, 33,
  34, 34, 34, 35, 36, 36, 36, 37, 37, 38, 39, 39, 40, 41, 41,
  41, 41, 41, 42, 43, 43, 43, 43, 43, 44, 45, 45, 46, 47, 47, 48 ]);
const WHLRNDUPS = new Uint8Array( // two rounds to avoid overflow, used in start address calcs...
  [ 0, 3, 3, 3, 4, 7, 7, 7, 9, 9, 10, 12, 12, 15, 15,
    15, 18, 18, 18, 19, 22, 22, 22, 24, 24, 25, 28, 28, 28, 30,
    30, 33, 33, 33, 37, 37, 37, 37, 39, 39, 40, 42, 42, 43, 45,
    45, 49, 49, 49, 49, 52, 52, 52, 54, 54, 57, 57, 57, 58, 60,
    60, 63, 63, 63, 64, 67, 67, 67, 70, 70, 70, 72, 72, 73, 75,
    75, 78, 78, 78, 79, 82, 82, 82, 84, 84, 85, 87, 87, 88, 93,
    93, 93, 93, 93, 94, 99, 99, 99, 99, 99, 100, 102, 102, 103, 105,
    105, 108, 108, 108, 109, 112, 112, 112, 114, 114, 115, 117, 117, 120, 120,
    120, 123, 123, 123, 124, 127, 127, 127, 129, 129, 130, 133, 133, 133, 135,
    135, 138, 138, 138, 142, 142, 142, 142, 144, 144, 145, 147, 147, 148, 150,
    150, 154, 154, 154, 154, 157, 157, 157, 159, 159, 162, 162, 162, 163, 165,
    165, 168, 168, 168, 169, 172, 172, 172, 175, 175, 175, 177, 177, 178, 180,
    180, 183, 183, 183, 184, 187, 187, 187, 189, 189, 190, 192, 192, 193, 198,
    198, 198, 198, 198, 199, 204, 204, 204, 204, 204, 205, 207, 207, 208, 210, 210 ]);
const WHLSTARTS = function () {
  let arr = new Array(WHLHITS);
  for (let i = 0; i < WHLHITS; ++i) arr[i] = new Uint16Array(WHLHITS * WHLHITS);
  for (let pi = 0; pi < WHLHITS; ++pi) {
    let mltsarr = new Uint16Array(WHLHITS);
    let p = RESIDUES[pi]; let i = (p - FRSTSVPRM) >> 1;
    let s = ((i << 1) * (i + FRSTSVPRM) + (FRSTSVPRM * ((FRSTSVPRM - 1) >> 1))) | 0;
    // build array of relative mults and offsets to `s`...
    for (let ci = 0; ci < WHLHITS; ++ci) {
      let rmlt = (RESIDUES[((pi + ci) % WHLHITS) | 0] - RESIDUES[pi | 0]) >> 1;
      rmlt += rmlt < 0 ? WHLODDCRC : 0; let sn = s + p * rmlt;
      let snd = (sn / WHLODDCRC) | 0; let snm = (sn - snd * WHLODDCRC) | 0;
      mltsarr[WHLNDXS[snm]] = rmlt | 0; // new rmlts 0..209!
    }
    let ondx = (pi * WHLHITS) | 0
    for (let si = 0; si < WHLHITS; ++si) {
      let s0 = (RESIDUES[si] - FRSTSVPRM) >> 1; let sm0 = mltsarr[si];
      for (let ci = 0; ci < WHLHITS; ++ci) {
        let smr = mltsarr[ci];
        let rmlt = smr < sm0 ? smr + WHLODDCRC - sm0 : smr - sm0;
        let sn = s0 + p * rmlt; let rofs = (sn / WHLODDCRC) | 0;
        // we take the multiplier times 2 so it multiplies by the odd wheel index...
        arr[ci][ondx + si] = ((rmlt << 9) | (rofs | 0)) >>> 0;
      }
    }
  }
  return arr;
}();
const PTRNLEN = (11 * 13 * 17 * 19) | 0;
const PTRNNDXDPRMS = new Uint32Array([ // the wheel index plus the modulo index
  (-1 << 6) + 44, (-1 << 6) + 45, (-1 << 6) + 46, (-1 << 6) + 47 ]);

function makeSieveBuffer(szbits) { // round up to 32 bit boundary!
  let arr = new Array(WHLHITS); let sz = ((szbits + 31) >> 5) << 2;
  for (let ri = 0; ri < WHLHITS; ++ri) arr[ri] = new Uint8Array(sz);
  return arr;
}

function cullSieveBuffer(lwi, bps, prmstrts, sb) {
  let len = sb[0].length; let szbits = len << 3;
  let lowndx = lwi * WHLODDCRC; let nxti = (lwi + szbits) * WHLODDCRC;
  // set up prmstrts for use by each modulo residue bit plane...
  for (let pi = 0, bpslmt = bps.length; pi < bpslmt; ++pi) {
    let ndxdprm = bps[pi];
    let prmndx = ndxdprm & 0x3F; let pd = ndxdprm >> 6;
    let rsd = RESIDUES[prmndx]; let bp = pd * (WHLODDCRC << 1) + rsd;
    let i = (bp - FRSTSVPRM) >> 1;
    let s = ((i << 1) * (i + FRSTSVPRM) + (FRSTSVPRM * ((FRSTSVPRM - 1) >> 1))) | 0;
    if (s >= nxti) { prmstrts[pi] = 0xFFFFFFFF; break; } // enough base primes!
    if (s >= lowndx) s -= lowndx | 0;
    else {
      let wp = (rsd - FRSTSVPRM) >> 1; let r = (lowndx - s) % (WHLODDCRC * bp);
      s = r == 0
        ? 0 | 0
        : (bp * (WHLRNDUPS[(wp + ((r + bp - 1) / bp) | 0) | 0] - wp) - r) | 0;
    }
    let sd = (s / WHLODDCRC) | 0; let sn = WHLNDXS[(s - sd * WHLODDCRC) | 0];
    prmstrts[pi | 0] = (sn << 26) | sd;
  }
//  if (szbits == 131072) return;
  for (let ri = 0; ri < WHLHITS; ++ri) {
    let pln = sb[ri]; let plnstrts = WHLSTARTS[ri];
    for (let pi = 0, bpslmt = bps.length; pi < bpslmt; ++pi) {
      let prmstrt = prmstrts[pi | 0]; if (prmstrt == 0xFFFFFFFF) break;
      let ndxdprm = bps[pi | 0];
      let prmndx = ndxdprm & 0x3F; let pd = ndxdprm >> 6;
      let bp = (((pd * (WHLODDCRC << 1)) | 0) + RESIDUES[prmndx]) | 0;
      let sd = prmstrt & 0x3FFFFFF; let sn = prmstrt >>> 26;
      let adji = (prmndx * WHLHITS + sn) | 0; let adj = plnstrts[adji];
      sd += ((((adj >> 8) * pd) | 0) + (adj & 0xFF)) | 0;
      if (bp < 64) {
        for (let slmt = Math.min(szbits, sd + (bp << 3)) | 0; sd < slmt; sd += bp) {
          let msk = (1 << (sd & 7)) >>> 0;
//          for (let c = sd >> 3, clmt = len == 16384 ? 0 : len; c < clmt; c += bp) pln[c] |= msk;
          for (let c = sd >> 3; c < len; c += bp) pln[c] |= msk;
        }
      }
//      else for (let sdlmt = szbits == 131072 ? 0 : szbits; sd < sdlmt; sd += bp) pln[sd >> 3] |= (1 << (sd & 7)) >>> 0;
      else for (; sd < szbits; sd += bp) pln[sd >> 3] |= (1 << (sd & 7)) >>> 0;
    }
  }
}

const WHLPTRN = function () {
  let sb = makeSieveBuffer((PTRNLEN + 16384) << 3); // avoid overflow when filling!
  cullSieveBuffer(0, PTRNNDXDPRMS, new Uint32Array(PTRNNDXDPRMS.length), sb);
  return sb;
}();

const CLUT = function () {
  let arr = new Uint8Array(65536);
  for (let i = 0; i < 65536; ++i) {
    let nmbts = 0|0; let v = i;
    while (v > 0) { ++nmbts; v &= (v - 1)|0; }
    arr[i] = nmbts|0;
  }
  return arr;
}();

function countSieveBuffer(bitlmt, sb) {
  let lstwi = (bitlmt / WHLODDCRC) | 0;
  let lstri = WHLNDXS[(bitlmt - lstwi * WHLODDCRC) | 0];
  let lst = lstwi >> 5; let lstm = lstwi & 31;
  let count = (lst * 32 + 32) * WHLHITS;
  for (let ri = 0; ri < WHLHITS; ++ri) {
    let pln = new Uint32Array(sb[ri].buffer);
    for (let i = 0; i < lst; ++i) {
      let v = pln[i]; count -= CLUT[v & 0xFFFF]; count -= CLUT[v >>> 16];
    }
    let msk = 0xFFFFFFFF << lstm; if (ri <= lstri) msk <<= 1;
    let v = pln[lst] | msk; count -= CLUT[v & 0xFFFF]; count -= CLUT[v >>> 16];
  }
  return count;
}

function fillSieveBuffer(lwi, sb) {
  let mod = (lwi >> 3) % PTRNLEN;
  for (let ri = 0; ri < WHLHITS; ++ri) {
    let pln = sb[ri]; let len = pln.length;
    pln.set(WHLPTRN[ri].subarray(mod, mod + len));
  }
}

let startx = +Date.now();
const cmpsts = makeSieveBuffer(16384 << 3);
const bparr = function () {
  let szbits = (((((((Math.sqrt(LIMIT) | 0) - 23) >> 1) + WHLODDCRC - 1) / WHLODDCRC)
                                                                     + 31) >> 5) << 5;
  let cmpsts = makeSieveBuffer(szbits); fillSieveBuffer(0, cmpsts);
  let ndxdrsds = new Uint32Array(RESIDUES.length - 1);
  for (let i = 0; i < ndxdrsds.length; ++i) ndxdrsds[i] = i >>> 0;
  cullSieveBuffer(0, ndxdrsds, new Uint32Array(ndxdrsds.length), cmpsts);
  let len = countSieveBuffer(szbits * WHLODDCRC - 1, cmpsts);
  let ndxdprms = new Uint32Array(len); let j = 0;
  for (let i = 0; i < szbits; ++i)
    for (let ri = 0; ri < WHLHITS; ++ri)
      if ((cmpsts[ri][i >> 3] & (1 << (i & 7))) == 0) {
        ndxdprms[j++] = ((i << 6) + ri) >>> 0;
      }
  return ndxdprms;
}();
let count = 8; let lwilmt = (LIMIT - 23) / (2 * WHLODDCRC);
let strts = new Uint32Array(bparr.length);
for (let lwi = 0; lwi <= lwilmt; lwi += 131072) {
  let nxti = lwi + 131072;
  fillSieveBuffer(lwi, cmpsts);
  cullSieveBuffer(lwi, bparr, strts, cmpsts);
  if (nxti <= lwilmt) count += countSieveBuffer(131072 * WHLODDCRC - 1, cmpsts);
  else count += countSieveBuffer((LIMIT - 23 - lwi * (WHLODDCRC << 1)) >> 1, cmpsts);
}
let elpsdx = +Date.now() - startx;
console.log("Found " + count + " primes up to " + LIMIT + " in " + elpsdx + " milliseconds.");

以上代码的运行速度仅比第3章“仅奇数”页面细分代码运行速度快三倍半,而不是由于预期的速度快四倍原因如下:

  1. 使用带有固定掩模图案的特殊简化剔除循环的加速技术不再像几乎没有任何小的剔除基础素数一样有效。这使每个复合数字剔除操作的平均时钟周期数增加了大约20%,尽管这适用于较慢的语言(例如JavaScript),而不是更有效的机器代码编译语言,因为它们可以使用其他技术(例如极限循环展开)来进一步将每个剔除操作循环的CPU时钟周期数减少到每个剔除循环的1.25个时钟周期左右。

  2. 尽管由于模位平面的模数较少(大约减少了两倍),所以计算所得素数的开销减少了大约两倍,而不是所需的四倍。在使用JavaScript的情况下,情况变得更糟,该JavaScript无法利用CPU POP_COUNT机器指令,其速度比此处使用的Counting LUT(CLUT)技术快约十倍。

  3. 尽管此处使用的LUT技术将起始地址的计算开销比所需的更复杂的模数计算的开销减少了五倍左右,但这些计算的开销大约是原来的二分之三到三倍。它比第3章中“奇数”筛子所要求的要复杂,因此不足以使我们实现比率度量的降低;我们将需要一种将时间减少两倍左右的技术,以使这些计算不会对减少的比例有所贡献。相信我,我尽力了,但是还没有使它变得更好。就是说,这些计算在比JavaScript和/或比我的超低端Atom CPU处理器更好的CPU的更高效的计算机语言中可能更高效,但是复合数字剔除速度也可能也更高效。 !

还是,速度提高了三分半,只增加了大约50%的代码行数,这还不错吗?在较新版本的节点/ Google Chrome浏览器上运行时,此JavaScript代码的运行速度仅慢三到五倍(取决于CPU,高端CPU的性能更接近)(Chrome 75仍约为25%比Firefox 68更快)比金·瓦里施(Kim Walisch)用“ C”编写并编译为x86_64本机代码的“ primesieve”

即将推出第4.5b章,它将多出两倍半的代码,但它将成为Prime生成器,能够筛选极大的范围,部分原因是JavaScript仅能有效地表示数字到53位或9e15左右的64位浮点尾数,也就是一个人要等待的时间:在更好的CPU上筛选到1e14大约需要一天的时间,但这并不是很多问题-只需打开浏览器标签即可!

答案 1 :(得分:2)

附录:使用其他JavaScript生成语言

直到my Chapter 4a answer,根据问题的请求和代码,该线程中仅使用了JavaScript。但是,在我看来,编写超过两百行代码时,编写JavaScript不再是正确的方法。造成这种情况的原因如下:

  1. JavaScript代码是在很久以前设计的,因此其编程模型已经过时。即使已根据ECMA2015及更高版本对其进行了(大量)更新,但大多数情况都显示了其对遗留代码的支持。但是,这些更新导致太多的工作方式,但并非所有方法都高效,并且使程序员对最佳方法感到困惑。
  2. JavaScript难以使用原型模型来支持一个版本的面向对象编程(OOP)!
  3. JavaScript是动态类型的,可能导致意外的代码错误,并使维护和重构代码变得更加困难。
  4. 为了提高速度而进行的手工编码(通常使用asm.js和较新的功能)并非总能产生最佳代码,除非人们对实际上将执行代码的JavaScript引擎所进行的这些优化了解很多。

有两种主流选择,可以使用另一种语言通过转译来生成JavaScript,其中两种最常见(也是最好的)如下:

  1. Microsoft的Typescript是一种(可选)静态类型的OOP语言,由于上述原因,它已经变得非常流行。
  2. 我最喜欢的是寓言,它是Microsoft主要基于功能的基于ML的语言F#的分支,其开发目的是产生有效的JavaScript代码,是一种甚至更好的静态类型检查语言,但它提供类型推断和各种简洁功能

如果我能再避免它,我就不做面向对象编程(函数编程-FP-是在需要的时候对我有用的方法,如下所示),但是here is a Fable version of the Chapter 4a code(在移动设备上,查看您的浏览器作为桌面网站使用);至于第4a章的代码,应该多次按“筛选”按钮,以使JavaScript引擎能够对生成的代码进行热调以进行优化,从而提高速度,该速度将在四到五次迭代后达到。使用Fable,即使使用用户界面(UI),也可以通过使用基于Elmish React的库来完全避免命令性代码,在此我为了避免混淆示例代码而没有这样做。同样,为了提高速度,我继续使用筛网剔除缓冲区作为可变数组-即使最终的FP语言Haskell也允许这种突变,尽管受到“ ST” monad的保护(可以在寓言/ F#,但它们表现不佳,因为Haskell没有自定义优化)。

CORRECTIONS_TO_COMMENTS::Tt证明Chrome V8 JavaScript引擎似乎已经优化了不可变的cull突变,并且第一次更改的速度差异是由于使用命令式JavaScript for / while循环而不是比Fable使用循环模拟尾递归函数要慢一些。直接调用JavaScript的更好数组副本的最大效果。使用不同的位长度进行计数也是一个很小的改进。总体改进幅度约为25%,但是复制效果与其他两个方面的总和大致相同。

在按上述链接打开的页面中,您可以查看生成的JavaScript代码,并看到生成的纯asm.js代码比手工编写的代码更好(更一致),但是寓言代码通过强制其在以下三个位置发出JavaScript代码来为性能提供一点帮助:

  1. 寓言/ F#代码在默认情况下是不可变的,并且为了忠实于功能精神,我使用了不可变的尾调用递归函数循环,但这比使用命令式for / while循环要慢一些。
  2. 用于数组复制/复制的寓言库(至少)还没有使用最快的JavaScript方法(设置子数组/切片),因此我强迫它发出JavaScript来做到这一点。
  3. 在计算剔除数组中未设置的位时,Fable不提供TypedArray视图的其他位宽(我不认为),因此我通过发出JavaScript来添加它,因为它对Uint32视图的使用比使用四个更快连续的Uint8会通过位移手动读取并组装Uint32。

您会发现生成的代码与第4a章中的手写JavaScript代码一样快!