有什么办法使敲除速度更快,或者数据结合根本存在缺陷?

发布于 2025-02-13 02:00:12 字数 10661 浏览 0 评论 0 原文

最近,我将项目作为一项技术,可以在接下来的十年左右的时间里完全拥有并运行。我讨厌框架和库,所以当我采用某些东西时,这是因为我发现它做得好,并且可以导航源以进行自己的修复和增强功能。否则我更喜欢自己做。淘汰赛取得了成功。

我喜欢推动极限,看看事情如何扩展和喜欢电子表格。因此,我认为电子表格应该是推动数据绑定的Gizmo限制的好方法。了解它的容易以及它如何使用较大的电子表格缩放。

容易吗?我认为这还不够容易。每个电子表格单元格应该是可观察的,一个可观察的可观察的,其中更改公式意味着更新读取功能。写功能在那里了解字符串文字值,数字和公式(以“ =”开头),然后更新公式函数。

使用敲除计算的可观察到的东西,您有一些需要其他东西来存储其值(如果有)。然后,这也倾向于也可以观察到,并且添加了每个其他可观察到的平方。依赖链更糟。

我已经构建了这个示例,从绝对最小的复杂性到完全需要的示例。

  • 当单击一个单元格时,您
  • 在离开编辑输入框时编辑公式时,我们回到值模式
  • 功能)的结果。
  • 值是功能(即使是恒定数字和字符串最终 引用变成了范围(...)函数的调用,
  • 其中范围flatmap's flatmap'到其值的数组。

我的实施是我能想到的最好的。这里还有另一个淘汰赛电子表格问题,但是问这并不是很严重的人,而发送的例子也没有调用。我有另一个示例以“干净”的方法为例,因为您应该“使用ko,其中小区是一个对象,有几件事可以观察到,但在压力测试中它却非常慢,所以我在这里坚持下去。

下面有一个压力测试,我过去可以做到最好。

我将解释我的代码。首先,html非常简单(顺便说一句,我在注释中有文本,因此您可以选择整个内容并复制和粘贴它,并且应该使用解释性文本,然后在评论中。

<html>
  <head>
    <title>Spreadsheet Example</title>
    <script src="knockout-3.5.0.debug.js" type="text/javascript"></script>
    <style type="text/css">
      table { border-collapse:collapse; } 
      th { border:1pt solid grey; } 
      td { width:10em; height:2ex; border:1pt solid grey; }
      input { width:100%, height:100%; }
    </style>
  </head>
  <body>
    <table>
      <thead>
        <tr>
          <th scope="col" data-bind="click: function() { filltest(); }">#</th>
          <!-- ko foreach: { data: sheet[0], as: 'col' } -->
          <th scope="col" data-bind="text: colalpha($index())"></th>
          <!-- /ko -->
        </tr>
      </thead>
      <tbody data-bind="foreach: { data: sheet, as: 'row' }"> 
        <tr>
          <th scope="row" data-bind="text: $index()+1"></th>
          <!-- ko foreach: row -->
          <td data-bind="click: function() {
                              $rawData._kos_editing(true); 
                              $rawData._state.isDirty = true; 
                              return true; }">
            <!-- ko if: (_ => { if(!$rawData._kos_editing)
                                    $rawData._kos_editing = ko.observable(false);
                                return $rawData._kos_editing })() -->
            <input data-bind="value: $rawData._kos_formula, 
                              event: { blur: function(obj, evn) {
                                          $rawData._kos_editing(false);
                                          $rawData(evn.target.value); } }"/>
            <!-- /ko -->
            <!-- ko if: !$rawData._kos_editing() -->
            <div data-bind="text: $data, style: $rawData.style"></div>
            <!-- /ko -->
          </td>
          <!-- /ko -->
        </tr>
      </tbody>
    </table>
    <script type="text/javascript"><!-- /*

) HTML,我认为这很明显,生成了该表。现在出现了JavaScript: */

const cols = 20;
const rows = 20;
const fakeDependency = ko.observable(42); /* XXX: to protect us from being disposed

这种伪造的依赖性是如此令人尴尬,但是如果没有IT敲除,就可以立即“处置”计算出的可观察到的,因为当还没有公式时,它没有依赖性。

现在,我们创建二维数组,并立即用ko.com填充它: */

const sheet = [...Array(rows)]
sheet.forEach((_,rownum,sheet) => { 
    const row = sheet[rownum] = [...Array(cols)];
    row.forEach((_,colnum) => {
        const cell = row[colnum] = ko.computed({ /*

现在每个单元格是可观察的一个主要可写入的。首先读取功能。我们必须存储计算的值,因此我添加了一个_KOS_VALUE属性( kos 是我使用的所有属性,我将其用于可观察到的所有属性)。我没有从价值登录器功能内观察到的可观察到的操作,这是非常尴尬的,如果我不想让事情变得复杂,我就没有位置存储该值的位置。 */

            read:  function() {
                fakeDependency(); // XXX: if we don't ask for anything, we'll get disposed
                const cell = row[colnum];
                if(!cell)
                    return "";
                const value = cell._kos_value;
                if(false && cell._kos_editing())
                    return cell._kos_formula;
                else if(typeof value == 'function')
                    return value();
                else
                    return value;
            }, /*

您在上面看到的是,有编辑模式(_KOS_editing)本身是可以观察到的,因为它用于在值和公式之间切换。然后,编辑模式中的值函数返回公式,在非编辑模式下,返回值。

这有点尴尬,因为读取功能所谓的远远超出您的预期。因此,有几件事可以强制对读取功能进行评估,有些是迫使_state.isdirty和一些强迫NotifySubScriber的人。

现在写功能。这知道它只会在编辑模式下被调用,然后获得公式。 */

            write: function(value) {
                const formula = value;
                const number = 1*value;
                      const cell = row[colnum];
                if(formula == "")                   
                    cell._kos_value = _ => 0;
                else if(!Number.isNaN(number))
                    cell._kos_value = _ => number;
                else if(formula.charAt(0) == '=')
                    cell._kos_value = new Function([], `    return ${formula.substring(1).replaceAll(/\b([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?\b/g, "range('$1',$2,'$3',$4)")};`);
                else 
                    cell._kos_value = _ => formula;

                cell._kos_formula = formula;
                cell._kos_editing(false);
                cell._state.isDirty = true;
                cell.notifySubscribers();
            }
        }); /*

在那里的正则是替换单元格参考A1,B3:E8,AA20,AB21等。就是这样。

现在有一件小事要对数字进行直接对准,并左对准文本: */

        cell.style = ko.computed({
            read: function() {
                const value = cell();
                if(typeof value == 'number')
                    return { 'text-align': 'right' };
                else
                    return { 'text-align': 'left' };
            }
        });
    })
}); /*

就是这样。现在,该表是用所有可观察到的。其余的是辅助功能。首先,所有单元引用的函数(上面的正则呼叫都会呼叫)。它返回单个值(如果单元单元格)或一个平面值,则如果范围 */

function range(colalpha, rownum, endcolalpha, endrownum) { 
    const [startRownum, startColnum] = rowcol(colalpha, rownum);
    const [endRownum, endColnum]     = endcolalpha ? rowcol(endcolalpha, endrownum) : [undefined, undefined];;

    if(typeof endRownum == 'undefined' || (endRownum == startRownum && endColnum == startColnum) )
        return sheet[startRownum][startColnum]();
    else 
        return sheet.slice(startRownum, endRownum + 1)
                    .flatMap(row => row.slice(startColnum, endColnum + 1)
                                       .map(cell => cell()));
} /*

具有将A..z,aa,ab,...字母编号转换为列号和行号的函数从1个基于1的零数组索引移动: */

function rowcol(colalpha, rownum) {
    const colnum = [...colalpha].map(chr => chr.charCodeAt(0) - 64).reduce((value, digit) => (value*26)+digit, 0);
    return [rownum - 1, colnum - 1 ];
} /*

现在,将列号转换为这些字母编号的函数实际上主要用于下面的应力测试。 */

function colalpha(colnum) { // map 0 .. 26, 27, 28 to A ... Z, AA, AB ... 
    let result = "";
    colnum++;
    do {
        colnum--;
        result = String.fromCharCode(65 + colnum % 26) + result;
        colnum = Math.floor(colnum / 26);
    } while(colnum > 0); // NOTE: only second time in my life that a repeat-until construct was (almost) helpful
    return result;
} /*

最后,作为概念证明,范围函数的示例,具有 = a1:c5.sum()的公式总结了整个块: */

Array.prototype.sum = function() { return this.reduce((sum,x) => sum + x, 0); }

ko.options.deferUpdates = true;
ko.applyBindings(sheet); /*

现在这是结束实际表。现在进行了压力测试,因为我没有选择,复制,粘贴和粘贴的配方。但是我可以快速通过大量依赖项的压力测试来填补工作表,我们会逐步这样做以查看更新。

该测试是针对每个单元格的总和,直接向其左侧的单元格上方。这会对依赖性产生重度压力测试。 */

function filltest () {
    sheet[0][0]("0");
    setTimeout(test_setup_cols, 100);
}
function test_setup_cols() {
    sheet[0].slice(1).forEach((cell,i) => cell(`= ${colalpha(i)}1+1`));
    setTimeout(test_setup_rows, 100);
}
function test_setup_rows() {
    sheet.slice(1).forEach((row,i) => row[0](`= A${1+i}+1`));
    setTimeout(test_fill, 100);
}
function test_fill (i = 1) {
    let rownum = i;
    let colnum = i;
    for(colnum = 1; colnum <= i; colnum++)
        sheet[rownum][colnum](`= ${colalpha(colnum-1)}${rownum+1} + ${colalpha(colnum)}${rownum}`); 
    colnum = i;
    for(rownum = 1; rownum <= i; rownum++)
        sheet[rownum][colnum](`= ${colalpha(colnum-1)}${rownum+1} + ${colalpha(colnum)}${rownum}`);   
    if(++i < cols && i < rows)
        setTimeout(test_fill.bind(null,i), 100);
} /*

这是结局: */

    --></script>
  </body>
</html>

我已经检查了有关Google表的表现,并且它的手势下降了。测试公式和评估的填充实际上并不那么糟糕(我挑战其他任何人以更快的速度)。但是,当我用大量依赖项更改细胞(最坏的是A1)时,它会失速并失败。

所以现在我对淘汰赛有了第二个想法。可以救出吗? Google表如何如此之快?这也是JavaScript。它必须进行相同的依赖性跟踪工作。而且要快得多!

我的标题问题的最后一部分很严重。我想看看可能像淘汰赛一样,关于数据绑定有根本缺陷。

upate:我想我知道一些慢病的原因。我的直觉是,依赖性跟踪实现了整个及物闭合,这将导致相同的单元格被通知大量次数。让我们对此进行测试(并以JavaScript作为“查询语言”播放)。

首先,让我们命名所有单元格:

sheet.forEach((row,rownum) => row.forEach((cell,colnum) => cell._kos_name = colalpha(colnum)+(rownum+1)))

现在让我们列出所有依赖项。

> sheet[1][1]._subscriptions.change
< (4) [ko.subscription, ko.subscription, ko.subscription, ko.subscription]

这表明我的直觉是错误的。每个单元似乎只有4个依赖项。而且我真的不能说什么。至少我怀疑这些订阅中的依赖性的及传递封闭是不正确的。

但是分析我确定最多的时间是在这里花费的:

evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
    window.cccnt = (window.cccnt||0)+1;

所以我添加了此计数器CCCNT,并且在使用公式填充公式后可以在20 x 20的表中,转到J10并将“ + 1”添加到那里的公式,这会更改结果值(“ * 1”没有)。

现在是计数。

  1. 首次初始化:
  2. 填充配方后的5,453:10,387-5,453 = 4,934 = 4,934差异
  3. 时,在J10:1,422,465-10,387 = 1,412,078中更新公式时,

仅14000万次评估400次。这绝对是不对的。当我将类似的计数器放入我的写入和阅读功能中时,我在整个测试后得到420写字,这很好,但是在J10中的公式更新之前,我已经阅读了运行837,更新后我有706,268。

因此,这显然是错误的,并且以某种方式优化。由于我们有依赖项后,我们应该运行更新,以一种方式将它们级联下来J10更新,因此2个依赖性单元格是K10和J11,因此它们更新。然后是他们的依赖者,这意味着只有100个单元格应更新。但是它以某种方式在这里运行。

我不确定如何开始攻击这个问题,但是我有一些乐观的态度。它实际上是某种错误。

另请参见有关敲除github的进一步测试和讨论: https://github.com/githockou com.com/knockout/knockout/knockout/knockout/knockout/knockout/knockout/问题/2596 特别是,我发现了客观证明>客观证明> - 替换我的台式测试,该测试将在O(n^2)中执行,其中淘汰赛将运行o(e^n),即使在桌子小至12 x 12中,我的代码也会通过我的代码提高了数千倍的速度。

”输入图像描述在这里”

I have recently adopted knockout for my projects as a technology to fully own and run for the next decade or so. I hate frameworks and libraries, so when I adopt something it is because I found that it is well done and that I can navigate the source to make my own fixes and enhancements. Otherwise I prefer doing it myself. Knockout made the mark.

I like to push the limits and see how things scale and I love spreadsheets. So I thought a spreadsheet should be a nice way to push the limit on a data binding gizmo. To see how easy it is, and how it scales with larger spreadsheets.

Is it easy? I thought it was not easy enough. Each spreadsheet cell should be an observable, a computed observable, where changing the formula means updating the read function. The write function is there to understand string literal values, numbers and formulas (start with "=") and then update the formula function.

With knockout computed observables you have something that needs something else to store its value if any. And then that tends to want to be observable too, and every additional observable is added squared. And worse are dependency chains.

I have built this example up from the absolute minimum complexity to have just exactly what is needed.

  • when clicking into a cell you edit the formula
  • when leaving the edit input box, we go back to value mode
  • value is the result of a function (even constant numbers and strings end up functions)
  • formulas are essentially javascript expressions with the usual cell and range references turned into calls to a range(...) function
  • where ranges are flatMap'ped to an array of their values.

My implementation is the best I could come up with. There is another knockout spreadsheet question on here, but the person who asked this wasn't really serious and the examples sent around were not tuned. I had another example following the more "clean" approach, as you "should" use ko, where the Cell is an object, with several things observable, but it is horribly slow in the stress test, so I stick to this here.

There is a stress test below which I used to get to the best I could do.

I will explain my code. First the HTML, is pretty simple (BTW, I have the text in comments, so you can just select the whole thing and copy and paste it and it should work, with the explanatory text then in comments.)

<html>
  <head>
    <title>Spreadsheet Example</title>
    <script src="knockout-3.5.0.debug.js" type="text/javascript"></script>
    <style type="text/css">
      table { border-collapse:collapse; } 
      th { border:1pt solid grey; } 
      td { width:10em; height:2ex; border:1pt solid grey; }
      input { width:100%, height:100%; }
    </style>
  </head>
  <body>
    <table>
      <thead>
        <tr>
          <th scope="col" data-bind="click: function() { filltest(); }">#</th>
          <!-- ko foreach: { data: sheet[0], as: 'col' } -->
          <th scope="col" data-bind="text: colalpha($index())"></th>
          <!-- /ko -->
        </tr>
      </thead>
      <tbody data-bind="foreach: { data: sheet, as: 'row' }"> 
        <tr>
          <th scope="row" data-bind="text: $index()+1"></th>
          <!-- ko foreach: row -->
          <td data-bind="click: function() {
                              $rawData._kos_editing(true); 
                              $rawData._state.isDirty = true; 
                              return true; }">
            <!-- ko if: (_ => { if(!$rawData._kos_editing)
                                    $rawData._kos_editing = ko.observable(false);
                                return $rawData._kos_editing })() -->
            <input data-bind="value: $rawData._kos_formula, 
                              event: { blur: function(obj, evn) {
                                          $rawData._kos_editing(false);
                                          $rawData(evn.target.value); } }"/>
            <!-- /ko -->
            <!-- ko if: !$rawData._kos_editing() -->
            <div data-bind="text: $data, style: $rawData.style"></div>
            <!-- /ko -->
          </td>
          <!-- /ko -->
        </tr>
      </tbody>
    </table>
    <script type="text/javascript"><!-- /*

now that's the end of the HTML, and I think it's pretty obvious, generating that table. Now comes the javascript: */

const cols = 20;
const rows = 20;
const fakeDependency = ko.observable(42); /* XXX: to protect us from being disposed

this fakeDependency is so embarrassing, but without it knockout would just immediately "dispose" the computed observable because when there is no formula yet, it has no dependencies.

Now we create the 2-dimensional array and immediately fill it with ko.computed observables: */

const sheet = [...Array(rows)]
sheet.forEach((_,rownum,sheet) => { 
    const row = sheet[rownum] = [...Array(cols)];
    row.forEach((_,colnum) => {
        const cell = row[colnum] = ko.computed({ /*

Now each cell is one main writable computed observable. First the read function. We have to store the value of the computed, so I add a _kos_value property (kos is the knockout spreadsheet prefix I use for all my properties I stick onto the observable). It's very awkward that I do not have a handle on the observable from within the value accessor functions and I have no place to store the value if I don't want to make things complicated. */

            read:  function() {
                fakeDependency(); // XXX: if we don't ask for anything, we'll get disposed
                const cell = row[colnum];
                if(!cell)
                    return "";
                const value = cell._kos_value;
                if(false && cell._kos_editing())
                    return cell._kos_formula;
                else if(typeof value == 'function')
                    return value();
                else
                    return value;
            }, /*

you see above that there is the edit mode (_kos_editing) that itself is an observable because it is used to switch between the value and the formula . Then the value function in edit mode returns the formula, and in non-edit mode returns the value.

This is somewhat awkward because the read function is called much less than you'd expect. So there are a few things to force evaluation of the read function, some forcing _state.isDirty and some forcing notifySubscribers.

Now the write function. This knows that it's going to be called only in edit mode, and then gets the formula. */

            write: function(value) {
                const formula = value;
                const number = 1*value;
                      const cell = row[colnum];
                if(formula == "")                   
                    cell._kos_value = _ => 0;
                else if(!Number.isNaN(number))
                    cell._kos_value = _ => number;
                else if(formula.charAt(0) == '=')
                    cell._kos_value = new Function([], `    return ${formula.substring(1).replaceAll(/\b([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?\b/g, "range('$1',$2,'$3',$4)")};`);
                else 
                    cell._kos_value = _ => formula;

                cell._kos_formula = formula;
                cell._kos_editing(false);
                cell._state.isDirty = true;
                cell.notifySubscribers();
            }
        }); /*

The regex up there was to replace cell references A1, B3:E8, AA20, AB21, etc. That's it.

Now comes a little thing to do right-aligned for numbers and left-aligned for text: */

        cell.style = ko.computed({
            read: function() {
                const value = cell();
                if(typeof value == 'number')
                    return { 'text-align': 'right' };
                else
                    return { 'text-align': 'left' };
            }
        });
    })
}); /*

And that's it. Now the table is created with all the observables. The rest is helper functions. First the function that is called for all cell references (the regex above makes those calls). It returns a single value (if a single cell) or a flat array of values if a range */

function range(colalpha, rownum, endcolalpha, endrownum) { 
    const [startRownum, startColnum] = rowcol(colalpha, rownum);
    const [endRownum, endColnum]     = endcolalpha ? rowcol(endcolalpha, endrownum) : [undefined, undefined];;

    if(typeof endRownum == 'undefined' || (endRownum == startRownum && endColnum == startColnum) )
        return sheet[startRownum][startColnum]();
    else 
        return sheet.slice(startRownum, endRownum + 1)
                    .flatMap(row => row.slice(startColnum, endColnum + 1)
                                       .map(cell => cell()));
} /*

with a function that converts the A..Z, AA, AB, ... letter numbers to column numbers and the row numbers also moved from 1-based to zero-based array indexes: */

function rowcol(colalpha, rownum) {
    const colnum = [...colalpha].map(chr => chr.charCodeAt(0) - 64).reduce((value, digit) => (value*26)+digit, 0);
    return [rownum - 1, colnum - 1 ];
} /*

now the function that turns column number into these letter numbers, used really mostly for the stress test below. */

function colalpha(colnum) { // map 0 .. 26, 27, 28 to A ... Z, AA, AB ... 
    let result = "";
    colnum++;
    do {
        colnum--;
        result = String.fromCharCode(65 + colnum % 26) + result;
        colnum = Math.floor(colnum / 26);
    } while(colnum > 0); // NOTE: only second time in my life that a repeat-until construct was (almost) helpful
    return result;
} /*

Finally, just as a proof of concept, an example for a range function, with a formula like =A1:C5.sum() sums up an entire block: */

Array.prototype.sum = function() { return this.reduce((sum,x) => sum + x, 0); }

ko.options.deferUpdates = true;
ko.applyBindings(sheet); /*

Now this is the end of the actual sheet. Now comes the stress test, since I don't have select, copy, paste, and paste-down with formulas that get adjusted. But I can quickly fill the sheet with a stress test of lots of dependencies, and we do it progressively to see the updates.

The test is for each cell being the sum of the cell directly above it with the cell directly left to it. This creates a heavy stress test of dependencies. */

function filltest () {
    sheet[0][0]("0");
    setTimeout(test_setup_cols, 100);
}
function test_setup_cols() {
    sheet[0].slice(1).forEach((cell,i) => cell(`= ${colalpha(i)}1+1`));
    setTimeout(test_setup_rows, 100);
}
function test_setup_rows() {
    sheet.slice(1).forEach((row,i) => row[0](`= A${1+i}+1`));
    setTimeout(test_fill, 100);
}
function test_fill (i = 1) {
    let rownum = i;
    let colnum = i;
    for(colnum = 1; colnum <= i; colnum++)
        sheet[rownum][colnum](`= ${colalpha(colnum-1)}${rownum+1} + ${colalpha(colnum)}${rownum}`); 
    colnum = i;
    for(rownum = 1; rownum <= i; rownum++)
        sheet[rownum][colnum](`= ${colalpha(colnum-1)}${rownum+1} + ${colalpha(colnum)}${rownum}`);   
    if(++i < cols && i < rows)
        setTimeout(test_fill.bind(null,i), 100);
} /*

and here is the end: */

    --></script>
  </body>
</html>

I have checked the performance of this against Google sheets, and it's hand down losing. The filling of the test formula and the evaluation is actually not even that bad (and I challenge anyone else to make something faster). But when I change cells with lots of dependencies -- the worst being A1 -- it stalls and fails.

So now I am having second thoughts about knockout. Can this be rescued? How can google sheets be so fast? It's also javascript. It has to do the same dependency tracking work. And it's so much faster!

My last part of the header question is serious. I want to see if perhaps there is something fundamentally flawed about data binding as knockout does it.

UPATE: I think I know some of the reason for the slowness. My intuition is that the dependency tracking materialized the entire transitive closure, which will cause the same cell to be notified of change a huge number of times. Let's test this (and play around with javascript as a "query language").

First, let's name all cells:

sheet.forEach((row,rownum) => row.forEach((cell,colnum) => cell._kos_name = colalpha(colnum)+(rownum+1)))

Now let's make a list of all dependencies.

> sheet[1][1]._subscriptions.change
< (4) [ko.subscription, ko.subscription, ko.subscription, ko.subscription]

this shows that my intuition is wrong. Each cell appears to have only 4 dependencies. And I can't really tell to what. At least it is not true what I suspected that the transitive closure of the dependencies is reified in these subscriptions.

But profiling I determined that the most time is spent here:

evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
    window.cccnt = (window.cccnt||0)+1;

so I added this counter cccnt, and I could in a 20 x 20 table after filling it with the formula, go to J10 and add " + 1" to the formula there, something that changes the result value ( " * 1" does not).

Now here is the counts.

  1. on first initialization: 5,453
  2. after filling with the formulas: 10,387 - 5,453 = 4,934 difference
  3. when updating the formula in J10: 1,422,465 - 10,387 = 1,412,078

That's 1.4 million times evaluating for just 400 formulas. This is definitely not right. When I put similar counters into my write and read functions, I get 420 writes after the whole test, which is good, but before the formula update in J10 I have read run 837 and after the update I have 706,268.

So this is clearly wrong and optimizable somehow. Since we have the dependencies once we should run the updates cascading them down one way J10 updates, the 2 immediately dependent cells are K10 and J11, so they update. Then their dependents, and so on which means only 100 cells should update. But somehow it runs amok here.

I am not sure how to start attacking this problem, but I have some optimism that it could be done. That it is actually a bug of some sort.

See also further tests and discussions on knockout github: https://github.com/knockout/knockout/issues/2596, in particular, I have found objective proof that the knockout dependency tracking is horribly wrong and I could build a drop-in replacement for my bench test that would perform in O(n^2) where knockout's would run in O(e^n), leading to thousands-fold speed improvements with my code even in tables as small as 12 x 12.

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

江湖正好 2025-02-20 02:00:13

在谈论性能时,鉴于我的经验,我必须说淘汰赛有一些利弊。
如果没有初始学习曲线,并且需要从不同的范式中适应,则淘汰赛是无处不在的,从反应性能方面可以实现的目标。

这就是贝格说的,淘汰赛在开发方面的效率要高得多,如果使用得当,您几乎可以管理所有用例(减去大量实时更新,而不是推送通知,例如具有成千上万的更新,即社交环境)。

我个人能够在单个主/详细的组件列表中呈现超过2.500个组件节点的重复列表,渲染时间约为7秒(我知道) (按钮,HTML编辑器,标志,多滴定选择等)以及相对论2014年的基本塔楼(CPU Wise等),

将介入所有详细信息的所有详细信息性能提示归结为一般最佳实践,例如:

  • 不要使用评论结合指令。对于KO来说,它们的处理要慢4倍,如果您正确地构造DOM
  • 不会对DOM结构进行太深的结构,请尽可能简单,
  • 不要使用可观察到的“用于不变的事物”,例如在不可用的上下文中的对象描述从用户角度来看,改用普通的JS属性。
  • 处理多个实例时,请使用JS原型设计,因为这为MEM和查找时间节省了很多。
  • 不要将绑定在额外的范围(即单击:function(){...})中,而是正确地构造了您的VM,以使其始终归结为函数参考(单击:$ data.somefunction)
  • 始终使用延期更新和适当的节流以管理模型的更新速率,该模型将导致链的重新调查。
  • 使用事件冒泡。与其在每个元素上附加一个单击处理程序,不如将一个处理程序附加到父元素元素上,并从一个页面上的单击处理程序中,每个“域组件”从一个页面上的单击处理程序来回调。
  • 处理数组时,请不要“更新foreach中的每个实例”,而是切成整个数组,并立即使用新数组更新,并以不可变的方式投影。这将对总更改进行一次更新任何阵列结合组件的更新,每一个更新的项目一次启动一次。 /2012/04/knockoutjs-performance-gotcha.html“ rel =“ nofollow noreferrer”>均

给出了所有上面的淘汰赛没有使用Shadow-dom,看来社区将进行所需的更新,因此它永远无法与React这样的绩效进行比较。对于您的特定上下文,由于我从未制作过这样的应用程序,所以我不能说您是否可以达到所需的性能,并且在何种规模上会进一步打破。
从长远来看,如果您重视规模和性能,则React应该是您下一步的前进。

如果您重视已知的声明性syntax(Angular,vue,淘汰等)以及您的发展速度,那么您应该能够创建一个像kockout相对较快的应用程序,至少是为了证明概念的证明, 。从我的角度来看,将其缩放到数千个电子表格行,可共享的用户等。

When talking about performance, given my experience, i would have to argue that knockout has some pros and cons.
Without initial learning curve and the need to adapt from a different paradigm, knockout is no-where near what you can achieve with react performance-wise.

That beign said, knockout is much much more efficient development-wise and if used properly, you can manage almost all use-cases ( minus huge real-time updates over push notifications for example with thousands of updates i.e social context ).

I have personally been able to render a recurring list of more than 2.500 component nodes, in a single master/detail component list, with a rendering time of around 7 seconds ( huge i know ) but with every element having a load of components onto it ( buttons, html editors, flags, multi-dropdown selects etc binded onto it ) and with the relativism basic tower-pc of 2014 ( cpu wise etc )

Going into all details for performance tips comes down to general best practices, for your example:

  • Dont use comment-binding directives. They are 4x slower for ko to process than if you would structure your dom properly
  • Dont go too deep with dom structure, be as simple as possible
  • Dont use observables "for things that dont change" for example an objects description in a non-editable context from user perspective, use plain js properties instead.
  • Use js prototyping when dealing with multiple instances, as this saves a lot for mem and lookup times.
  • Dont wrap bindings in extra scopes ( i.e click: function() { ... } ), instead structure your vm properly so that it always comes down to a function reference (click: $data.someFunction )
  • Always used deferred updates and appropriate throttling to manage the update-firing rate of the model that will cause chain re-evals of computed.
  • Use event bubbling. Instead of attaching a click handler on each element, attach one handler to the parent dom element and infer the clicked event and callback from one page-wise click handler per "domain component".
  • When handling arrays, dont "update each instance in a foreach", rather slice the whole array and update it with a new array at once, projected with the previous array in an immutable way. This will fire an update of any array-binding component once for the total changes, instad of once for each updated item, along with any subscriptions.A nice article on this is this one

Given all the above knockout does not utilize shadow-dom, nor does it look like the community will be making the required updates, so it could never compare performance-wise to the likes of react lets say. For your specific context and because i have never made such an app, i can not say if you could achieve the required performance and at which scale it would further break.
If you value scale and performance in the long run, react should be your next step forward.

If you value the known declarative-syntax ( angular, vue, knockout etc ) and the development speed at which you will progress, you should be able to create a good-enough spreadsheet like app with kockout relatively fast at least for a proof of concept. Scaling it to thousands of spreadsheet rows, shareable for different users etc, react should be the way to go from my perspective.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文