【问题标题】:Improve performance of searching JSON object with jQuery提高使用 jQuery 搜索 JSON 对象的性能
【发布时间】:2012-03-29 19:35:03
【问题描述】:

如果已经在某处回答了这个问题,请原谅我。我已经搜索过了,这似乎是一个相当具体的案例。

这是 JSON 的示例(注意:这是非常精简的 - 这是动态加载的,目前有 126 条记录):

var layout = {
"2":[{"id":"40","attribute_id":"2","option_id":null,"design_attribute_id":"4","design_option_id":"131","width":"10","height":"10",
            "repeat":"0","top":"0","left":"0","bottom":"0","right":"0","use_right":"0","use_bottom":"0","apply_to_options":"0"},
    {"id":"41","attribute_id":"2","option_id":"115","design_attribute_id":"4","design_option_id":"131","width":"2","height":"1",
            "repeat":"0","top":"0","left":"0","bottom":"4","right":"2","use_right":"0","use_bottom":"0","apply_to_options":"0"},
    {"id":"44","attribute_id":"2","option_id":"118","design_attribute_id":"4","design_option_id":"131","width":"10","height":"10",
            "repeat":"0","top":"0","left":"0","bottom":"0","right":"0","use_right":"0","use_bottom":"0","apply_to_options":"0"}],
"5":[{"id":"326","attribute_id":"5","option_id":null,"design_attribute_id":"4","design_option_id":"154","width":"5","height":"5",
            "repeat":"0","top":"0","left":"0","bottom":"0","right":"0","use_right":"0","use_bottom":"0","apply_to_options":"0"}]
};

我需要匹配正确的值组合。这是我目前使用的功能:

function drawOption(attid, optid) {
    var attlayout = layout[attid];
    $.each(attlayout, function(k, v) {
        // d_opt_id and d_opt_id are global scoped variable set elsewhere
        if (v.design_attribute_id == d_att_id
            && v.design_option_id == d_opt_id  
            && v.attribute_id == attid 
            && ((v.apply_to_options == 1 || (v.option_id === optid)))) {
                // Do stuff here
        }
    });
}

问题是我可能会遍历 10-15 个布局(唯一的 attid),而任何给定的布局 (attid) 可能有多达 50 种可能性,这意味着这个循环正在运行很多次。

考虑到必须匹配的多个条件,AJAX 调用会更好吗? (这个 JSON 是通过 PHP 动态创建的,所以我可以制作一个 PHP 函数,它可能会更有效地做到这一点), 还是我完全错过了有关如何在 JSON 对象中查找项目的信息?

一如既往,欢迎提出任何改进代码的建议!

编辑:
我很抱歉没有说清楚,但这个问题的目的是找到一种提高性能的方法。该页面有很多 javascript,这是我知道性能低于其可能的位置。

【问题讨论】:

  • 您可以使用jsonpath 库。几乎不会更快,但您将拥有更简单的代码。
  • 您是否关心可读性、性能或其他一些指标?
  • 首要关注的是性能。可读性是次要的。
  • jsonpath : 无需特殊脚本即可在客户端以交互方式查找和提取数据。[link]goessner.net/articles/JsonPath
  • 如果代码可以正常工作并且您只是想改进它,您应该将其发布到codereview.stackexchange.com。

标签: javascript jquery json


【解决方案1】:

首先,您应该衡量,并且只有在存在真正的性能问题时才采取行动。您需要确切的数字,例如 200 毫秒或 80% 的时间都花在了那里。 “它运行很多”并不意味着什么。浏览器可以非常快地循环。

您可以像其他人提到的那样改进常量因素,例如使用原生 for 循环而不是 jQuery.each。在这种情况下,Chaching 全局变量不会对您有太大帮助。

如果你真的想提高效率,你应该找到比 O(n) 更好的算法。假设您只使用这些数据来查找符合特定条件的元素,您可以使用 JS 对象作为哈希来实现 O(1) 性能。

只是针对您的具体情况的示例:

var layout = {
  "2": { "2,,4,131,10,0": ["40", "93"], "2,115,4,131,0": ["41"] },
  "4": { ... },
  ...
};

在 php 中生成此输出相当容易,然后您只需使用查找来查找与您的特定条件匹配的 id。

【讨论】:

  • 我同意现代解释器缓存全局引用并没有多大帮助。但是,鉴于问题中的信息,这几乎是唯一可以建议的事情。当然,除了从缩减数据开始。但他已经想通了。
  • 我倾向于接受这个答案,但有一个问题:您能否提供一个简单的代码示例来说明查找代码的外观?正如您所提到的,在 php 中生成此 JSON 将是微不足道的,但我将非常感谢一个示例,说明 javascript 会根据您上面提供的 O(1) 示例寻找/找到适当的记录。
  • 因此,在其他答案的帮助下,我偶然发现了如何使用您的代码。它是最快的:对于 100,000 次迭代,我的原始代码用了 2304 毫秒,Colin Godsey 的答案用了 120 毫秒,而这段代码只用了 82 毫秒。
  • 对不起,我几天没上网了。很高兴看到你最终能解决这个问题。 :)
【解决方案2】:

恕我直言,一个简单的 hashmap 索引可能效果最好。这确实需要您提前循环数据,但可以轻松地附加和缓存索引。

生成索引后,查找的时间应该是 O(1),并且每个键都会处理多个条目。

    var layout = {
    "2":[[...], ...],
    "5":[[...], ...]
    };

    var index = {};

    function makeKey(data) {
        return data.join('_');
    }

    for(var l in layout) {
        var cur = layout[l];

        for(var i in cur) {
            var item = cur[i];
            var key = makeKey([item.p1, item.p2, ...]);

            index[key] = index[key] || [];
            index[key].push(item);
        }
    }

    function find(attid, optid) {
        var key = makeKey([attid, optid, 1, d_att_id, ...]);
        return index[key]; //this is an array of results
    }

【讨论】:

  • 因为你的是唯一有一些具体代码的,所以我先测试了你的。我的原始脚本运行 100,000 次需要 2,304 毫秒。您的运行耗时 120 毫秒(索引的“加载”耗时
  • 科林,这是一个很好的答案,没有它,我将无法理解/解决@galambalazs 的答案。而且,现实情况是,他的答案是表现最好的:在 php 方面,如果我将辅助键设置为哈希(如您的建议),我可以快速访问它而无需任何循环,而且我没有不必运行 javascript 循环来准备索引。我投票赞成你的答案,因为它有很大帮助,但因为他的速度最快,他将获得赏金。谢谢!
  • 请记住,每当您获得新数据时,您都需要更新索引。
  • np,很高兴我能帮上忙。我忽略了数据可能来自服务器的事实(nutz!)。欣赏UPS!因为它,我现在可以自己贴其他海报了:)
【解决方案3】:

我的第一个建议是停止使用$.each,如果您想尽可能地发挥性能。 jQuery.each 比传统循环做得更多。在浏览器的调试器(即 Safari 的/Chrome 的 Web 开发者工具)运行的情况下查看 this jsFiddle,然后逐步调试直到从 jQuery 完全返回执行。

供参考,小提琴的代码是:

var myArr = [{"foo":"bar"},{"answer":42}];

debugger;

$.each(myArr, function(k, v) {
    console.log(k + ': ');
    console.dir(v);
});​

现在运行the second version:

var myArr = [{"foo":"bar"},{"answer":42}],
    i, j;

debugger;

for (i = 0, j = myArr.length; i < j; i += 1) {
    console.log('myArr[' + i + ']: ');
    console.dir(myArr[i]);
}​

请注意,在第二个版本中执行的操作要少得多。所以这是微小的一点性能提升。

其次,尽可能多地消除本地范围之外的查找。即使您缓存了对全局变量的引用(嘘!),那么您可以save a lot of time 考虑到该循环可能会执行数百次。所以而不是:

function foo(a, b) {
    if (a === globalA && b === globalB) {
        // Do stuff
    }
}

你会这样做:

function foo(a, b) {
    var gA = globalA,
        gB = globalB;
    if (a === gA && b === gB) {
        // Do stuff
    }
}

至于根据对象成员配对条件,我没有看到其他可以改进的地方。您正在检查的对象属性是顶级的,并且您正在查看每个对象的本地实例(因此范围链查找很短)。

如果不知道这实际上应该如何工作,这些是我能提出的最佳建议。但是,我可以猜测您从更简单的 JSON 数据开始的想法将是一个很大的改进。如果您知道布局及其约束是什么,那么从服务器请求特定详细信息意味着您不必检查这么多条件。您可以简单地向服务器询问您实际需要实现的详细信息并循环访问这些信息(以更新 DOM 或其他)。

【讨论】:

  • 很好的答案。我可以跟进几个问题吗?您如何看待这里提到的其他选项 - jsonpath 和 grep?另外,鉴于我的变量 layout 是一个全局作用域的 json 变量,而您的 cmets 是关于全局作用域的,那么将 THAT 设置为局部作用域是否也有好处?如果是这样,您是否有任何内存消耗问题,或者担心每次触发函数时变量在本地范围内的问题?
  • 粗略地看一下 jsonpath 和 $.grep,您只是将循环再次推到另一段代码中。但是,jsonpath 提供了一个 PHP 版本,您可以在服务器端使用它;所以这对它来说是一个优势。关于layout 的范围,您可以通过var attlayout = layout[attid]; 缓存对对象特定属性的引用,从而降低循环中查找的成本(即使它们成为本地)。这是浅拷贝,不是深拷贝。因此,消耗的新内存非常少;只是创建了一个新指针。
  • 我已经测试了这些调整,它们肯定会有所改进,但不是按照我看到的其他选项的顺序。我的原始代码运行 100,000 次迭代需要 2304 毫秒,根据您的建议,它已降至 1472 毫秒——这是一个很大的改进,但请参阅我使用 Colin Godseys 答案的结果(100,000 次迭代需要 120 毫秒)。
【解决方案4】:

我看到您正在搜索 5 个字段:v.design_attribute_id、v.design_option_id、v.attribute_id、v.apply_to_options、v.option_id。

您可以做的是向对象添加一个名为“key”的额外字段,该字段由这些字段中的值组合而成。

这是一个例子

       {
            "key": "4_131_2_0_0" //i picked 0 to represent null, but you can pick any number                                    
            "id": "40",
            "attribute_id": "2",
            "option_id": null,
            "design_attribute_id": "4",
            "design_option_id": "131",
            "width": "10",
            "height": "10",
            "repeat": "0",
            "top": "0",
            "left": "0",
            "bottom": "0",
            "right": "0",
            "use_right": "0",
            "use_bottom": "0",
            "apply_to_options": "0"
        }

请注意,您必须标准化每个值的长度。 这意味着如果一个对象 optionId 是 1 而另一个对象 optionID 是 566 你必须代表 密钥字符串中的第一个 optionId 为 001。

使用此字段,您可以在将数组返回给客户端之前在服务器端对数组进行排序。

然后您可以使用二进制搜索来查找客户端上的值。

使用位于此处的二进制搜索实现 http://www.nczonline.net/blog/2009/09/01/computer-science-in-javascript-binary-search/

您的搜索功能类似于

function drawOption(attid, optid) {
    var attlayout = layout[attid];
    var needle = d_att_id + "_" + d_opt_id + "_" + attid + "_" + optid; //remember to normalize  length if you have to

    var idx = binarySearch(attlayout,needle);

    var item;
    if(idx !== -1){
        item = attlayout[idx];
        //do something 

    }
}

您可以尝试使用此复合键思想的另一种方法是让服务器返回 一个大对象中的布局对象由 attid,v.design_attribute_id,v.design_option_id,v.attribute_id,v.apply_to_options,v.option_id

然后您可以在 O(1) 时间内查找。 它看起来像

 function drawOption(attid, optid) {

    var needle = attid + "_" + d_att_id + "_" + d_opt_id + "_" + attid + "_" + optid; //remember to normalize  length if you have to

    var item = layout[needle];
    if(typeof item !== "undefined"){
        //do something 

    }
}

【讨论】:

    【解决方案5】:

    在尝试改进代码时,最好使用 firebug 分析检查哪些函数需要时间。您可以通过单击 firebug 控制台面板中的配置文件按钮进行配置,然后运行您的代码或在您的代码中使用 firebug's profiling commands

    从你给出的代码中,只能给出几个改进点。

    1. $.each 与本机循环解决方案相比速度较慢。为了最好 循环解决方案,看看这个JsPref test
    2. 最好将 JSON 更改为使用数组而不是对象文字。据说检索值更快。

    【讨论】:

      【解决方案6】:

      我以前有过此类问题的经验,我的 js 对象数组包含 8000 条记录甚至更多。 我的经验不是关于性能,而是代码的可读性、可维护性和可扩展性。

      因此我在 2 年前开发了一个 JS 对象查询库:JSOQL http://code.google.com/p/jsoql/

      它像 SQL 一样工作,允许您使用类似于 SQL 的语法查询您的 js 对象数组。

      示例用法是这样的,我不记得了,但是您可以在下载选项卡中下载示例用法。

      new JSQOL().Select([field1, field2, field3 ...]).From({ ... }) .Where(fn) .Offset(int) .Limit(int) .Get();
      

      注意: {...} 是你的对象数组,或者它自己的一个对象。

      希望对您有所帮助,如果您需要更多信息,可以给我发消息。

      【讨论】:

        【解决方案7】:

        它不会在任何地方都有效,但您的问题听起来像是可以通过 webworkers 解决的问题

        如果您没有网络工作者,我会考虑的另一件事是尽量不要长时间阻止 ui。如果您可以将它分成大约 40 毫秒的位,然后在几毫秒后设置下一个块,用户将获得更愉快的体验。这需要一点点摆弄,但是当某些事情花费的时间超过 50 到 100 毫秒之间时,用户会开始注意到一些东西

        【讨论】:

          【解决方案8】:

          你考虑过使用 jQuery 的 grep 函数吗?

          jQuery grep

          以jquery grep on json object array 为例。

          【讨论】:

          • 谢谢。这看起来很有希望,但是我想知道这样做是否会显着提高性能?我主要关心性能/速度。
          • 您是唯一一个可以准确跟踪您正在做的事情的人。尝试使用几种不同的方法来过滤您的数组,看看哪种方法最适合您的情况。
          【解决方案9】:

          这是一种可能会以使用更多内存为代价产生更好性能的技术。

          我将保留我的代码示例,只是为了说明这个概念。

          首先,您需要将 JSON 数据预处理为一些充当索引的附加数组。以下是预处理后最终数组的示例:

          var layouts_by_attribute = {
              // attribute_id => array(layouts)
              2: [40, 41, 44],
              5: [326]
          };
          
          var layouts_by_design_attribute_id = {
              // design_attribute_id => array(layouts)
              4: [40, 41, 44, 326]
          };
          

          现在通过属性查找布局非常快捷:

          function findByAttribute(attribute_id) {
              return layouts = layouts_by_attribute[attribute_id];
          }
          
          function findByDesignAttribute(design_attribute_id) {
              return layouts = layouts_by_design_attribute[design_attribute_id];
          }
          

          【讨论】:

          • 抱歉,我的编程语言混用了。这个概念仍然是合理的,我更新了代码。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-30
          • 2018-12-10
          • 2011-08-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多