V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
hupo0
V2EX  ›  JavaScript

感觉这次写的库能比 js 原生数组方法要快

  •  2
     
  •   hupo0 · 2021-03-06 16:44:41 +08:00 · 3790 次点击
    这是一个创建于 1140 天前的主题,其中的信息可能已经有所发展或是发生改变。

    no-stream 似乎比 js 原生数组方法快

    正在等待被锤的忐忑心理中。

    28 条回复    2021-03-08 14:10:31 +08:00
    eason1874
        1
    eason1874  
       2021-03-06 17:23:02 +08:00
    不太懂。是循环展开的意思吗?话说现在 JS 不是都要编译了吗,这个在编译时不会自动优化吗?
    hupo0
        2
    hupo0  
    OP
       2021-03-06 17:53:56 +08:00
    @eason1874 应该不叫循环展开,是把多次循环合成一个。只见过 c++ 和 rust 有这类优化,而且对代码有不少限制,可能高级语言要考虑闭包什么的。
    cyberpoint
        3
    cyberpoint  
       2021-03-06 18:06:33 +08:00
    比如程序员为了方便快捷就会写这种代码:const foo = bar.filter(...).map(...).reduce(...)
    这样就会有 3 次遍历,但是其实这些...的过程是可以在一次遍历中完成的
    muzuiget
        4
    muzuiget  
       2021-03-06 18:12:47 +08:00
    明明就是写法问题,这种 [...xxx].map(xxx).filter(xxx).slice(xxx) 我为什么不直接用 for,也是一次循环。
    kkocdko
        5
    kkocdko  
       2021-03-06 18:12:58 +08:00
    是这样的,我记得 rust 的迭代器有这种优化
    hupo0
        6
    hupo0  
    OP
       2021-03-06 18:13:01 +08:00
    @cyberpoint 表达到位
    Jirajine
        7
    Jirajine  
       2021-03-06 18:13:24 +08:00 via Android   ❤️ 1
    这个应该不是语言的优化吧,只要方法实现上不返回另一个数组,而是返回一个 lazy evaluation 的 iterator 就可以链式调用多次只遍历一次了。
    muzuiget
        8
    muzuiget  
       2021-03-06 18:14:36 +08:00
    谁白了这种链式求值,就是方便而已,不在乎这点性能损失,真遇到巨大数组,就应该专门写优化代码。
    supermao
        9
    supermao  
       2021-03-06 18:17:22 +08:00
    这种不需要猜,你自己做个 benchmark 不就出来了?
    supermao
        10
    supermao  
       2021-03-06 18:18:10 +08:00
    @supermao 原来已经做了。。。
    Rorysky
        11
    Rorysky  
       2021-03-06 18:26:05 +08:00
    @muzuiget 这不就是 java stream 的写法么
    hupo0
        12
    hupo0  
    OP
       2021-03-06 18:35:44 +08:00
    @Jirajine iterator 转成另一个 iterator,一般是包多一层,该做的“结束检查”还是不会漏的,还是会有额外的性能损耗。实际用 benchmark 测也会发现比较慢。
    jones2000
        13
    jones2000  
       2021-03-06 19:35:50 +08:00   ❤️ 1
    太高深了, 看不懂, 学了 2 年 js, 只会用 for(var i=0;i<data.length;++i) .......
    musi
        14
    musi  
       2021-03-06 20:02:53 +08:00   ❤️ 1
    纠正一下,这不叫比“ js 原生数组方法要快”
    js 可没让你本来遍历一次的事用三次遍历完成
    geelaw
        15
    geelaw  
       2021-03-06 20:06:38 +08:00 via iPhone
    这个问题和数组遍历几遍没关系,主要区别在于内存分配。

    Array.map 每次都要分配新的数组,可以想象 no-stream 的 map 只是变换迭代器,当然快。当然这不能怪 Array,毕竟功能不同,要写出值得比较的代码可以对 mf 复合自己 map_count 次,再变换成 reducer,然后直接在 data 数组上用 reduce 。

    而且好好写 for 循环不香吗?
    kuunnnn
        16
    kuunnnn  
       2021-03-06 21:01:46 +08:00   ❤️ 1
    这不就是延迟计算吗,lazy.js 有做这个的,lodash 好像也有类似的
    shyangs
        17
    shyangs  
       2021-03-06 22:45:25 +08:00
    為什麼不用 lodash 的_.chain()
    molika
        18
    molika  
       2021-03-06 23:12:16 +08:00 via iPhone
    transduce 而已 clojure/clojureScript N 年前就这么玩了 话说 js 的 map reduce 啥的竟然不是惰性的 transduce ? 吃惊
    hupo0
        19
    hupo0  
    OP
       2021-03-07 00:29:02 +08:00
    @shyangs lodash 没用过,我才知道 chain 。看了下源码,他用一个 Wrapper 的抽象,在求值前,把中间的 actions 合成一个。虽然感觉大家想做的事情一样,但实现上效率不那么高。不过 lodash 自带的东西多,容错好,本身不用太注重效率。

    实际 benchmark 跑下来,速度和数组自带的方法差不多,但是内存上应该会更优越。
    hupo0
        20
    hupo0  
    OP
       2021-03-07 00:32:38 +08:00
    @geelaw 比较的代码确实有的斟酌的地方,但是太难找到合适的,就粗暴点只比较了 map 和 reduce 一起用的情况。

    就是不想好好写 for 循环。
    hupo0
        21
    hupo0  
    OP
       2021-03-07 00:34:06 +08:00
    @musi 所以用了似乎,实际上,如果要提高代码的表达,很多时候不会选择把逻辑都挤在一个循环里,至少我是这样的。
    civet
        22
    civet  
       2021-03-07 00:59:17 +08:00 via iPhone
    @molika js 不是天生就是为函数式编程服务的,而是社区各路大神引领了这种潮流,实际上就是底层没实现,写法先出现
    Rocketer
        23
    Rocketer  
       2021-03-07 01:58:30 +08:00 via iPhone
    js 聊性能的意义不是特别大,毕竟主要用途是前端,数组大小不会超过 100 条。相比较而言,节约代码提高开发效率更受欢迎。

    真要用在后端处理大数据,那还是像 Python 那样用 C 做类库,让 js 调用吧,纯 js 再努力也提升不了太多
    darknoll
        24
    darknoll  
       2021-03-07 11:59:43 +08:00
    没有意义,前端无需注重效率,数据量大了应该是分页之类的,反正 Iterable 对象我都用 for of,我也知道 for 循环速度最快。
    wxsm
        25
    wxsm  
       2021-03-07 23:27:09 +08:00 via iPhone
    从算法角度来说,1 次循环,3 次循环,n 次循环,时间复杂度都是 O(n),并没有实质区别。
    cenbiq
        26
    cenbiq  
       2021-03-08 12:27:49 +08:00
    不禁感慨,C#的 Linq 真是有远见
    hupo0
        27
    hupo0  
    OP
       2021-03-08 13:47:21 +08:00
    @cenbiq 看看所谓的 Zero cost abstraction - 刘雨培的文章 - 知乎
    https://zhuanlan.zhihu.com/p/24975048
    吐槽 C# - 刘雨培的文章 - 知乎
    https://zhuanlan.zhihu.com/p/30653282

    参考这两篇回答,17 年的 C#还没有对 linq 进行优化。虽然用迭代器也是相当于只遍历一遍,但是迭代器“调用”迭代器的过程还是会有额外的性能损耗。完美一点的是,能做到跟 C++一样,把迭代器 yield 的逻辑内联到一个循环里。

    想来 no-stream 的做法,是函数调用层面,把"yield"部分通过函数包函数的方式合并到一个循环里。虽然也是有函数调用方面的开销,但目前来看,会比 JS 的 Generator 还节省性能些。

    从现有的 C++和 Rust 的案例来看,其实正道还是 iterator,似乎这样的结构更容易优化。linq 也是在正道上,只是需要引擎对这部分进行优化。与之对应的是 JS 的 Generator 。可惜就是目前他们还很拉胯。
    cenbiq
        28
    cenbiq  
       2021-03-08 14:10:31 +08:00
    @hupo0 用 yield 肯定会产生额外性能损耗嘛。js 是有 Generator 但是我接触下来应用很少,反观 C# IEnumerable 和 IEnumerator 几乎是必不可少的东西,no-stream 类似 rx 管道吧,收集操作最后一次循环执行,无非就是这些方法了。
    不过还是得夸 linq,想当初换到 js 和 java 各种不习惯。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4750 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 09:54 · PVG 17:54 · LAX 02:54 · JFK 05:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.