• JSON 解析出错 at 2018年05月11日

    这。。。明显不是一个 JSON 字符串,引号和键值对中间的冒号都是错的。

    如果要把 Ruby Object 转换成 JSON,需要 JSON.generate(object) 或者 object.to_json。直接传 object 会转成 String,但那个 String 并不是一个合法的 JSON 串。

  • 2015 年來的時候還不是因為參加 RubyConf 來的,是來旅遊順道去台大聽了幾場演講,打了一場 Hackathon。當時知道 xdite 還不是從 Rubyist 口中得知,感覺其實當時已經有不少人都知道這些事了。

  • Ruby 2.6.0-preview1 已发布 at 2018年02月24日

    喷了,这个笔误有点太傻了。。。🤦‍♀️

  • ...你需要先搞清 LTS、rolling distribution 和 half rolling 各自都在干什么,以及为什么需要这么干。LTS 是不会也不能引发 Kernel 的 API 变化的,如果引发了,那么这个发行版基本就完蛋了。

  • 其实我很好奇的是,我看你们研究不同的 IO lib 跟玩玩具一样的。。。为什么不直接用系统 API?

    你能正确封装各个操作系统 API 一起用那也算是大几千个 Star 的项目了,操作系统级别的 API 哪里那么简单。从 libev libevent 到什么 Java 里的 NIO 或者 Rust 里的 tokio 做的都是这件事。你以为看着文档写个 kqueue 就能用了?不讲别的,你先搞清 kqueue 在 Darwin 内核里和 OpenBSD 内核里有哪些区别,MacOS 和 iOS 的各个不同版本 kqueue 的行为是否一致就可以搞好几天了。随随便便就说调用操作系统 API 才叫玩玩具。

    像 libevent 里对于 macOS kqueue 上的 bug 从 16 年讨论到 18 年了,有好几个其他 lib 都是假设 mac 不会被用作服务器而忽略这个 bug 的。再比如 midori 中对于 macOS 还有一个 patch

    class TCPServer
      def tcp_fast_open
        opt = (/darwin/ =~ RUBY_PLATFORM) ? 1 : 5
        self.setsockopt(6, Socket::TCP_FASTOPEN, opt)
      end
    end
    

    一个调用在 Linux 和 BSD 下是什么样的。BSD 是这样,fork 自 BSD 的 Darwin 是不是还这样?这些都是要非常小心测试的。不是写了能运行就代表能用。也不是我能用,大家都能用这么粗暴的关系。

    软件是一个系统工程,很多问题,特别是对于缺乏出问题经验的人会想当然被认为简单。只有出过几次问题,最好这几次问题赔了很多钱,自己才会开始谨慎。

    我就说写个 Hello World 好了,你看看是不是在 docker 的 log 里你的 Hello World 程序不 return 掉,log 都不会正确打印?你这时候要返回去看 printf 到底干了啥。STDOUT 是啥?怎么还操作 I/O 了?怎么还有 buffer 了?buffer 在什么情况下会 flush?docker 的 STDOUT buffer 长啥样?一圈看下来才发现,原来写个 Hello World 也能出问题,还得回去看标准库和操作系统的实现。

    软件就是这么复杂,不是想当然的想用什么用什么,想写什么写什么的。

  • 服务器都是开辟/派遣一个 thread 你的一次 action 的

    谁告诉你的?你要不看看 Node.js 这种连自带原生线程这种东西都不存在大几千 rps 的,或者 nginx 这种单线程下也可以几十万 rps 的,是怎么处理 action 这个问题好不好?这两个都「开辟/派遣」thread 了吗?没有,开销那么大的事情讲究性能谁会去做?

    你倒是反过来理解一下,对于 CPU,几个核就能同时做几件事,Thread 在操作系统层面的本质到底是个什么东西。对于网卡硬件,一次只能读进一个二进制流,多路的本质是什么?讲不清这两个问题你才会陷入对多路复用的理解仅限于 Thread 的解决方案。

    读了一下 Thread::Green 的代码,其实 FD 通信,本身就是 blocking 的。

    求求你把 iom.h 重新读一遍,谢谢。

    比如 select 就是个阻塞调用

    。。。一个 multiplexing 的东西要是变成阻塞调用了,怕不是各个操作系统内核都要气死。select, epoll_wait, kevent 本质上做的是一个事情,而他们实现方法不同,导致了效率的区别。你在 Thread::Green 里看到的 select 也不是真正的 select,而是在 iom.h 里对 select epoll 和 kqueue 进行统一封装后的 API。

    服务器的 Ruby I/O 的问题个人认为是 Ruby String 类 效率问题

    你随便怎么 profile,能 profile 出这个结果也是算你牛逼的。

  • JIT for MRI 开始开发了 at 2018年02月12日

    「让一个性能很好的 Thin 拖得只剩下跟其它一样的水平」少开玩笑了,Thin 的优化可以说是非常差了,和其他 Web 服务器比起来最大的区别就是把 I/O 包装成了异步。

    puma 只处理 Web 服务器的 I/O,应用层调用 DB 也是 I/O,其背后的调用逻辑和

    Thread.new { // 数据库 -> @data }
    

    还真的区别不大,Rails 的实现就是类似于 GCD 一样做了个线程池,很明显这个方法是问题的根源。

    你确定 View 能渲染出 @data 的内容,你确定渲染的时候 数据库已经读完了?

    当然是可以的,但是这里就不是依靠 Thread 或者什么 join 来解决的问题。这就是并发和并行最大的区别,吞吐量和请求处理时间也不是一个概念。你试试用 em-synchrony 替换掉这个部分,就会得到数倍的性能改善。em-synchrony 也是纯 Ruby 实现的,按理说,往里面加一层东西应该会更慢才对,为什么反而快了好几倍呢?

    你对并发和并行的理解太混淆了,以及对 I/O 是什么理解很有问题。View 层和 DB 层都不是在 Ruby 里面处理的,为什么同样调用 mysql2 的驱动,用 goroutine 和用 Ruby 性能差别这么多,问题就出在这里,调用的方法不对。而不是什么别的。

  • JIT for MRI 开始开发了 at 2018年02月12日

    我期待它能本身提供编译的功能,就是 ruby file.rb 就已经能从 YARV -> 机器码,而不依赖外部工具。

    这不叫 JIT 这叫 AOT。尝试的话 RubyMotion 的 Ruby 就是 AOT 编译的。这些尝试 Ruby 社区都有做过,优缺点也都是很明显的。

    rails 太臃肿了,I/O 开销实际上是很小的一部分... 而你的 HTTP parsing、业务处理才是最大的问题。

    我们拿比 Rails 轻量很多的 Sinatra 和 mysql2 裸驱动,不用 ActiveRecord 出来跑个 benchmark 好了。

    Web Server Basic View View + DB
    WEBrick 273 req/s 116 req/s 111 req/s
    Thin 1597 req/s 174 req/s 139 req/s
    Unicorn 605 req/s 121 req/s 121 req/s

    为什么 Thin 在 Basic 的时候碾压其他 Server,因为它 I/O 异步了。为什么 View 和 DB 又变差了呢?因为读取模板和查询数据库是同步的。我写 midori 的时候,把每个请求的栈分配都弄到极轻量,还用了 C extension 做 HTTP Parser,单跑 Hello World 的时候也就比 Thin 再快一倍,而加任何一个 I/O 操作就慢十倍。

    谁是「很小的一部分」谁是「很大的一部分」就是这么简单的问题,哪来什么刚好相反。但是只关注 I/O 会限制 Ruby 在 Web 服务器以外别的领域的发展。所以 MJIT 还是非常必要搞的,没人讨厌快,但不希望快是以牺牲其他东西为代价,比如 Rubinius 把代码写到自己也维护不动。而 MJIT 所做的就是这一点。

  • 其实在我做那个试验时候,你的 em-midori 还没加 C HTTP parser 进去,后来我看到你加进去了。

    midori 的 HTTP parser 用 C ext 是 2017 年 1 月 6 日随着 commit 3438d00 加入的,你注册 Ruby China 是 2017 年 6 月 10 日。

    Ruby parsing 效率很低

    快不快慢不慢 profile 一下就知道,midori 换完 parser 对性能影响 < 10%。

    我的观点是,Ruby 与其发明这些 IO 优化的玩意,不如解决计算上优化。

    IO 是有瓶颈的,就算你解决了并发,非阻塞,实际上计算性能还是差得老远的情况下,你 Ruby 代码连磁盘 I/O 都跑不赢,很可能你刷新一个网页,I/O 就那么点东西,但是逻辑就已经够你处理半天了。

    如果计算是瓶颈,那无法解释 Thin 服务器单线程的 Hello World benchmark 为什么能比 Webrick 快将近 20 倍。Web 服务器本来就是 I/O heavy 而不是计算 heavy 的。你用 Ruby 写个堆排那才是真惨,但写完你发现跑的竟然比 Python 3 还快。可见 Ruby 服务器慢绝不是但但的解释器执行效率低这种问题。再反过来 Ruby 上了 RTL-MJIT 我们按最快的那个版本算,就算快 6 倍,比起 Pypy 还是有很大的性能差距,为什么?

    实际上问题还在后头,你有业务逻辑代码。

    网站的业务逻辑绝不是性能瓶颈,大多数 API 核心就是 CRUD,CRUD 的部分的计算都是依赖数据库在运行。自然很大的问题是等待数据库时的性能,而不是 Ruby 本身运算的性能。

    IO 就那么点东西,但是逻辑就已经够你处理半天了。

    究竟 I/O 是大头,还是逻辑是大头,不是想当然说出来的。是通过跑 profiler,跑 benchmark,跑 gdb lldb 找出来的。高德纳说过:「程序员浪费了大量时间考虑程序非关键部分的速度,但如果算上调试和维护,这些性能企图事实上带来严重负面影响。我们必须忘记微小性能收益,97% 的时候:不成熟的优化是万恶之源。但也别放过优化关键 3% 的机会。」优化性能的关键就是去找这 3%。如果为了计算上的极致优势,那我们都应该写汇编。

    Thread::Green 实现的是不用陷入内核态的轻量级调度,解决的是 类似于 Promise,RxJava 那种异步同步的问题吧。这样的话,跟 GCD 也没什么区别,GCD 很大一部分用例就是把线程异步回调同步起来。

    无论 Promise,RxJava 还是 Thread::Green 和 GCD 解决的问题都有很大的区别。这里面只有 GCD 是「把线程异步回调同步起来」,剩下的都不是。如果为了解决 I/O 异步去用 GCD,那 performance 可以说是惨不忍睹的。GCD 是来做并行计算的同步回调的,而不是解决并发 I/O 的同步回调的。这是完全两个概念。比如你用 GCD 写个 fibonacci 就会有性能提升,剩下几个写 fibonacci 都不会提升。但写个 RPC 则是前几者的性能提升很明显,后者提升不明显。这两个东西不但完全解决的是不同问题,而且从原理和实现上也差的非常多。

    不能因为名字里都有「异步」「回调」「同步」就认为是一个东西。既然你那么喜欢 Cocoa 的 API。那么关于 Web Server 用 GCD 本质是 blocking 的这个问题 IBM Kitura 2 年前就讨论过了。https://github.com/IBM-Swift/Kitura/issues/121。或者看 vapor 去年 11 月前后的 GitHub Issue,社区试图从 libdispatch 转换到 non-blocking I/O 所作的工作。

    对客户端作用大一些吧,对于服务端做同步这种事情。。。你让服务器去做长时间等待异步同步,那是在消耗服务端资源。

    你不让服务器等待异步同步,就要等待阻塞同步,或者线程上下文切换。你可以比较一下到底哪个消耗资源。

  • JIT for MRI 开始开发了 at 2018年02月11日

    JRuby 主要是和 MRI 有诸多不兼容的地方。一方面没有引入类似 JNI 的机制做 C 扩展的引用,另一方面像是 Ruby 这几个版本非常重要的 Fiber 特性,一直不跟进。这对 JRuby 的推广有很大的障碍。但是反过来说,Ruby 社区为了保留灵活的演进,在几次劝进下都拒绝搞标准化。事实上最后搞了一个标准化 Ruby ISO,基于那个实现了 mruby,社区反映也是不温不火,两边都有苦难言啊。