2

背景

开始

       故事的开始因为在工作中遇到一个需求去解决加载数据非常慢的问题,关键程序是已经在生产环境运行了相当长一段时间的,按道理来说是不会有问题,至少从线上运行的稳定性来说。

个人观点

       在这里仅作为开发小结记录一下,谁写的和为什么这样写的问题,不在此次记录总结范围之内,只针对代码做出分析,如果您看了觉得或者已经有更好的办法,烦请告诉我一下,我们可以共同讨论,如果有地方不对,也请不吝斧正。

为什么要记录下来?

       做任何事情都要有产出,日常开发也一样,除了交付任务,还应有自己的总结,以利于自己后续复盘,产出不在于多少,几行文字、一个表格或者画一幅图。产出也不在于100%能搞懂开发中的每一个问题,把看不懂的总结出来,也是产出。

       后续解决的问题或没解决的都在这里总结记录,此前解决过的也会陆续重新总结

找到原因

       经过一番调试定位到代码之后,找到了问题主要分2类:

1.重复求值

    这个很好理解,就是上面已经取得相同操作结果,或者可以取得相同操作结果存入变量,但是此处并没有这个做而是
每一次都执行需要求值的表达式去得到结果,大大的降低了执行效率和提高了资源占用
    

image.png

2.循环嵌套

       鉴于涉及公司代码,不方便贴出,为了更加生动的描述具体问题,下面是我根据问题抽象出的代码Demo和描述如下:

  • 1).循环500次,对应的list集合中有50W条数据
  • 2).根据Lambda找到ProductId为213000的数据,然后存入result集合

image.png


    根据上面的示例代码可以发现,在循环中,又使用了循环,暂且不考虑
ForEach是否是多线程或者它和foreach 以及for的执行效率,为什么说又使用了循环呢?经过排查发现导致问题的核心的
原因就是内部的Lambda表达式求值,为什么求值慢呢?
    特意查看了源码并且查看相关参考资料,因为Lambda就是循环实现的,只不过加入了迭代器和状态机这样一些的延迟
机制,即使这样它依然会影响效率。
    如果从数据结构的角度抽象来分析,此处时间复杂度就是O(500*50w) 随着次数增大,执行时间就是越来越大的增长。

3.验证执行时间

image.png

验证执行时间为:1195毫秒,如果在外层再加一层循环呢?

image.png

集合操作皆可Lambda?

       鉴于已经找到了问题所在,下一步就要着手解决的方法了,第一个问题好解决,改变一下求值写法就好了。

       主要是第二个问题的解决,在解决问题之前得到了一个很重要的的结论 那就是"Lambda并不适用所有地方,反而有时会让你的程序更糟糕" 看了上面的例子是不是深有体会。

解决方案

  • 如何优化lambda(循环)?

    我们要思考问题的本质是在这里对list怎么查找比顺序循环快,在数据结构中顺序表查找分为三类:

     1.顺序查找
     2.二分查找
     3.索引查找
     

           如何选择对应查找优化,这里的重点就是要将时间复杂度从O(n)或者O(10x500x50w)转为O(1),毫无疑问在这选择索引查找,因为根据索引下标访问元素的时间复杂度为O(1),那索引查找必定优于顺序查找的,接下来如何变为索引查找呢?

image.png

       我们不禁会问:"在这里恰好是索引下标跟ProductId对上了才能找到,那如果是乱的呢"? 那就需要变通一下,就是变为字典集合,根据键找到值

对于无重复数据的集合来说我们先转为Dictionary再看执行时间,虽比不上索引,但是跟循环比呢?

image.png

       到这里我们还需要思考,这里因为ProductId是不重复的你才可以加到
Dictionary,如果有重复的咋整?别慌有办法,就是:

对于有重复数据的集合来说我们可以使用ILookup,因为他会把相同键分组使用一个Key

image.png

总结

 1.语法糖和开发库确实很不错,但是对于维护来说,可能会造成一定的障碍。
 2.在日常开发中,虽然现在有很多的库供我们调用,在使用的过程中我们应该去结合代码思考是否可以在特定的地方用
   而不是一味的追求语法糖的简单、新奇。

yuxl01
18 声望0 粉丝

写代码我必须再认真一点,当我最终放下键盘时,我不想仍有遗憾。