Project1

标题: 关于RPG Maker全系列Ruby脚本执行效率求解 [打印本页]

作者: 熊猫    时间: 2011-12-2 20:40
标题: 关于RPG Maker全系列Ruby脚本执行效率求解
本帖最后由 熊猫 于 2011-12-2 20:40 编辑

据说VX使用了更新的Ruby版本,比XP的执行效率好得多。可是……
近来做了一个脚本性能的小测试,只针对一个方面。
首先代码是:
  1. a = Time.now
  2. e = ""
  3. f = ""
  4. for i in 0...10000000
  5. e = "aaa"
  6. f = e
  7. e = "bbb"
  8. end
  9. b = Time.now

  10. p b - a
复制代码
参赛选手:
  1. .NET 4.0
  2. RPG Maker XP
  3. RPG Maker VX
  4. RPG Maker VX Ace
复制代码
成绩嘛:
  1. .NET
  2. 0.04s
  3. Ruby:
  4. XP 3.8s
  5. VX 6.5s
  6. ACE 9.5s
复制代码
咱CPU渣,跑得慢了点……不过从时间对比上来看,结论很明显啊。
单纯字符操作一千万+1次循环来看,Ruby完败.NET,这个倒不是出乎意料。
我在意的是……XP VX VXACE一代比一代慢,而且灰常明显……

不管RGSS引擎,就Ruby来讲……版本肯定是新了的,为什么慢了呢?
VX比起XP新的Ruby脚本解释引擎到底优秀在哪里?
dsu_plus_rewardpost_czw
作者: 越前リョーマ    时间: 2011-12-2 20:48
VXms没有使用新版本的ruby只是改了rgss……
作者: 各种压力的猫君    时间: 2011-12-2 22:16
本帖最后由 各种压力的猫君 于 2011-12-2 22:35 编辑

XP 1.02(事件):2.364
XP 1.02(脚本):1.884

XP 1.03(事件):2.343
XP 1.03(脚本):1.561

VX 1.02(事件):3.867
VX 1.02(脚本):3.921

VX Ace 1.00 trail(事件):3.982
VX Ace 1.00 trail(脚本):2.538

.Net 未作测试

测试平台:
i7 2600k
Windows 7 x64

以上测试结果均测试5次去掉最高、最低值后取算术平均值。
“事件”指事件指令中的脚本输入测试代码,
“脚本”指在脚本编辑器最顶端添加TEST脚本输入测试代码。

VX Ace 脚本测试结果略优于 VX,但是VX和VX Ace都完败给XP……这是为何?!

拿RGSS和.Net比效率的话……不具可比性啊……
Ruby是靠虚拟机运行的,.Net最后总说还有个编译器不是。
作者: DeathKing    时间: 2011-12-2 22:32
你还真不了解Ruby啊……
作者: bbaugle    时间: 2011-12-2 22:47
我猜是ruby先是给C编译再给机器运行。所以ruby的速度是慢不奇怪。
作者: 各种压力的猫君    时间: 2011-12-2 22:52
本帖最后由 各种压力的猫君 于 2011-12-2 22:53 编辑



平均值是去掉5次的最高值和最低值之后做算术平均得出的。
作者: 熊猫    时间: 2011-12-2 23:38
各种压力的猫君 发表于 2011-12-2 22:52
平均值是去掉5次的最高值和最低值之后做算术平均得出的。

嗯,如果猫君在各个RM中测试的代码是一样的话,是不是可以得出这个结论:
单纯循环操作字符来看RMXP比较强。
近似总体的操作(猫君的线性插值测试,具体我也不知道是神马~)来看Ace倒是一匹黑马。
但是RMVX两项测试都处于最后。
所以RMVX脚本性能优于RMXP不成立。ACE仍有待考证。
作者: R-零    时间: 2011-12-3 07:46
貌似只是位图的处理效率比较高
作者: ⑨姐姐    时间: 2011-12-3 11:06
builder.c
"
do sth
"
貌似可以这样用0 0?
作者: 第七水螰    时间: 2011-12-3 13:43
Ruby 1.9 加入了 Enumerator(枚舉器)的功能,默認情況下調用 #each 會在每次迭代過程中也同時進行枚舉器的處理,這是 1.8 沒有的開銷。你把這個 for 循環換成 while 應該就能看到速度的提升了(這裡的 for 循環調用的是 Range#each)。當然,這段代碼最終也不會和 1.8 有太大差距,因為這段代碼測的只不過是 Ruby 的堆和 GC 性能罷了,而在這兩個方面 Ruby 1.9 並沒有多大改進。

有興趣的話可以看 1.9 源 range.c 中有關 Range#each 的部分:
  1. static VALUE
  2. range_each(VALUE range)
  3. {
  4.     VALUE beg, end;

  5.     RETURN_ENUMERATOR(range, 0, 0);

  6.     beg = RANGE_BEG(range);
  7.     end = RANGE_END(range);
  8.     ...
  9. }
复制代码
其中 RETURN_ENUMERATOR 就是額外的開銷。

VX比起XP新的Ruby脚本解释引擎到底优秀在哪里?

VX 和 XP 用的 Ruby 版本是相同的,都是 1.8.1。VX Ace 升級到了 1.9.2,這個可以在幫助菜單裏查到,或是自行在腳本中打印 RUBY_VERSION 這個常量的值。

@各种压力的猫君
Ruby是靠虚拟机运行的,.Net最后总说还有个编译器不是。

Ruby 1.9 之前的實現是純粹的抽象語法樹求值器,也就是純解釋性實現,直到 1.9 才有了虛擬機 YARV。.NET 在微軟平臺下的虛擬機是 CLR,但它和 YARV 面向的語言完全不同,YARV 主要是服務 Smalltalk 式(類 Smalltalk 面向對象模型)高度動態語言的,而 CLR 的模型更類似於 JVM。前者在執行效率上很難超過後者,但後者在動態性能上(如各種元編程能力)也別想超過前者。


作者: 退屈£无聊    时间: 2011-12-3 14:23
XP :17.405
VX :46.437
外带一提,XP不用10S直接被分
作者: 熊猫    时间: 2011-12-4 22:13
第七水螰 发表于 2011-12-3 13:43
Ruby 1.9 加入了 Enumerator(枚舉器)的功能,默認情況下調用 #each 會在每次迭代過程中也同時進行枚舉器 ...
VX 和 XP 用的 Ruby 版本是相同的,都是 1.8.1。VX Ace 升級到了 1.9.2

嗯……VX没有额外的压力还处理得那么慢,果然没有优势。应该很快会被Ace取代吧。


其实,既然是说道了.NET我就多说一句。
.NET应该是对代码有优化的,微软这方面做得比较到位,否则执行10000000次字符处理根本不能是0.04s。

感谢这位仁兄回答,我的问题得到了解决并且学到了很多。
作者: 第七水螰    时间: 2011-12-5 01:46
熊猫 发表于 2011-12-4 22:13
嗯……VX没有额外的压力还处理得那么慢,果然没有优势。应该很快会被Ace取代吧。

VX 是怎麼回事還沒想明白。

虛擬機的工作之一就是提供(編譯時、運行時的)代碼優化,這一點無論是 CLR 還是 YARV 都一樣,只是限於語言動態的天性,能達到的優化程度不同而已。就比如這段代碼,如果放到一個不是特別動態的語言中,由於用到的字符串都是常量,編譯器在編譯時就能靜態決定最終三個引用的值,在编译时就可以把有循環優化成沒有循環。然而在 Ruby 中,用戶可以利用猴子補丁隨時重定義 Range#each 這樣的方法,那麼看似是基本語言結構的 for 循環也可以在每次迭代時做一些用戶自定義的事情了,如此一來編譯器就不能確定是否可以移除循環 [1]。這些動態的信息很難被編譯器用靜態的方式分析,所以現在的動態語言虛擬機主要是靠 JIT。主樓這段 Ruby 代碼其實也沒多少「字符串處理」,整個程序的生命週期內做的就三件事:不斷的通過 "..." 字面值形式生成新的字符串對象(這是給 Ruby 堆施加壓力),不斷的把對象賦給某些局部引用變量(這是給虛擬機的內核以及棧施加壓力,但相比於其他開銷來說已是很小),回收失去引用的字符串对象(這是給 Ruby 的 GC 施加壓力)。在 .NET 中如果想測試類似的東西,那就也得讓每次迭代都生成新的對象才行,否則的話使用的字符串就有可能被字符串常量池給緩存了(也就是 string interning)。比如在 C# 中:

  1.             string s1 = "test";
  2.             string s2 = "test";
  3.             string s3 = new StringBuilder("test").ToString();
  4.             Console.WriteLine((Object)s1 == (Object)s2);        // True
  5.             Console.WriteLine((Object)s2 == (Object)s3);        // False
复制代码
可見每當使用字符串字面值時都動用了字串池進行緩存,所以在循環中賦值的時候用 StringBuilder 和字符串常量的開銷是有很大差別的。

[1] 比如,如果重定義 Range#each 為每次迭代輸出一個 1,那麼無論在任何場合下用 for i in range 都會在 for 循環原本執行的內容之餘輸出 range 長度那麼多個 1,而如果把循環移除後這個行為就被抹殺了。




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1