作者:孟岩 来源:CSDN博客 酷勤网收集 2007-12-01
这是一个研究笔记,主要是为了向同好请教。除了这个开头以外,没有多余的废话,也就免了其他的客套。请大家不要抱怨可读性不好。
1. 在一个名字或者字符串前面加上冒号,得到一个symbol对象。还可以通过String#to_sym、Fixnum#to_sym和String#intern得到。
2. 一般用symbol做hash的key,号称是为了节省内存,提高执行效率。
3. 为什么可以节省内存?Ruby中的String是可变对象,这一点跟Java、C#、Python都不一样。注意跟某些C++标准库中的COW的basic_string<T>也不一样。Ruby中每一个String都可以就地改变。可能是因为这个原因,Ruby中两个内容相同的字符串文本量实际上是两个不同的对象。
a = "hello"
b = "hello"
虽然俩字符串内容都一样,但是你比一下a和b,就知道a.object_id != b.object_id,它们指向的不是同一个对象。结果反而很像未经string pooling优化的C语言的行为。到底immutable好还是mutable好,或者还是貌似聪明的COW好,见仁见智了。不过Ruby的设计在把字符串用作hash key的时候毛病就大了。比如你写:
h["ruby"].name = "Ruby"
h["ruby"].author = "matz"
h["ruby"].birth_year = 1995
的时候,"ruby"这个字符串动态生成了三次,占用三倍内存。这就严重地浪费了内存。而用:ruby做为key,因为在整个运行过程中,Ruby runtime保证名为:ruby的symbol对象只有一个,所以就不用生成三个,节省内存。
4. 为什么可以提高执行效率?显然的原因是免得多次动态生成'ruby'字符串了。还不单如此,Hash的key值应该是常量,所以Ruby的Hash对于作为key的String对象都要施加保护,所谓保护,也就是把String冻结了,免得你之后还改变其值。保护当然是有代价的,symbol无需保护,当然是能提高效率的。附带说明,其他mutable的对象也可以作为hash的key,这是Ruby设计得比较奇怪的地方。在irb里运行以下代码,你会发现Ruby的Hash丢值。
h = Hash.new
L = [1, 2]
h[L] = "A big object!"
L << 3 # 居然能改!
h[L] # ==> nil,找不到了,似乎正常
# 可是
h[[1, 2]] # ==> nil,居然还是找不到
# 看看keys
h.keys # ==> {[1, 2, 3]} 似乎还在里面
h[[1, 2, 3]] # ==> nil
# 可是
h # ==> {[1, 2, 3]=>'A big object'},明明在这里,就是找不到
h.rehash # ==> 这样就会一切恢复正常。
这一点上Python的设计要比较容易理解,list根本就是unhashable的,不能用来做hash的key。
回过头来在说提高效率的事。Symbol效率提高还有第三个原因,那是因为symbol本质上不比一个整数多出多少东西,用Symbol#to_i可以得到一个在整个程序中唯一的整数。Hash完全可以利用这个整数来产生hash值,那岂不是比根据字符串内容去算hash值快得多?这还是小意思,既然这个整数是唯一的,那么产生一个唯一的hash值也就是小菜一碟,要是能保证hash值唯一,那还是什么hash表,根本就变成数组了。Hash表还可能会冲突,数组根本不会冲突,百分之百保证O(1),当然快。我没看Ruby源码,不知道是不是这么处理的。
5. 为什么Ruby runtime可以保证每一个symbol唯一?因为Ruby把symbol存放在运行时维护的一个符号表里了,而这个符号表实际上是一个atom数据结构,其中存储着当前所有的程序级的name,确保不出现内容相同的多个对象。几乎每一个语言和系统都会有这样一个符号表,只不过象C/C++那样的语言,这个符号表只是在编译时存在,运行时就没了。而Python、Ruby则在运行时也保留这张表备用。有这样一个现成的数据结构干嘛不用?
6. 但是这个表中存放的并不光是我们自己主动生成的symbols,还有Ruby解释器对当前程序进行词法分析、语法分析后存在其中的、当前程序的所有名字。这可是Ruby引擎用的东西啊,我们只要加上一个冒号,就让自己的对象跟Ruby引擎内部使用的对象成邻居了。所以String#intern这个方法叫做intern(内部化)。
.NET Framework中String类也有一个Intern方法,意思是一样一样一样的,在李建忠的经典译本里翻译为“驻留”。
7. 可以用Symbol#all_symbols查看当前定义的全部symbol。可以体验一下自己往符号表中塞一个对象的感觉,想想你写的程序跟Ruby引擎能干一样的事情,应该还是挺爽的。
8. Python中用不着这个,因为字符串是immutable的。放下有用没用不说,有没有办法在Python中intern呢?我还没找到办法。有没有Python牛知道?
补充一下:查到了,Python中做这个事情的函数叫做 intern()。
9. 我觉得Ruby的这个设计是从Perl的glob中简化而来的。Perl中可以用*a得到对应于符号a的glob,那是一个八爪鱼一样的怪物。Ruby也可以很容易的得到symbol table中的对象,不过没有把symbol设计成八爪鱼。
10. 还有一些小问题没搞清楚,比如:name跟@name是什么关系。attr_reader :name,实际上是给attr_reader方法传了一个symbol作为参数,前者要通过这个symbol找到@name变量,是不是'@' + :name.id2name这么简单?大概可以去看看source了。
评论
蛋蛋 发表于2006-09-21 00:39:00 IP: 199.246.40.*| 孟老大猜错了哈。符号这个概念是Matz从Lisp里引进的。看看Common Lisp的代码,符号无处不在哈。:name和@name和name()的关系是由生成accessor的函数定的,当然不是‘◎’+:name.id2name那么简单。用attr_reader这个函数为例:你把:name这个符号传给attr_reader这个函数。attr_reader根据:name生成name()这个函数(当然真正的实现有很多具体考虑): def name() @name end 不信你可以写自己的attr_reader: class T def T.my_attr_reader(name) class_eval <<-READER def #{name} @#{name}; end READER end my_attr_reader :name def set_name(name) @name=name; end end t = T.new t.set_name('myan'); puts t.name 打印结果就是'myan'了。 再说了,attr_reader()不一定只接受符号哈: attr_reader :name attr_reader "name" attr_reader :name.to_s attr_reader "name".to_sym 都一样的。 思考题:写一个自己的attr_accessor()。 Symbol的意义不仅在于充当一个名字提高点性能,或者充当某个对象的标识符。Symbol对象代表代码解析树里的每个token。比如说def foo; "foo"; end。除了"foo",每个token, def, foo, end什么的都有对应的符号。换句话说,符号为我们提供了进入Ruby解析树的大门。不定哪天我们就可以利用符号直接操作部分解析的Ruby代码了。换句话说,我们可以超越各式eval, 进入人见人耐的macro世界了。慢慢等吧。哈哈哈! |
蛋蛋 发表于2006-09-21 00:49:00 IP: 199.246.40.*| P.S., 孟老大的学习还是比较仔细和全面的嘛。CSDN不少大嘴巴"专家"明明缺乏对编程语言的基本了解(比如开篇就来什么被解释的语言就是动态语言一类),却酷耐指点江山,臧否语言,实在应该好好学学孟老大的踏实哈。CSDN办个专题怎么样?普及一下编程语言的基本知识,比如语义的重要性(设计语言总得知道自己要解决什么问题吧?),常见的范式(太多老大言必OO,动不动就叫嚣我的xxx是最好的),类型(工业界和学术界的研究热点哈),常见语言特性(免得universee给closure取了个别名就开始吹嘘自己的2B语言了),常见设计思路什么的。像云风那样认真读过Programming Language Pragmatics这类教材的高手还是应该不少的。程序语言要从娃娃抓起啊。 |
myan 发表于2006-09-21 07:39:00 IP: 221.218.165.*| to 蛋蛋: 谢谢你的指教。这样看来,attr_reader之类的函数反而是主要接受字符串了。只不过"#{sym}"可以自动对sym调用to_s转型。 |
shhgs 发表于2006-09-22 08:27:00 IP: 74.116.184.*| 谢谢myan,看懂了。 不知为什么,Programming Ruby像是故意回避一样,对这个东西语焉不详。或许是为ruby藏拙吧。这个symbol纯粹是多出来的东西。如果String是immutable的,根本就没这么多麻烦。 |
肖海彤 发表于2006-09-22 23:09:00 IP: 59.42.124.*| ruby 的数据库性能访问如何? 当初用python 的时候, 就上过一个当, 网上看到 sqlobject 用得很多, 朋友也说好用, 于是一个 web 项目就用了, 用着用着就发现不对, 后台刷新的数据, 网页怎么也取不到, google, 分析source code 之后发现, 这个破烂 sqlobject 居然只适合于一个process 访问数据库, 如果还有其他process 访问数据库, 就必须关闭 cache, 关闭 cache 之后, 访问 ORM对象的任何一次字段, 就触发一个数据库访问, 完全没有办法接受. 还是一个 python 大牛写的代码, 真的不知道是怎么想的. 现在是用sqlalchemy 了. ruby 不会有这么弱智的问题吧? |
dennis 发表于2006-09-23 01:39:00 IP: 222.79.145.*| symbol,把解释器中的符号表暴露给程序员而已 |
myan 发表于2006-09-23 09:48:00 IP: 221.218.161.*| to redsea: SQLObject好在它的pythonic的风格上,我感觉它根本就不是给大型Web应用设计的东西。Anyway,毫无实践经验。 你说关闭Cache是SQLObject自己的Cache还是SQLObject后端的cache?这个问题是否可以想办法用memcached绕过? Ruby的ORM比较有名的有两三个,最火的当然是Active Record,还有Og。我研究太浅,回答不了你的问题。不过至少从表面上看,AR毕竟已经支持了上百万访问量的Web站点。你单说AR或者Og本身,它们不一定不弱智,不过如果全面考虑的话,可能这也不是什么大不了的问题。 |
SQLObject cache 发表于2006-09-23 11:35:00 IP: 59.42.124.*| 是 sqlobject 的cache. 这个项目不是给大型 web 应用用的, 总共就百来个客户, 不过每读一个字段执行一个SQL 语句也受不了。用memcached 就完全没有用 sql object 简单的意义了。 turbogears 的部分代码捐献者和很多用户也对SQLObject 不满,要求和提供代码转移到SqlAlchemy 上。 ok, 不再说离题的话了,说起来只是因为自己上过这个当,很不爽,所以想知道一下ruby 这方面如何。 |
beyking 发表于2006-09-23 16:26:00 IP: 59.107.13.*| 不知道為什么CSDN不多宣傳一些Python呢? 我感覺無論從那個方面來說,Python都強于Ruby; 而且Python社區相對來說也比較低調。不像Ruby只會Rails,就整天吵吵嚷嚷Rails;在Python下面Wed框架一抓一大把,跟Rails結構一樣的也一批 |
cloudzm 发表于2006-09-23 18:33:00 IP: 58.212.90.*| to shhgs: Ruby里的String是mutable的,所以需要Symbol来表示immutable String,这样可以提高效率。 String做成mutable的,有它的灵活性,比如 a = "Hello" a << " World" 这种写法比下面这种写法效率高: a = "Hello" a += " World" |
TripleX 发表于2006-09-24 02:45:00 IP: 61.51.70.*| to 肖海彤: 拜托说详细一点阿 你们用什么Web框架阿 怎么用会用出问题来呢? |
莫名其妙 发表于2006-09-24 15:15:00 IP: 220.189.223.*| to 肖海彤: 貌似不是sqlobject的问题。而且,turbogears迁移到sqlalchemy也和这类问题无关(就和java社区最后逐渐集中到hibernate一样,并不是说别的都是玩具)。如果真存在你说的这类问题,sqlobject根本就流行不起来(毕竟在sqlalchemy之前还是火过一段时间的) 而且,从另一个角度说,只要不是应用独占数据库,cache策略始终是一个重点. |
不清楚 发表于2006-09-24 15:16:00 IP: 220.189.223.*| to 肖海彤: 貌似不是sqlobject的问题。而且,turbogears迁移到sqlalchemy也和这类问题无关(就和java社区最后逐渐集中到hibernate一样,并不是说别的都是玩具)。如果真存在你说的这类问题,sqlobject根本就流行不起来(毕竟在sqlalchemy之前还是火过一段时间的),也不会被turbogears当作首选(Web环境显然不是单进程的) 而且,从另一个角度说,只要不是应用独占数据库,cache策略始终是一个重点. |
BUGONLINE 发表于2006-09-24 20:35:00 IP: 221.137.91.*| 学习了! http://bugonline.net 在线的BUG管理系统,无需安装,注册即可使用! |
robin 发表于2006-09-25 10:09:00 IP: 207.46.89.*| Intern String是很普遍的用法,各种语言都有相关实现。 intern string对效率的提高不是绝对的,因为它的构造成本比较高,不是常数级。 |
肖海彤 发表于2006-09-25 11:21:00 IP: 202.105.38.*| to TripleX 和 莫名其妙(不清楚) 我用 turbogears 做的. 哦, 我记错了一些, 不是多process 需要关闭 cache, 而是用到 transaction 需要关闭 --------------------- document 上 http://www.sqlobject.org/SQLObject.html Transactions Transaction support in SQLObject is left to the database. Transactions can be used like: conn = DBConnection.PostgresConnection('yada') trans = conn.transaction() p = Person.get(1, trans) p.firstName = 'Bob' trans.commit() p.firstName = 'Billy' trans.rollback() The trans object here is essentially a wrapper around a single database connection, and commit and rollback just pass that message to the low-level connection. If you want to begin a new transaction after a .rollback() call .begin(). If you want to use transactions you should also turn _cacheValues off, like: class Person(SQLObject): . _cacheValue = False # ... --------------------- 我本来以为, 关闭之后, 我读一个对象, 它会执行一个 sql 语句, 那也就罢了, 问题不大, 我将这个 orm 对象 copy 到另外一个对象中就 ok 了. 打开 connection 的log 结果看到是每个字段一个 sql 语句. 本来也认为sqlobject 很多人用, 应该没有问题, 没有进行这方面的性能测试就上了, 这下搞得麻烦了. 本来还想修改sql object 内部实现的, 但是 sql object 内部实现玩得太高深, 都是run time 动态修改对象的 property的, 玩的是 meta class 这个级别, 我对python 这方面不熟悉, 脑袋转不过来, 只要放弃了. 国内我有朋友用sql object 的, 但是只是单process, 无transaction 的应用, 他感到很好. 当时, 国外我也看到有人抱怨在他的应用里(和我差不多), sql object 性能很差. sql object 2.0 中似乎是要解决这个问题的, 但是 2.0 遥遥无期. 本来这个object cache 可以大大地减少数据库的负荷, 可以让数据 |
来自:http://blog.csdn.net/myan/archive/2006/09/20/1255966.aspx

