Ruby Ruby 2.3 中的魔法注释 # frozen_string_literal: true

charleszhang · 2018年03月12日 · 最后由 wakasa 回复于 2018年04月03日 · 2777 次阅读
本帖已被设为精华帖!

背景

Ruby中冻结的对象只会创建一次,以后遇到相同的对象会复用之前创建的对象,这样可以减少对象创建次数和垃圾回收次数。Ruby中的符号、整数、浮点数默认都是冻结的,字符串字面量目前还不是。

为了提高程序性能,在Ruby 3中,字符串字面量在所有文件中默认被冻结。

为了过渡,Ruby2.3增加了一个魔法注释:

# frozen_string_literal: true

作用

它告诉Ruby,文件中的所有字符串字面量都被隐式冻结,不可修改,就像每一个字符串都调用了freeze方法一样。

# frozen_string_literal: true
s = "string"
puts s.frozen?      => true
s << "12"           => can't modify frozen String (RuntimeError)

使用方式

  • 把该注释加在文件的第一行。

  • 另外,在Ruby 2.3中使用--enable=frozen-string-literal标志运行ruby,也会默认冻结所有文件中的字符串字面量。在单个文件中可以通过# frozen_string_literal: false覆盖全局设置(Ruby 3中也可以用此方式覆盖全局设置)。

    ruby --enable=frozen-string-literal t.rb
    

如何修改

如果想要修改字符串字面量怎么办?

无论全局或每个文件如何设置,可以使用一元+运算符(注意优先级)来产生非冻结字符串或调用dup方法来对其进行复制

# frozen_string_literal: true
"".frozen?
=> true
(+"").frozen?
=> false
"".dup.frozen?
=> false

还可以使用一元-运算符冻结可变(未冻结)字符串。

(-"").frozen?
=> true
共收到 13 条回复
huacnlee 将本帖设为了精华贴 03月13日 01:11

最后两个方式很Hack。 😀

Sidekiq作者的博客里面也提到了这个特性: https://www.mikeperham.com/2018/02/28/ruby-optimization-with-one-magic-comment/

在sidekiq里面实际测试之后,作者总结说:

frozen_string_literal reduces the generated garbage by ~100MB or ~20%! Free performance by adding a one line comment.

为了性能,增加语言复杂度。以后支持jit 编译后,这个多出来的语法又要删除吧?

sevk 回复

支持 jit 就更容易 inline 了啊,快了又快。

sevk 回复

看个人选择吧?觉得值就值。 用了 ruby 也不见得一点不优化。

如何在 Rails 全局启用该功能的帖子。试了一下,不要说第三方 gem,首先 action_view 就报错了(Ruby: 2.3.4,Rails:5.1.5)。当然这只是想在 Rails 中全局使用的情况。

我的理解就是 Mutable & Immutable Variable,LLVM Style,Rubinius 口味,语义解析中的常量折叠,Java 中的 final。

To Think(遗留问题):如果 frozen 不被 GC,那么问题可大了,内存占用问题。

jakit 回复

现在的 symbol 都能被 gc,frozen 和 gc 没有必然联系。

Rei 回复

既然能被 GC 则不是 static,然后又 final,那就是相当于“不可改变的动态创建对象”,作为默认值属性用途还是不错的

能解释下,为什么freeze了能提高性能?

hiveer 回复

Ruby中冻结的对象只会创建一次,以后遇到相同的对象会复用之前创建的对象,减少了对象创建次数和垃圾回收次数。Ruby中的符号、整数、浮点数默认都是冻结的。我补充一下原文😀

截止到现在,主要gem包对全局开启(--enable=frozen-string-literal)的支持依然不是特别好。

建议还是使用魔法评论局部启动。可以使用rubocop帮助自动添加这个评论。以后deprecated后还能自动删除。:)

对性能的提高主要跟GC的实现有关。如果不严谨地总结的话,对象这玩意,当然是创建得越少越好啦!尤其是字符串这种,代码逻辑里到处都在用的数据类型。不仅省内存,还省了对分配内存内核API的调用。万一有多线程,使用这种字符串的话,也不用考虑竞态条件了,因为这对象都不变的。

如果学习过C的话,估计能更加体会到冻结字符串的好处。 :)C的数据结构里有一个叫做原子(Atom)的玩意。根据字符串内容唯一对应一块内存的话。单凭一次指针(内存地址)这样的整型比较,就能完成任意长度字符串的比较了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册