都知道 ruby 的性能是弱项,但心里总觉得 ruby 一直在进步,差距会越来越小的。
今天为了试验 ractor,写了一个非常简单的斐波那契数列求和:
def fib(n)
return n if n == 0 || n == 1
fib(n-1) + fib(n-2)
end
一台小破笔记本,ruby2.7,单线程跑 fib(40),14 秒。开 jit 后 5 秒(之前以为开不开 jit 一样,后来发现是 rails 的问题,代码是放在 rails 里跑的)。
当时没觉得有啥,因为没有对比就没有伤害。后来顺便用同机的 java11 跑了下——1 秒不到。
我从 ruby2.3 用到现在,这是第一次对 ruby 感到绝望。
难道每一个 ruby 项目的结局,都是用 java 重写吗?
试了一下 lz 的代码,在我的电脑上,打开 jit 8 秒左右,不开 jit 14 秒。
然后找了一段 Python 递归实现的斐波那契数列计算:
def fib(n):
if n==1 or n==2:
return 1
return fib(n-1)+fib(n-2)
在 Python 2.7 下,执行时间是 23 秒。
我感觉这个例子并不能说明语言的性能,因为是递归实现的,只能看出不同语言对函数的处理。这种场景,编译型语言有优势,因为编译时候,编译器可以进行代码优化,把递归的调用给优化掉,所以会快。
我乱说的哈
给楼主另一个参考,我的环境在 wsl1 里,ruby2.7,没啥 jit 之类的,跑了 8s;python 3.9,跑了 28s,跑 python 的时候慢的我一度怀疑写的代码是不是死循环了
计算型任务应该用 C 扩展。另外应该只优化 cpu 瓶颈。性能就没那么差了。
ps:可以参考网上一些 bench 数据
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/yarv-python3.html https://github.com/kostya/benchmarks
不要过早优化,在实际项目中,可以找到最费时间的地方优化。
工作中一个 C++ 项目,语言够快没得说吧,用了 Vector,发现删除操作非常慢,原来,每删除一个的元素,后面的元素都往要前挪一位。这个操作太费时了。因为排序不重要,就把要删除的元素和最后一位调换,然后删除最后一位,性能提升 1000 倍。
语言是有快有慢,在我们的工作中,真能碰到天花板的机会不多,到时再优化就可以了。真要到用 Java 重写的那一天,我估计你也财务自由了,是别人干这个脏活了。
所以面试考算法是有道理的,这个方法慢主要是因为写的有问题。
def fib(n, cache = {0 => 0, 1 => 1})
return n if cache[n]
cache[n - 1] ||= fib(n - 1, cache)
cache[n - 2] ||= fib(n - 2, cache)
cache[n - 1] + cache[n - 2]
end
java 可能对烂代码有优化,如果很需要这种优化就选 java 吧。
应该是 Martin Fowler 的企业应用架构模式
里提到过,语言的分层选择。
对于底层的代码,由于后期变动少,对性能要求高,建议用中高级语言,如 C,java 这些。
对于近业务层的代码,建议使用语言表达更丰富,开发效率更高的语言,如 Ruby 之类。
这是我看了所有关于语言讨论里最认可的。
动态语言肯定比静态语言慢了,Ruby 的比较对象应该是 Python、PHP、JavaScript。
性能也不是技术选型的唯一标准,要不然都用 C 好了,Java 就没必要诞生。
唠叨一下。
设计数据密集型应用的作者,把开发开发分为这样几个方面
可维护性,可扩展性,可用性。
可扩展性,也分为横向和纵向。纵向是单机的性能能到多少。那么,语言性能影响的是纵向的扩展性。
设计应用,如果能横向扩展,几乎可以说是赢了。而横向扩展跟语言性能没什么关系。
再来看下我们的环境,首先是硬件越来越便宜,cpu 单核性能已经不那么容易增加了,得靠多核提升性能。
性能瓶颈多数是在存储这。我们拿 Facebook 的 memcache 集群为例,有篇论文是介绍他们怎么玩的。他们的优化都在处理横向扩展,如何应对突发流量,如何减少网络请求之类的。单机的优化很少。分布式里,似乎都不是很在意单机的能力。
再比如 discord 用 rust 替换了一个 erlang 的 orderedsort 实现,性能翻了多少多少。先不说没找到具体的测试用例、机器情况。他们的实现,erlang 的是遍历,rust 的是跳表。当数据量比较大的时候,算法的重要性远大于语言。顺便一提,erlang 有 ets,sort 是平衡树,c 实现,并发性非常好。
有的时候,看清楚问题是什么,当前的情况是什么,更重要。
问题到底是快速迭代还是极致性能(高性能,多数的时候大家还是用 c++,相对来说 java 差不少,外加加上 gc)。如何利用现有资源,比如分布式,大内存,算法。
语言不过是工具盒里的工具,性能不过是这些工具的一个属性。
多谢,感谢指教!但是,想必您也能看出来,我这样写其实是就是为了跑 benchmark,故意让它的运算次数增加,可以跑得时间长点。否则按您优化后的算法,用任何语言跑都是几毫秒,用作性能对比不太直观。就好比两个人比赛爬楼梯,本来就是为了比体力,结果您在旁边说了一句“干嘛不坐电梯?”……
当然,优化算法往往对性能的提升是最大的,好代码与烂代码之间的性能差距何止千、万倍,这是用任何其它优化手段都做不到的。但是,现实中存在很多复杂计算,算法优化的空间有限,或者即使用了已知理论上最快的算法、用户体验上仍然无法接受,这就不得不想办法在其它方面(包括语言层面)压榨性能了。
至于 java 跑得快是不是因为对烂代码有优化,我对编译器知之甚少,不敢评价;不过,可能暂时持保留意见,原因有二:1、优化过的算法(好代码)只有几 ms,而烂代码 java11 实测 1 秒左右。以 java 编译器的功力,既然做了优化,为何不干脆优化到底?2、烂代码.net 4.8 实测 5s,.net 5.0 实测 2s,明显 5.0 的优化更好,但仍输于 java。而根据网上评测,.net5 的大部分算法 benchmark 小胜 java。如果这种烂代码很容易被优化,为何 2s 离 java 的 1s 仍有不小的差距?
您说的没错,Ruby 是众多编程语言“爬楼梯”吊车尾的,这点毋庸置疑。如果需要处理“爬楼梯”场景,java 也可以略过,直接上 c c++ rust。
绝大部分应用处理的是“坐电梯”的事情,语言之间的差距是“走进电梯里的那一小步”,即使有差距也并不明显,主要看电梯调教的怎么样。
Ruby 慢这种事没什么好争的,但是“应用慢是因为 Ruby”这样的结论大部分情况是不客观的。
不意外。
柱状图高度也直接表现了语言的舒适性。
省功不省力,省力就不省功。
快的东西,写起来就复杂,人牵就机器。 写起来舒服的东西,本质内部就复杂相对就慢,相当于机器牵就、服务了人。
挺有趣的例子,我也知道 Ruby 慢,所以一般遇到瓶颈,必然还是要准备一下直接上 C 语言的,花了一小时,写了我的第一个 C 扩展 gem fibc。
Fibc.fib_pure(4) # processing time: 0.000034s
Fibc.fib(4) # processing time: 0.000030s
Fibc.fib_pure(40) # processing time: 15.568888s
Fibc.fib(40) # processing time: 0.511246s