JS中的快速矩阵数学
#javascript #性能 #deno #math

有很多用来表示JS中矩阵数学的方法。有些是可读的,有些是快速的。我想稍微探索差异。某些技术实际上救了我多少钱?

为此,我将要查看一个操作:元素添加以减少总案例,但是差异操作可以稍微改变整体值,尤其是诸如矩阵乘法之类的值,矩阵乘法需要更复杂的应用程序规则。各州也在我的计算机上,使用deNo(v8)较大的i7 8700k。如果有不同的优化,则不同的运行时间可能会有所不同。

简单的功能方式

我以为我会从这里开始,因为这就是我可以为初稿编写它的方式。这是非常优化的代码,但我怀疑性能实际上很差。不变性非常适合避免虫子,但对于perf来说很可怕,尤其是当JS没有智能副本时。

//mat.js
export function addMatrixFunc(a, b) {
    return a.map((row, ri) => row.map((val, ci) => b[ri][ci] + val));
}

矩阵表示是数组的数组。外部数组是行,内部阵列是列。

使用Deno的内置基准测试工具,我们可以看到它在不同尺寸的矩阵上的性能。

import { addMatrixFunc } from "./mat.js";
import { mat100A, mat100B } from "./data/mat-data.js";

Deno.bench("Add 1x1", () => {
    addMatrixFunc([[44]], [[65]]);
});

Deno.bench("Add 2x2", () => {
    addMatrixFunc([[44]], [[65]]);
});

Deno.bench("Add 4x4", () => {
    addMatrixFunc([[44]], [[65]]);
});

/* ... */

Deno.bench("Add 100x100", () => {
    addMatrixFunc(mat100A, mat100B);
});

mat100Amat100B是预先生成的100x100矩阵,太大了,无法放入测试文件中。

这里的一些笔记是,我认为Deno至少不再让您设置迭代或热身迭代了。我认为它只是寻找数字的融合。实际运行次数显示在JSON输出中,每个测试略有不同。

这是我们的工作方式:

名称 最低 max avg p75 p99 p995
添加1x1(func) 63ns 180ns 70ns 74NS 113ns 124ns
添加2x2(func) 144ns 208NS 152ns 158ns 184ns 196NS
添加4x4(func) 312NS 373NS 329NS 335NS 370NS 373NS
添加8x8(func) 694ns 930ns 724NS 731NS 930ns 930ns
添加16x16(func) 1798ns 1942ns 1836ns 1843ns 1942ns 1942ns
添加32x32(func) 5274NS 6599ns 5495NS 5605NS 6599ns 6599ns
添加64x64(func) 13000ns 2331200NS 17451NS 16300NS 41900NS 60700ns
添加100x100(func) 30800NS 512800NS 40269ns 38200NS 105700NS 218300NS

循环

所以我认为我们可以改进的第一种方法是循环。功能有开销

export function addMatrixLoop(a, b) {
    const out = [];
    for (let row = 0; row < a.length; row++) {
        const arrayRow = [];
        for (let col = 0; col < a[0].length; col++) {
            arrayRow.push(a[row][col] + b[row][col])
        }
        out.push(arrayRow);
    }
    return out;
}

请注意,我不会进行严格的界限检查,我们只是假设ab的大小与边界检查相同。

名称 最低 max avg p75 p99 p995
添加1x1(loop) 28ns 210NS 46ns 47ns 142ns 168ns
添加2x2(loop) 55ns 163ns 71NS 76ns 125ns 143ns
添加4x4(loop) 122ns 227NS 143ns 151ns 195ns 225ns
添加8x8(loop) 360NS 807NS 411NS 422NS 744NS 807NS
添加16x16(loop) 1179ns 1246ns 1208NS 1217ns 1246ns 1246ns
添加32x32(loop) 5031NS 5216NS 5090NS 5105NS 5216NS 5216NS
添加64x64(loop) 14300ns 362400NS 20651NS 19200ns 52900NS 110500NS
添加100x100(循环) 38200NS 425400NS 54401NS 54100NS 227700NS 256300NS

循环启动更快,但是一旦我们击中32x32左右,它们就等于.map,并且比.map更大。非常令人惊讶!

预先分配阵列

我的下一个想法是预先分配数组,因为将数组推入数组可能会导致重新尺寸,也许这就是为什么它较慢。

export function addMatrixLoopPreAlloc(a, b) {
    const out = new Array(a.length);
    for (let row = 0; row < a.length; row++) {
        const arrayRow = new Array(a[0].length);
        for (let col = 0; col < a[0].length; col++) {
            arrayRow[col] = a[row][col] + b[row][col];
        }
        out[row] = arrayRow;
    }
    return out;
}
名称 最低 max avg p75 p99 p995
添加1x1(loop prealloc) 13ns 137ns 18NS 20NS 56ns 73ns
添加2x2(loop prealloc) 25ns 65ns 28ns 27ns 45ns 53ns
添加4x4(loop prealloc) 61NS 152ns 73ns 78ns 124ns 129ns
添加8x8(loop prealloc) 203ns 444ns 228NS 232ns 348NS 434ns
添加16x16(loop prealloc) 710ns 942NS 762NS 768ns 942NS 942NS
添加32x32(循环prealloc) 2648NS 2769ns 2700NS 2716ns 2769ns 2769ns
添加64x64(循环prealloc) 9500NS 372100NS 10926ns 10100NS 25000NS 35800NS
添加100x100(循环prealloc) 24500NS 515800NS 28392NS 26300NS 62100NS 204400NS

那做了这个技巧!我们比开始的地方快1.5倍!

展开循环

如果我们只是删除了所有循环并长时间写了它?

export function addMatrix4x4(a, b) {
    return [
        [a[0][0] + b[0][0], a[0][1] + b[0][1], a[0][2] + b[0][2], a[0][3] + b[0][3]],
        [a[1][0] + b[1][0], a[1][1] + b[1][1], a[1][2] + b[1][2], a[1][3] + b[1][3]],
        [a[2][0] + b[2][0], a[2][1] + b[2][1], a[2][2] + b[2][2], a[2][3] + b[2][3]],
        [a[3][0] + b[3][0], a[3][1] + b[3][1], a[3][2] + b[3][2], a[3][3] + b[3][3]]
    ];

}

这不是很灵活,因为您需要要添加的每个矩阵形状的功能。但是,在某些情况下,像3D这样的情况还不错,因为您的数量非常有限,通常只有4x4。在机器学习中,这可能会引起问题。

这是一个为展开的循环生成JavaScript文本的函数:

export function genMatAddBody(rows, cols) {
    let funcBody = "return [\n";

    for (let r = 0; r < rows; r++) {
        funcBody += "\t\t["
        for (let c = 0; c < cols; c++) {
            funcBody += `a[${r}][${c}] + b[${r}][${c}]${c < cols - 1 ? ", " : ""}`
        }
        funcBody += `]${r < rows - 1 ? ", " : ""}\n`
    }

    funcBody += `\t];\n`
    return funcBody;
}
export function genMatAddFunc(rows, cols) {
    rows = Number(rows);
    cols = Number(cols);
    const body = genMatAddBody(rows, cols);
    return new Function("a", "b", body);
}

我也很好奇,如果使这一动态变化很大:

export function genMatAddFunc(rows, cols) {
    rows = Number(rows); //prevents code injection
    cols = Number(cols);
    const body = genMatAddBody(rows, cols);
    return new Function("a", "b", body);
}

由于我们正在使用评估,我们应该确保对输入进行消毒。

const addMatrix1x1Dyn = genMatAddFunc(1,1);
const addMatrix2x2Dyn = genMatAddFunc(2,2);
const addMatrix4x4Dyn = genMatAddFunc(4,4);
// etc.
const addMatrix100x100Dyn = genMatAddFunc(100,100);
名称 最低 max avg p75 p99 p995
添加1x1(展开) 7ns 34NS 8NS 8NS 19NS 20NS
添加1x1(展开的动态) 7ns 40ns 8NS 7ns 19NS 20NS
添加2x2(展开) 11ns 46ns 13ns 12ns 26ns 29ns
添加2x2(展开的动态) 11ns 39ns 12ns 12ns 27ns 29ns
添加4x4(展开) 36ns 159ns 59ns 72NS 124ns 130ns
添加4x4(展开的动态) 36ns 236ns 67ns 84NS 156ns 181ns
添加8x8(展开) 92NS 243ns 130ns 142ns 235ns 242ns
添加8x8(展开的动态) 89ns 262NS 113ns 119ns 186ns 209ns
添加16x16(展开) 500NS 672800NS 734ns 600NS 3400NS 10500NS
添加16x16(展开的动态) 500NS 2052000NS 799ns 600NS 6400NS 10600NS
添加32x32(展开) 73800NS 562500NS 83976ns 85200NS 136400NS 160600NS
添加32x32(展开的动态) 73000ns 908200NS 90772NS 90900NS 137900NS 162600NS
添加64x64(展开) 328700NS 737300NS 350104NS 343900NS 574500NS 587000NS
添加64x64(展开的动态) 327600NS 698800NS 349201NS 345400NS 573900NS 592400NS
添加100x100(展开) 829600NS 1250900NS 876580NS 873700NS 1143900NS 1157500NS
添加100x100(展开的动态) 816900NS 1416300NS 891844NS 894500NS 1227700NS 1288200NS

对于小价值观,击败预先分配的环路约1.5至2倍,这是一个很大的进步,但对于大型循环的速度较慢。我不确定为什么是这样,也许必须适应功能本身的大小?生成的代码很大。同样,动态的生成基本上与写出它们相同。因此,如果您想节省有效载荷(并且不受CSP的限制),则可以在无惩罚的情况下动态创建这些。

使阵列变平

我认为我们可以保存的另一件事是数组。从技术上讲,我们不需要有很多嵌套阵列,它们会添加一些开销来创建。所以现在有一个2x2阵列看起来像这样:

[
    4, 7,
    10, 5
]

但是,您现在需要知道它可以使用的尺寸,因为不同的矩形形状可以具有相同数量的元素。因此,也许让我们成为对象。

{
    shape: [2,2],
    data: [
        4, 7,
        10, 5
    ]
}

形状是阵列而不是属性,因为我们可以将此想法扩展到n维张量。实际上,这就是TensorFlowJs之类的库这样做的方式。为了方便起见,让我们构建一些功能以在格式之间转换。

export function nestedArrayToFlat(nested){
    return {
        shape: [nested.length, nested[0].length],
        data: nested.flat(Infinity)
    }
}

export function flatToNestedArray(flat){
    const data = new Array(flat.shape[0]);
    for(let row = 0; row < flat.shape[0]; row++){
        const rowArray = new Array(flat.shape[1]);
        for(let col = 0; col < flat.shape[1]; col++){
            rowArray[col] = flat.data[row * flat.shape[1] + col]; 
        }
        data[row] = rowArray;
    }
    return data;
}

到目前为止,我认为预先分配的阵列和循环具有最佳的一般性能扩展到更大的值,因此我们现在坚持下去。这也意味着我将省略平坦和循环,因为它们没有在任何类别中获胜,也没有动态。

export function addMatrixFlat(a, b) {
    const out = {
        shape: a.shape,
        data: new Array(a.data.length)
    };
    for (let row = 0; row < a.shape[0]; row++) {
        for (let col = 0; col < a.shape[1]; col++) {
            const index = (row * a.shape[1]) + col;
            out.data[index] = a.data[index] + b.data[index];
        }
    }
    return out;
}
名称 最低 max avg p75 p99 p995
添加1x1(flat) 9NS 53ns 10ns 10ns 24ns 29ns
添加2x2(flat) 14NS 49ns 15ns 15ns 29ns 30ns
添加4x4(flat) 32ns 107ns 40ns 46ns 86ns 94NS
添加8x8(flat) 97ns 167ns 110NS 113ns 143ns 157ns
添加16x16(flat) 400NS 548NS 436ns 447ns 517NS 548NS
添加32x32(flat) 1985ns 2900NS 2222NS 2276ns 2900NS 2900NS
添加64x64(flat) 8512NS 10514NS 8775NS 8715NS 10514NS 10514NS
添加100x100(flat) 15500NS 701100NS 23261NS 21800NS 54200NS 194800ns

在更大的矩阵上比以前的最佳矩阵快20%,但在1x1和2x2上的矩阵速度比独立的矩阵慢20%。由于这些并不重要,我会说这是一个巨大的胜利。

行与专栏专业

我们是否越过行与列相对于列,是否有关系?有人可能会怀疑当CPU缓存某些内容参与其中时,我们可能会进行测试。

export function addMatrixFlatColMajor(a, b) {
    const out = {
        shape: a.shape,
        data: new Array(a.data.length)
    };
    for (let col = 0; col < a.shape[1]; col++) {
        for (let row = 0; row < a.shape[0]; row++) {
            const index = (row * a.shape[1]) + col;
            out.data[index] = a.data[index] + b.data[index];
        }
    }
    return out;
}
名称 最低 max avg p75 p99 p995
添加1x1(平面Col Major) 9NS 41NS 10ns 9NS 21ns 22ns
添加2x2(平面Col Major) 14NS 41NS 15ns 14NS 29ns 32ns
添加4x4(平面Col Major) 32ns 79ns 37ns 37ns 61NS 67ns
添加8x8(平面Col Major) 101NS 156ns 114ns 116ns 147ns 153ns
添加16x16(平面Col Major) 423ns 532NS 453ns 465ns 513ns 532NS
添加32x32(平面Col Major) 2047ns 3228NS 2199ns 2258NS 3228NS 3228NS
添加64x64(平面Col Major) 7500NS 413800NS 10417NS 10200NS 26200NS 37000NS
添加100x100(平面Col Major) 19800ns 575300NS 25090NS 23500NS 63000NS 198500NS

事实证明,列大遍历实际上比行大遍历慢一点。这可能是因为缓存线的读取更最佳。

但是,由于元素的添加非常简单,我们实际上可以抛弃循环结构,只用一个环线性地添加所有元素。

export function addMatrixFlatSimple(a, b) {
    const out = {
        shape: a.shape,
        data: new Array(a.data.length)
    };
    for(let i = 0; i < a.data.length; i++){
        out.data[i] = a.data[i] + b.data[i];
    }
    return out;
}
名称 最低 max avg p75 p99 p995
添加1x1(平面简单) 7ns 46ns 8NS 8NS 18NS 20NS
添加2x2(平面简单) 9NS 54NS 10ns 10ns 23ns 26ns
添加4x4(平面简单) 18NS 77ns 24ns 28ns 51NS 56ns
添加8x8(平面简单) 55ns 159ns 73ns 78ns 125ns 136ns
添加16x16(平面简单) 276ns 405NS 315ns 335NS 393NS 405NS
添加32x32(平面简单) 1387ns 1682ns 1490NS 1547ns 1682ns 1682ns
添加64x64(平面简单) 6381NS 7219NS 6602NS 6675NS 7219NS 7219NS
添加100x100(平面简单) 9000NS 598000NS 17166ns 15700NS 49400NS 178400NS

这就像20%+更快。

展开

我们也可以展开这些内容,看看会发生什么,也许更简单的结构会有所帮助?使用此代码:

export function genMatAddFlatBody(rows, cols){
    let funcBody = "return [\n";

    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            funcBody += `a[${r * cols + c}] + b[${r * cols + c}]${(c * r) < ((rows - 1) * (cols - 1)) ? ", " : ""}`
        }
    }

    funcBody += `];\n`
    return funcBody;
}

我们可以生成这样的函数:

export function addMatrixFlat2x2(a, b) {
    return [
        a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];

}

我们可以通过这样的评估动态创建它们:

export function genMatAddFlatFunc(rows, cols) {
    rows = Number(rows);
    cols = Number(cols);
    const body = genMatAddFlatBody(rows, cols);
    return new Function("a", "b", body);
}
名称 最低 max avg p75 p99 p995
添加1x1(平面展开) 6ns 53ns 7ns 7ns 19NS 22ns
添加2x2(平面外) 7ns 62ns 8NS 8NS 21ns 23ns
添加4x4(平面展开) 24ns 136ns 37ns 41NS 84NS 93NS
添加8x8(平面展开) 61NS 185ns 81NS 86ns 131ns 144ns
添加16x16(平面展开) 300NS 564700NS 508NS 400NS 1000NS 6100NS
添加32x32(平面展开) 63600NS 826700NS 74574NS 75200NS 133000ns 162600NS
添加64x64(平面展开) 263500NS 788800NS 286503NS 280600NS 502900NS 528900NS
添加100x100(平面展开) 706400NS 1760300NS 764369ns 758900NS 1102800NS 1118900NS

它只是在1x1和2x2上越过简单的循环,并且在大尺寸的情况下会失去并变得更糟。

打字阵列

因此,我可以看到的下一个可能的优化领域是实际使用类型。我们可以在JavaScript中使用类型阵列进行此操作。这将使我们能够分配一块内存并减少任何数组结构的开销。不过,这实际上更重要。通过使用类型阵列,我们实际上可以减少转换。像WASM,WebGL和WebGPU这样的API处理内存块,而我们要更快地转换的速度就越少。因此,我认为即使事实证明这要慢一点,仍然有充分的理由追求它。尽管我们最终走了不同的路径,但一个用于浮子,一个用于整数,即使我们选择不同的位宽度也可能很重要。另外,由于我们已经证明平坦结构的整体表现更好,因此我们不需要考虑嵌套阵列。简而言之

浮点64

名称 最低 max avg p75 p99 p995
添加1x1(f64) 330ns 1600NS 400NS 397NS 663ns 1600NS
添加2x2(f64) 329NS 598NS 393NS 409NS 493NS 598NS
添加4x4(f64) 393NS 1786ns 490NS 503NS 662NS 1786ns
添加8x8(f64) 490NS 778ns 621NS 664ns 778ns 778ns
添加16x16(f64) 1024ns 5425NS 1311ns 1334ns 5425NS 5425NS
添加32x32(f64) 3346ns 4707NS 3772NS 4115NS 4707NS 4707NS
添加64x64(f64) 8000NS 2309700NS 14203NS 12700NS 35300NS 44800NS
添加100x100(F64) 23200NS 3328400NS 35026NS 33300NS 82400NS 231000NS

JavaScript号码是Float 64。因此,这些行为比普通的JavaScript阵列慢,这确实令人惊讶。进行小阵列实际上比array.map慢。我猜这与引擎如何对待它们有关。随着矩阵变得更大,这些速度变得更快,但是即使在100x100项目中,它仍然比普通的平面阵列要慢。

浮点32

名称 最低 max avg p75 p99 p995
添加1x1(f32) 324ns 554NS 380NS 391NS 506ns 554NS
添加2x2(f32) 324ns 594NS 391NS 408NS 520NS 594NS
添加4x4(f32) 396ns 658NS 463ns 489ns 569ns 658NS
添加8x8(f32) 508NS 822NS 620NS 673NS 822NS 822NS
添加16x16(f32) 1148NS 1784ns 1345ns 1422ns 1784ns 1784ns
添加32x32(F32) 3258NS 3840NS 3344ns 3337NS 3840NS 3840NS
添加64x64(F32) 10500NS 1101800NS 18473ns 21600NS 66500NS 101200NS
添加100x100(F32) 25800NS 1797500NS 37062NS 35800NS 99800NS 245400NS

f32阵列与float64s具有相同的问题。尽管较小,但性能几乎是相同的,因此对于纯速度来说,选择它们是没有意义的。实际上,在100x100时,F64阵列的速度更快。我们获得的唯一好处是一半的记忆,这可能是选择这些的原因。

INT 32

名称 最低 max avg p75 p99 p995
添加1x1(i32) 321NS 1015ns 390NS 398NS 704NS 1015ns
添加2x2(i32) 324ns 570NS 390NS 403ns 501NS 570NS
添加4x4(i32) 372NS 530ns 426ns 443ns 488ns 530ns
添加8x8(i32) 455ns 621NS 539ns 575NS 616ns 621NS
添加16x16(i32) 784ns 1202NS 913NS 966ns 1202NS 1202NS
添加32x32(i32) 2111ns 2704NS 2182NS 2182NS 2704NS 2704NS
添加64x64(i32) 8742NS 9569ns 9138NS 9305NS 9569ns 9569ns
添加100x100(i32) 12600NS 2578300NS 22470NS 21600NS 50300NS 72200NS

i32的行为再次类似,但开始在较大的矩阵下看到更大的收益。实际上,在100x100时,i32矩阵大约等于平坦基质。并不令人惊讶,但是如果您要处理大整数矩阵,这可能是您的最佳选择。

结论

对于简单的单线读JavaScript,我们已经观察到了一些内容(在Deno/V8 @ 2023-03-31中):

  • 循环的性能大多要比.map更好,但只有很大的值,并且只有嵌套数组(我尝试了一个平坦的数组,而且还不足以复制数据)。
  • 定制的展开功能在非常小的4x4或更小的尺寸上很好地工作,但不会击败一个简单的循环,并且非常非常快速地掉落。
  • 减少结构有很大的不同。
  • 预分配阵列会使巨大不同,如果可以的话,请始终执行此操作。
  • 打字阵列没有任何速度优势(但是我们可能会更少的转换开销和节省空间)。

我们有更多的方法可以处理矩阵,我想看看wasm和webgpu的样子,而高空头顶,但由于并行性而导致的实际计算速度可能会增加。网络工人也是如此。另外,不同的操作可能会差异很大。矩阵乘法对左手和右手结构的使用方式不同,可能需要一些不同的策略。但是我认为最大的外卖:

对广义元素矩阵OP的最佳选择是在正常JS数组上的单个平坦环,因为它快速缩放

数据

名称 最低 max avg p75 p99 p995
添加1x1(func) 63ns 180ns 70ns 74NS 113ns 124ns
添加1x1(loop) 28ns 210NS 46ns 47ns 142ns 168ns
添加1x1(loop prealloc) 13ns 137ns 18NS 20NS 56ns 73ns
添加1x1(展开) 7ns 34NS 8NS 8NS 19NS 20NS
添加1x1(展开的动态) 7ns 40ns 8NS 7ns 19NS 20NS
添加1x1(flat) 9NS 53ns 10ns 10ns 24ns 29ns
添加1x1(平面Col Major) 9NS 41NS 10ns 9NS 21ns 22ns
添加1x1(平面简单) 7ns 46ns 8NS 8NS 18NS 20NS
添加1x1(平面展开) 6ns 53ns 7ns 7ns 19NS 22ns
添加1x1(f64) 330ns 1600NS 400NS 397NS 663ns 1600NS
添加1x1(f32) 324ns 554NS 380NS 391NS 506ns 554NS
添加1x1(i32) 321NS 1015ns 390NS 398NS 704NS 1015ns
添加2x2(func) 144ns 208NS 152ns 158ns 184ns 196NS
添加2x2(loop) 55ns 163ns 71NS 76ns 125ns 143ns
添加2x2(loop prealloc) 25ns 65ns 28ns 27ns 45ns 53ns
添加2x2(展开) 11ns 46ns 13ns 12ns 26ns 29ns
添加2x2(展开的动态) 11ns 39ns 12ns 12ns 27ns 29ns
添加2x2(flat) 14NS 49ns 15ns 15ns 29ns 30ns
添加2x2(平面Col Major) 14NS 41NS 15ns 14NS 29ns 32ns
添加2x2(平面简单) 9NS 54NS 10ns 10ns 23ns 26ns
添加2x2(平面外) 7ns 62ns 8NS 8NS 21ns 23ns
添加2x2(f64) 329NS 598NS 393NS 409NS 493NS 598NS
添加2x2(f32) 324ns 594NS 391NS 408NS 520NS 594NS
添加2x2(i32) 324ns 570NS 390NS 403ns 501NS 570NS
添加4x4(func) 312NS 373NS 329NS 335NS 370NS 373NS
添加4x4(loop) 122ns 227NS 143ns 151ns 195ns 225ns
添加4x4(loop prealloc) 61NS 152ns 73ns 78ns 124ns 129ns
添加4x4(展开) 36ns 159ns 59ns 72NS 124ns 130ns
添加4x4(展开的动态) 36ns 236ns 67ns 84NS 156ns 181ns
添加4x4(flat) 32ns 107ns 40ns 46ns 86ns 94NS
添加4x4(平面Col Major) 32ns 79ns 37ns 37ns 61NS 67ns
添加4x4(平面简单) 18NS 77ns 24ns 28ns 51NS 56ns
添加4x4(平面展开) 24ns 136ns 37ns 41NS 84NS 93NS
添加4x4(f64) 393NS 1786ns 490NS 503NS 662NS 1786ns
添加4x4(f32) 396ns 658NS 463ns 489ns 569ns 658NS
添加4x4(i32) 372NS 530ns 426ns 443ns 488ns 530ns
添加8x8(func) 694ns 930ns 724NS 731NS 930ns 930ns
添加8x8(loop) 360NS 807NS 411NS 422NS 744NS 807NS
添加8x8(loop prealloc) 203ns 444ns 228NS 232ns 348NS 434ns
添加8x8(展开) 92NS 243ns 130ns 142ns 235ns 242ns
添加8x8(展开的动态) 89ns 262NS 113ns 119ns 186ns 209ns
添加8x8(flat) 97ns 167ns 110NS 113ns 143ns 157ns
添加8x8(平面Col Major) 101NS 156ns 114ns 116ns 147ns 153ns
添加8x8(平面简单) 55ns 159ns 73ns 78ns 125ns 136ns
添加8x8(平面展开) 61NS 185ns 81NS 86ns 131ns 144ns
添加8x8(f64) 490NS 778ns 621NS 664ns 778ns 778ns
添加8x8(f32) 508NS 822NS 620NS 673NS 822NS 822NS
添加8x8(i32) 455ns 621NS 539ns 575NS 616ns 621NS
添加16x16(func) 1798ns 1942ns 1836ns 1843ns 1942ns 1942ns
添加16x16(loop) 1179ns 1246ns 1208NS 1217ns 1246ns 1246ns
添加16x16(loop prealloc) 710ns 942NS 762NS 768ns 942NS 942NS
添加16x16(展开) 500NS 672800NS 734ns 600NS 3400NS 10500NS
添加16x16(展开的动态) 500NS 2052000NS 799ns 600NS 6400NS 10600NS
添加16x16(flat) 400NS 548NS 436ns 447ns 517NS 548NS
添加16x16(平面Col Major) 423ns 532NS 453ns 465ns 513ns 532NS
添加16x16(平面简单) 276ns 405NS 315ns 335NS 393NS 405NS
添加16x16(平面展开) 300NS 564700NS 508NS 400NS 1000NS 6100NS
添加16x16(f64) 1024ns 5425NS 1311ns 1334ns 5425NS 5425NS
添加16x16(f32) 1148NS 1784ns 1345ns 1422ns 1784ns 1784ns
添加16x16(i32) 784ns 1202NS 913NS 966ns 1202NS 1202NS
添加32x32(func) 5274NS 6599ns 5495NS 5605NS 6599ns 6599ns
添加32x32(loop) 5031NS 5216NS 5090NS 5105NS 5216NS 5216NS
添加32x32(循环prealloc) 2648NS 2769ns 2700NS 2716ns 2769ns 2769ns
添加32x32(展开) 73800NS 562500NS 83976ns 85200NS 136400NS 160600NS
添加32x32(展开的动态) 73000ns 908200NS 90772NS 90900NS 137900NS 162600NS
添加32x32(flat) 1985ns 2900NS 2222NS 2276ns 2900NS 2900NS
添加32x32(平面Col Major) 2047ns 3228NS 2199ns 2258NS 3228NS 3228NS
添加32x32(平面简单) 1387ns 1682ns 1490NS 1547ns 1682ns 1682ns
添加32x32(平面展开) 63600NS 826700NS 74574NS 75200NS 133000ns 162600NS
添加32x32(f64) 3346ns 4707NS 3772NS 4115NS 4707NS 4707NS
添加32x32(F32) 3258NS 3840NS 3344ns 3337NS 3840NS 3840NS
添加32x32(i32) 2111ns 2704NS 2182NS 2182NS 2704NS 2704NS
添加64x64(func) 13000ns 2331200NS 17451NS 16300NS 41900NS 60700ns
添加64x64(loop) 14300ns 362400NS 20651NS 19200ns 52900NS 110500NS
添加64x64(循环prealloc) 9500NS 372100NS 10926ns 10100NS 25000NS 35800NS
添加64x64(展开) 328700NS 737300NS 350104NS 343900NS 574500NS 587000NS
添加64x64(展开的动态) 327600NS 698800NS 349201NS 345400NS 573900NS 592400NS
添加64x64(flat) 8512NS 10514NS 8775NS 8715NS 10514NS 10514NS
添加64x64(平面Col Major) 7500NS 413800NS 10417NS 10200NS 26200NS 37000NS
添加64x64(平面简单) 6381NS 7219NS 6602NS 6675NS 7219NS 7219NS
添加64x64(平面展开) 263500NS 788800NS 286503NS 280600NS 502900NS 528900NS
添加64x64(f64) 8000NS 2309700NS 14203NS 12700NS 35300NS 44800NS
添加64x64(F32) 10500NS 1101800NS 18473ns 21600NS 66500NS 101200NS
添加64x64(i32) 8742NS 9569ns 9138NS 9305NS 9569ns 9569ns
添加100x100(func) 30800NS 512800NS 40269ns 38200NS 105700NS 218300NS
添加100x100(循环) 38200NS 425400NS 54401NS 54100NS 227700NS 256300NS
添加100x100(循环prealloc) 24500NS 515800NS 28392NS 26300NS 62100NS 204400NS
添加100x100(展开) 829600NS 1250900NS 876580NS 873700NS 1143900NS 1157500NS
添加100x100(展开的动态) 816900NS 1416300NS 891844NS 894500NS 1227700NS 1288200NS
添加100x100(flat) 15500NS 701100NS 23261NS 21800NS 54200NS 194800ns
添加100x100(平面Col Major) 19800ns 575300NS 25090NS 23500NS 63000NS 198500NS
添加100x100(平面简单) 9000NS 598000NS 17166ns 15700NS 49400NS 178400NS
添加100x100(平面展开) 706400NS 1760300NS 764369ns 758900NS 1102800NS 1118900NS
添加100x100(F64) 23200NS 3328400NS 35026NS 33300NS 82400NS 231000NS
添加100x100(F32) 25800NS 1797500NS 37062NS 35800NS 99800NS 245400NS
添加100x100(i32) 12600NS 2578300NS 22470NS 21600NS 50300NS 72200NS

Image description

代码

https://github.com/ndesmic/fast-mat