我测了一下。用另一个 terminal 执行 sync; echo 3 > /proc/sys/vm/drop_caches
可以把 cache drop 掉
Linux 对内存极端不够的场景确实是会直接 kill 掉的,这一点非常。。。但 Linux 有一个指令可以清空掉 buffer 和 cache 的内存来方便调试,你可以找一下。
如果你使用 htop 查看进程的话,内存里黄色的和蓝色的这块,就分别是 buffered 和 cached 内存。
Linux 倾向于不直接释放,并吃满内存以做缓存加速系统运行,只有当内存不够时再去释放掉。这一点在 Windows 上从 Windows Vista 中也引入了这一特性。Mac 上不出意外也是有的,只是因为你一直在用,所以被慢慢释放掉了。
你测 sys_mem 的方法是 RSS,对 RSS 的定义如下
Resident Set Size = how much memory is allocated to that process and is in RAM (including shared memory!). This includes all stack and heap memory and shared libraries, as long as they are in RAM. It does not include memory which is swapped out.
操作系统会通过 shared memory 对内存进行缓冲(buffered),以使得重启线程或程序后能更快加载,这是 Linux 的内存管理特性,只有当系统本身内存不够用时,才会去释放这部分 buffered 的内存。程序本身不能主动释放自己的缓存内存。
用 CentOS 跑了一下
[root@6d454a5b971b /]# ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
[root@6d454a5b971b /]# vi gc.rb
[root@6d454a5b971b /]# ruby gc.rb
created a json file: 52866672[50.42 MB]
init the json file at the first launch, please run again.
[root@6d454a5b971b /]# ruby gc.rb
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
sys_mem:9.48 MB, objspace_mem:4.15 MB
3s sys_mem:9.62 MB, objspace_mem:3.46 MB
6s sys_mem:9.63 MB, objspace_mem:3.46 MB
9s sys_mem:9.63 MB, objspace_mem:3.46 MB
read content from file 52866672[50.42 MB]
12s sys_mem:60.12 MB, objspace_mem:53.88 MB
15s sys_mem:60.12 MB, objspace_mem:53.88 MB
18s sys_mem:60.12 MB, objspace_mem:53.88 MB
21s sys_mem:60.12 MB, objspace_mem:53.88 MB
24s sys_mem:60.12 MB, objspace_mem:53.88 MB
27s sys_mem:60.12 MB, objspace_mem:53.88 MB
30s sys_mem:60.12 MB, objspace_mem:53.88 MB
33s sys_mem:60.12 MB, objspace_mem:53.88 MB
36s sys_mem:60.12 MB, objspace_mem:53.88 MB
39s sys_mem:60.12 MB, objspace_mem:53.88 MB
parse json string to object
43s sys_mem:360.47 MB, objspace_mem:322.96 MB
46s sys_mem:360.54 MB, objspace_mem:322.96 MB
50s sys_mem:360.54 MB, objspace_mem:322.96 MB
53s sys_mem:360.54 MB, objspace_mem:322.96 MB
56s sys_mem:360.54 MB, objspace_mem:322.96 MB
1m sys_mem:360.54 MB, objspace_mem:322.96 MB
1m3s sys_mem:360.54 MB, objspace_mem:322.96 MB
1m7s sys_mem:360.54 MB, objspace_mem:322.96 MB
1m10s sys_mem:360.54 MB, objspace_mem:322.96 MB
set string&object to nil, and sleep 30 min to check the memory usage.
1m13s sys_mem:304.78 MB, objspace_mem:3.46 MB
1m16s sys_mem:304.78 MB, objspace_mem:3.46 MB
1m19s sys_mem:304.78 MB, objspace_mem:3.46 MB
1m22s sys_mem:304.78 MB, objspace_mem:3.46 MB
几秒钟就释放干净了。你的启动参数是怎样的?
统计的时候能不能跑一下 GC.stat
,这样看不出来是哪里的内存。
矩阵运算并不是 Python 语言的特性啊。。。主要是 Ruby 在这类语法糖的问题上处理非常宽松,经常一个方法一堆 alias,这 Python 吧。。。
突然就明白我为什么通过不了 Ruby Certificate 考试了
reduce
被 Python 3 移到 functools
,迫使每次使用的时候都要 import
一下呢?我想出来的使用 Ruby 的一行解:
def kai_encode(num)
num.to_s(2).rjust(240, '0').scan(/.{5}/).map {|n| '123456789abcdefghijkmnopqrstuvwx'[n.to_i(2)]}.join
end
def kai_fmt(kai)
kai.scan(/.{4}/).join(' ')
end
kai_encode(0x00004a52931ce4085c14bdce014a0318846a0c808c60294a6314a34a1295b9ce) # => "aaaa788522f2agff16617755e979244166677664a9aacfff"
kai_fmt(kai_encode(genome)) # => "aaaa 7885 22f2 agff 1661 7755 e979 2441 6667 7664 a9aa cfff"
好在 Ruby 的 rjust
方法是内建的,而不需要 import 一个叫 left-pad
的包,一旦被删除了就天下大乱了(逃
没,做内部服务的下位机,不是用来做生产服务器的。做生产服务器这性能恐怕不行,我现在用的是 3B+
在跑,运行了好久了。
看了眼地址,变成知乎的 CDN 了,还是看不了。CDN 图片有缓存,自己上传的时候看过的当然能看到,但盗链到其它网站别人看到的全部都是无权限啊。
「ミファーの祈り、いつでも使えるよ」
感觉看了半天大多都是套路云的问题啊,如果是墙的升级的话不该是这种情况吧。
按道理来说 Ruby China 服务器在香港,也没有见过严重丢包。海外链路有丢包其实是挺常见,最常见的问题都和链路质量有关,特别是 peer 的情况。像是 Linode 的几个服务器到中国的 peer 质量本身就很差,有一定丢包量对 TCP 连接其实影响还挺大的,确实对 HTTPS 的影响会更大。不过也有类似于 TCP-BBR 的解决方案。但单针对 HTTPS 进行丢包的实在是很少见。
为啥境外 https 会严重丢包?而且 http 直接被 mitm 才是帮了墙一把吧。
RubyMotion 使用的关键就是。。。必须先通过 meta-programming 把这堆东西全部封装一遍,然后这世界就清净了。否则,真的辣眼睛。
这。。。明显不是一个 JSON 字符串,引号和键值对中间的冒号都是错的。
如果要把 Ruby Object 转换成 JSON,需要 JSON.generate(object)
或者 object.to_json
。直接传 object 会转成 String,但那个 String 并不是一个合法的 JSON 串。
2015 年來的時候還不是因為參加 RubyConf 來的,是來旅遊順道去台大聽了幾場演講,打了一場 Hackathon。當時知道 xdite 還不是從 Rubyist 口中得知,感覺其實當時已經有不少人都知道這些事了。
喷了,这个笔误有点太傻了。。。🤦♀️
...你需要先搞清 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 出这个结果也是算你牛逼的。
「让一个性能很好的 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 性能差别这么多,问题就出在这里,调用的方法不对。而不是什么别的。
我期待它能本身提供编译的功能,就是 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 所作的工作。
对客户端作用大一些吧,对于服务端做同步这种事情。。。你让服务器去做长时间等待异步同步,那是在消耗服务端资源。
你不让服务器等待异步同步,就要等待阻塞同步,或者线程上下文切换。你可以比较一下到底哪个消耗资源。
JRuby 主要是和 MRI 有诸多不兼容的地方。一方面没有引入类似 JNI 的机制做 C 扩展的引用,另一方面像是 Ruby 这几个版本非常重要的 Fiber 特性,一直不跟进。这对 JRuby 的推广有很大的障碍。但是反过来说,Ruby 社区为了保留灵活的演进,在几次劝进下都拒绝搞标准化。事实上最后搞了一个标准化 Ruby ISO,基于那个实现了 mruby,社区反映也是不温不火,两边都有苦难言啊。
是不矛盾,我就是补充一下我觉得就连 token 这个问题也是要打问号的。