「Ruby有析构函数吗? 」一个问题引发的思考

Published on:

昨天,在QQ群里闲聊,有人扯出一个问题:Ruby有析构函数吗?

  • 我: 没有。
  • 他: C++里有啊。
  • 我: C++有GC吗?
  • 他: C++没有自带GC,但是有库。
  • 我: 那你用这个库吗?
  • 他: 不常用。。
  • 我: 那不就对了嘛, C++里原本没有实现GC,所以需要在对象死亡的时候用析构函数去释放空间,这样避免内存泄漏。但是Ruby里有GC的。
  • 他: 那为什么Python里也有析构函数?Python也有自己的GC呀。
  • 我: 。。。

我曾暂时无语。但是这个问题引起了我的兴趣,是啊,为什么Python有呢?

这个世界上任何事物的存在都有其合理性。 更何况,Python之父不会无缘无故的加上析构函数。 Python有GC, 对象在被GC回收的时候,执行析构函数。而Ruby也有GC,可是为什么没有呢? 难道是因为Python GC和Ruby GC的算法不同吗?

Ruby GC,每个Rubyist都知道, 从1.8时代的标记清除方式,到2.1的分代GC算法,GC的清除都不是实时性的,要预测一个对象什么时候释放,是比较困难的。 所以,只能是定时的去执行GC,或者设置阀值,大于xxMB就会执行一次GC, 在一些框架里,比如Goliath,默认会设置为每100个请求执行一次GC。

但是Python,是利用了引用计数算法的GC, 引用计数算法的一个优点,就是对象在引用数为0的时候,就马上就被释放。但是引用计数也有缺点,比如循环引用的问题,这会产生bug。所以,为了避免这种缺陷,Python也引入了分代回收算法,来检测有循环引用的对象,然后把这些对象的引用设置为0, 我们刚才说了,当引用数为0的时候, 对象会马上被释放。 所以,正是因为Python使用了引用计数算法,所以才能提供一个析构函数供程序员使用,在对象被清理的时候做一些工作。 当然这个跟C++还是有区别的,在Python里,使用了内存对象池技术,当你调用析构函数的时候,只是把对象归还到内存池中,而不是马上释放给系统内存。当然,Ruby同样也使用了内存池技术。

这个问题,至此,有了一个相对比较圆满的答案,解释了,为什么Python有析构函数,而Ruby没有。

问题还没有完

这个时候,另外一个人说了: Ruby是有析构函数的,不信你去看看ObjectSpace.define_finalizer方法。

ObjectSpace.define_finalizer说明:
Adds aProc as a finalizer, to be called after obj was destroyed.

也就是说, 这个方法会在对象被销毁以后执行一个指定的Proc。

比如这样用:

class Devil::Image
  attr_reader :name, :file

  def initialize(name, file)
    @name = name
    @file = file

    ObjectSpace.define_finalizer( self, proc { IL.DeleteImages(1, [name]) } )
  end
end

这段代码是一个开源gem:Devil里的,使用了ObjectSpace.define_finalizer,在image对象被回收的时候,删除掉相应的图片。

但是这里有个bug,会引起内存泄漏:

self,会被proc引用,所以,会导致image对象永远无法被回收,因为这个对象一直被proc所引用。
这个问题其实是proc闭包引起的问题。

所以,如果要使用它的话,一定要小心:

def initialize(name, file)
  @name = name
  @file = file

  ObjectSpace.define_finalizer( self, self.class.finalize(name) )
end

def self.finalize(name)
  proc { IL.DeleteImages(1, [name]) }
end

我们用类方法来避免上面所说的内存泄漏问题。 但是,ObjectSpace.define_finalizer是个出了名的难用的方法,「出了名难用」这个词,不是我说的,虽然我是刚知道这个方法的存在,但是在我查找一些资料的时候,看到别人说的。

严格意义来说,ObjectSpace.define_finalizer不是一个析构函数

我们回到传统的析构函数的行为来看, ObjectSpace.define_finalizer方法不算一个真正意义的析构函数。 且不说C++,拿Python来说,Python里的析构函数,不光是在对象被del的时候执行, 在对象跳出作用域外(object goes out of scope)也会被执行。 那么「啥叫对象跳出作用域外」?

看这个例子:

if true
   a = Array.new[1,2,3]
   b = a
end

上面的a,就是一个跳出作用域外的对象。 而对于这个对象a,只能等着GC回收了。而不会马上就调用ObjectSpace.define_finalizer。

Rails框架中的运用

Rails中,只在actionview/lib/action_view/template.rb 使用过这个方法。

# Among other things, this method is responsible for properly setting

# the encoding of the compiled template.

#

# If the template engine handles encodings, we send the encoded

# String to the engine without further processing. This allows

# the template engine to support additional mechanisms for

# specifying the encoding. For instance, ERB supports <%# encoding: %>

#

# Otherwise, after we figure out the correct encoding, we then

# encode the source into <tt>Encoding.default_internal</tt>.

# In general, this means that templates will be UTF-8 inside of Rails,

# regardless of the original source encoding.

def compile(mod) #:nodoc:

  encode!
  ...
  ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
end

...
  
# This finalizer is needed (and exactly with a proc inside another proc)

# otherwise templates leak in development.

Finalizer = proc do |method_name, mod| # :nodoc:

  proc do
    mod.module_eval do
      remove_possible_method method_name
    end
  end
end

用在这里主要是为了给模板进行编码,编码完毕之后,执行相应的代码防止在开发环境下templates内存泄漏。

仅此一处使用了ObjectSpace.define_finalizer。

总结

  1. Python之所以有完整的析构函数,是由其设计思想决定的,比如GC使用了引用计数算法。引用计数算法的优点就是当引用数为0的时候, 对象会马上被释放。
  2. Ruby里有析构函数: ObjectSpace.define_finalizer,但是这个析构函数在对象跳出作用域外是不会被执行的,只能等着GC来回收对象时候才能被执行。
  3. ObjectSpace.define_finalizer在使用的时候如果不小心,会引入一些bug,比如上面例子中的导致内存泄漏。 所以Rubyist一般不太常用这个方法。整个Rails框架中,仅有一处使用了这个方法。

「KSNCTF攻略」Easy Cipher

Published on:

昨天有幸得知一个网站:KSNCTF,是一个解谜过关的网站,但是你过关的时候,需要各种知识,包括简单的密码学、编程等,我目前只过了三关。你要想过关,就必须找到形如 FLAG_123456xyz 这样的过关钥匙, 这把钥匙就隐藏在你要过的关卡中。而第一关,就是个练习, 直接告诉你FLAG_123456xyz,让你输入过关就行。

本篇blog我们来说说 第二关:Easy Cipher


这是一道50分的题,题目如下:

EBG KVVV vf n fvzcyr yrggre fhofgvghgvba pvcure gung ercynprf n yrggre jvgu gur yrggre KVVV yrggref nsgre vg va gur nycunorg. EBG KVVV vf na rknzcyr bs gur Pnrfne pvcure, qrirybcrq va napvrag Ebzr. Synt vf SYNTFjmtkOWFNZdjkkNH. Vafreg na haqrefpber vzzrqvngryl nsgre SYNT.

这就是一堆乱码,再没有其他的说明。

再这堆乱码中想找出形如「FLAG_123456xyz」的钥匙字符串,显然没那么容易。 肉眼看过去,也就数「SYNTFjmtkOWFNZdjkkNH」这段字符比较像。 那么唯一的线索,就是想到,这可能是一段加过密的字符串。那么既然这里是让我们解题, 那么应该是这种对称加密方式,否则,此题无解,因为并没有任何其他的线索(包括查看网页源码寻找隐藏线索,一般的解迷过关游戏都会这样隐藏信息,否则没地方隐藏了)。 所以,接下来就应该思考,这段文字,是用什么样的对称加密方式呢?

在网上搜索了一下,对称加密方式有好几种, 最简单的一种是凯撒移位加密,还有其他的方式。但是除了凯撒移位的加密方法,其他的方式基本都是需要密文配合使用的,也就是所谓的密钥。 但是我们说了,本题再没有任何的线索,那么只有考虑凯撒移位加密方式,你才能把本题进行下去。

凯撒移位加密:

“恺撒密码”据传是古罗马恺撒大帝用来保护重要军情的加密系统。

它是一种置换密码,通过将字母按顺序推后起3位起到加密作用,如将字母A换作字母D,将字母B换作字母E。据说恺撒是率先使用加密函的古代将领之一,因此这种加密方法被称为恺撒密码。

假如有这样一条指令:
明文(小写):easy cipher
用恺撒密码加密后就成为(加入我们移动1位):
密文(小写):fbtz djqifs

那么接下来,我们就需要寻找, 题中给的密文,到底是移动了多少位? 反正就26个英文字母, 我唯一能用的就是暴力破解了:

选定一个单词,然后把每个字母从1-26都移一遍位,看看得出的单词,那个是比较通顺的。

def caesar_cipher(str)
  result = []
  1.upto(25) do  |i|
    arr = str.downcase.each_char.inject([]) do |s, c|   
      if (c.ord+i> 122) 
        s << (c.ord - (26 - i)).chr
      else
        s << (c.ord + i).chr
      end
      s
    end 
    result << arr.join
  end 
  result
end

上面这个方法(我没判断大小写,全部转为小写),用来暴力破解凯撒位移密码,我们输入那个觉得可疑的字符串:

caesar_cipher("SYNTFjmtkOWFNZdjkkNH")

#=>


["tzougknulpxgoaeklloi",
 "uapvhlovmqyhpbflmmpj",
 "vbqwimpwnrziqcgmnnqk",
 "wcrxjnqxosajrdhnoorl",
 "xdsykoryptbkseioppsm",
 "yetzlpszqucltfjpqqtn",
 "zfuamqtarvdmugkqrruo",
 "agvbnrubswenvhlrssvp",
 "bhwcosvctxfowimsttwq",
 "cixdptwduygpxjntuuxr",
 "djyequxevzhqykouvvys",
 "ekzfrvyfwairzlpvwwzt",
 "flagswzgxbjsamqwxxau",
 "gmbhtxahycktbnrxyybv",
 "hnciuybizdlucosyzzcw",
 "iodjvzcjaemvdptzaadx",
 "jpekwadkbfnwequabbey",
 "kqflxbelcgoxfrvbccfz",
 "lrgmycfmdhpygswcddga",
 "mshnzdgneiqzhtxdeehb",
 "ntioaehofjraiuyeffic",
 "oujpbfipgksbjvzfggjd",
 "pvkqcgjqhltckwaghhke",
 "qwlrdhkrimudlxbhiilf",
 "rxmseilsjnvemycijjmg"]

然后我们看看结果, 发现第10个转换后的字符"flagswzgxbjsamqwxxau"很可疑, 因为有flag前缀。那么我们把这个字符按照KSNCTF网站的过关规则,结合密文中的大写小写位置,变化一下这个字符串:

"SYNTFjmtkOWFNZdjkkNH" # 原来的字符

"flagswzgxbjsamqwxxau" # 破解出来的可疑字串

"FLAGSwzgxBJSAMqwxxAU"  # 大小写比对,肉眼变换

"FLAG_SwzgxBJSAMqwxxAU" # 在FLAG后面加下划线


经过上面几步,我们得到了一个过关钥匙串,把他输入到我们的题目中:

Congratulation! Flag FLAG_SwzgxBJSAMqwxxAU is correct.

恭喜你! 过关了。


请期待下一篇: 「KSNCTF攻略」Crawling Chaos

所谓正则表达式的技巧

Published on:
Tags: 正则 ruby

今天下午,看到一道正则的题目:最少包含数字和字母, 只能包含._-@这四个特殊字符,其他的字符不允许使用。

我一看, 这不就是某些网站在输入密码时候为了让用户选择安全强度比较高的密码而做的过滤吗? 还比上面那个题目上多了一个条件: 密码最少要求8位,最大32位。

 第一次想简单了:

/\d+|[a-zA-Z]+|[_.@-]+/

这个是没有考虑到题目中「最少包含数字和字母」这个条件, 这个条件的意思是:字符中最少的组合是数字+字符,也就是说数字和字符必须同时存在,单有数字或单有字母,是不能通过验证的。

否定匹配

这样的话就有点难度了。 正则表达式中, 没有直接表示「且」这种同时存在的概念。想表达「且」,有一个办法就是使用否定匹配(?!),比如:

"abcdsss".match(/^(?!abcd).*?$/)
=> nil
"acbcdsss".match(/^(?!abcd).*?$/)
=> #<MatchData "acbcdsss">

但是用在这个题目上,还不太适合,因为你需要找出一个否定条件:「除数字和字母之外的任意字符都不匹配」。 除数字和字母之外的任意字符,太多了, 而且,这个条件也把题目中允许的那四个特殊字符给否定了。

所以这种方法排除。

环视

经过思考, 我确定了一种方法,是可以判断这种情况的, 那就是判断: 「数字旁边是字母, 字母旁边是数字, 字母和数字之间可能有特殊字符,包括允许和不允许的」。 那么,只有通过「环视」功能,才可以做出以上的判断,于是得出如下正则表达式:

# 数字旁边是字母,字母旁边是数字

/[0-9a-zA-Z]*(?<=[a-zA-Z])[0-9a-zA-Z]*(?=[0-9])[a-zA-Z0-9]*/

"1234fsfsfd123123sdfs".match(/[0-9a-zA-Z]*(?<=[a-zA-Z])[a-zA-Z]*(?=[0-9])[a-zA-Z0-9]*/)
=> #<MatchData "234fsfsfd123123sdfs">


"111".match(/[0-9a-zA-Z]*(?<=[a-zA-Z])[a-zA-Z]*(?=[0-9])[a-zA-Z0-9]*/)
=> nil

"abcd".match(/[0-9a-zA-Z]*(?<=[a-zA-Z])[a-zA-Z]*(?=[0-9])[a-zA-Z0-9]*/)
=> nil

看起来靠谱, 思路应该是没错了,可是。。。 这只符合数字和字母的组合, 数字和字母之间还可能有任意的特殊符合,包括允许的和不允许的。最终,沿着这个思路,我得出了下面这段很长很笨的正则:

"sfdsf_@ssf11_@11".match(/(?<must>[0-9a-zA-Z]*(?<spec>[@_.-]*)(?<=[0-9])[0-9a-zA-Z]*(?<spec>[@_.-]*)(?<=[@_-])[0-9a-zA-Z]*(?<spec>[@_.-]*)(?=[a-z])[0-9a-zA-Z]*(?<spec>[@_.-]*))|(?<must>[0-9a-zA-Z]*(?<spec>[@_.-]*)(?<=[a-z])[0-9a-zA-Z]*(?<spec>[@_.-]*)(?<=[@_-])[0-9a-zA-Z]*(?<spec>[@_.-]*)(?=[0-9])[0-9a-zA-Z@_-]*(?<spec>[@_.-]*))/)

=> #<MatchData

 "sfdsf_@ssf11_@11"
 must:nil
 spec:nil
 spec:nil
 spec:nil
 spec:nil
 must:"sfdsf_@ssf11_@11"
 spec:""
 spec:"_@"
 spec:"_@"
 spec:"">

我使用了命名捕获组,must代表必须匹配, spec代表允许匹配的特殊字符。但是这个答案还不是最终的,因为它不完美,它不能满足所有的字符组合。

一定还有更简单的方法。

奇技淫巧

最终是看了一位正则高手给出的答案(也许有更简单的):

(?=^.*?[0-9])(?=^.*?[A-Za-z])^[0-9a-zA-Z_.@\-]{8,32}$

这个非常简洁,思路跟我那个笨办法是一样的,采用了环视,通过位置的判断达到数字与字母「且」的组合, 其中简单的技巧在于:

^.*?

这四个常用的正则元字符, 单个的意思,大家都知道, 但是组合在一起,非常强大。

"ssfds".match(/^.*/)
=> #<MatchData "ssfds">


"ssfds".match(/^.*?/)
=> #<MatchData "">

我们都知道.*的组合是非常强大的, 匹配任意字符, 但是加了问号「?」, 就不一样了。

? (问号),有两层意思:

  1. 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。
  2. 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。也就是懒惰模式。

所以, (^.*?)这个技巧,就是使用了「懒惰模式」, 意思是「匹配任意字符0次,也就是忽视任意字符」。 回到我们的正则表达式中:

(?=^.*?[0-9]) # 正向环视 ,匹配一个右边包含数字(忽视数字之前的任意字符)的位置

(?=^.*?[A-Za-z]) #正向环视, 匹配一个右边包含大小写字母(忽视字母之前的任意字符)的位置

^[0-9a-zA-Z_.@\-]{8,32}$ # 在匹配到上面的位置之后, 再匹配位置右边是否存在8-32位长的包含数字、字母、四位被允许的特殊字符组合。

这就是这个表达式的含义。 (.*?) 这样的用法,估计很多人知道,您看完之后,也许感觉很简单, 但是如果看了我上面的笨办法之后,你应该可以感受得到,对于不经常写正则表达式的人来说,这样的技巧太重要了,也非常值得我们学习。

这就是所谓的技巧,也许你早已知道,但你却不知道何时何地如何使用。

最后再推荐一个在线调试正则的工具:https://www.debuggex.com/

使用Gitbook来编写你的Api文档

Published on:

Gitbook是一个很优秀的社区,上面有很多优秀的作者自出版自己的著作,就好像Leanpub,可能很多人喜欢Leanpub,但是我还是喜欢Gitbook,这种类似于Github的原创社区。同时Gitbook还提供一个开源的配套的工具。也许看到此文章的很多人很早就知道Gitbook,但是也许你没有使用过,现在Gitbook已经比较成熟了,功能也比较完善。下面我们首先来介绍下Gitbook的使用。

Gitbook的使用

当你使用Gitbook的时候,新建一个项目的时候,会弹出下面选项,共四种类型的模板提供给你选择,实际上他们没什么区别,只是一个Markdown模板:

我们选择了第一项,当然,初次尝试的朋友,可以都选择看一看不同的Markdown模板。

如图所示, Gitbook Editor,实际上就是一个特殊的Markdown编辑器。我创建了一个test项目作为示例,你也可以自己创建一本新书,然后打开源目录,会看到如下文件:

  • _book 文件夹
  • SUMMARY.md
  • README.md

SUMMARY.md 这个文件就是书的目录结构。Gitbook Editor对中文支持不太好,有时候你用Editor创建了一个章节,用中文命名,但是当你点击那个新建的章节的时候,会报错,解决办法就是用你自己的编辑器打开这个文件,直接编辑这个文件就好了。

具体Editor如何上手,就不详细说了,相信你会用Markdown编辑器就会用Editor。

当你创建了一本书之后,可以通过「Book->Publish As...」功能来把你的书发布到Gitbook,但是前提是你必须要在Gitbook网站上面也相应创建好这本书。

你也可以通过使用Gitbook的帮助,使用Git来创建并上传你的书:

touch README.md SUMMARY.md
git init
git add README.md SUMMARY.md
git commit -m "first commit"
git remote add gitbook https://push.gitbook.io/blackanger/test.git
git push -u gitbook master
…or push an existing repository from the command line

git remote add gitbook https://push.gitbook.io/blackanger/test.git
git push -u gitbook master

你也可以在本地使用Editor的Preview Website功能,在本地_books目录中生成静态网页,也就是书的Web版本。 早先的Gitbook Editor版本可以直接在本地生成epub、pdf、mobi格式的文件,但是最新版本把这些功能去掉了。

使用Gitbook写你自己的Api文档

Gitbook写自己的书很方便,本人前段时间也发布了一本免费书籍《Chef之道》。其实你用Gitbook不只是可以写书,也可以来写Api文档,我一直用Gitbook写Api文档,我总结了几个优点:

  • Gitbook可以免费创建私有库,保密性比较高。
  • 类似于Github,有版本控制。就是一个电子书版的Github。
  • Gitbook Editor是一个很好用的Markdown编辑器,有很多贴心的快捷键让你发掘,比如cmd+shift+d,如果你用习惯Atom、Sublime、Textmate之类的编辑器,会很喜欢这些特性。当然此类快捷键也不是很多,但是相信以后Editor功能会更加丰富,因为我刚才说的这个特性应该也是新加的。起码比Logdown这个Markdown编辑器好用多了。
  • 一次编写,多处使用。接下来我们重点说这个,也就是我今天重点要说的。

一次编写,多处使用。

现在是移动互联网时代,很多App已经开发在维护,还有很多很多的App待开发,而且HTML5、js mvc框架的发展,有很多人都在维护Api接口。那么写一个可维护、可读性高、带版本控制、可随心所欲分发的接口文档是多么重要。

可读性/ 可维护 / 版本控制

Gitbook是用Markdown写的,还支持语法高亮等,用它写出来的文档,那看起来是相当愉悦的。

Gitbook正是天生带版本控制,你可以选择任意一个你发布过的版本。

可随心所欲分发

  1. 文档写好以后,你可以把Gitbook源目录下面的所有文件都复制到你项目下(app_root/docs/api/gitbook_api_dir)。这样,你的项目就多了一份漂亮的文档,开发人员还可以在本地打开Web Preview生成在_book目录下的静态网页愉悦的看你的Api接口文档。 如果觉得复制太土了,你可以直接把Gitbook Editor的Api文档目录创建在项目中。

  2. 后台接口项目、Android App项目、iOS App项目都可以分发一份,大家可以使用Gitbook Editor来协同管理接口。

  3. 上传到Github,也可以在线修改阅读你的文档,因为Github也支持Markdown。

是不是非常方便?


Update: 就在我写完本文之后,我就发现gitbook增加了付费计划, 免费的私有项目只允许创建一个。

「书籍纠错」Ruby Under a Microscope

Published on:

最近看了 Pat Shaughnessy的「Ruby Under a Microscope: An Illustrated Guide to Ruby Internals」一书,发现书中epub版P335,Note的一句话说的比较困扰:

Ruby implements extend in exactly the same way, except the included class becomes the superclass of the target class’s class, or metaclass. Thus, extend allows you to add class methods to a class.”

上面这段话中,描述了extend的内部实现行为的时候,使用了 the superclass of the target class’s class 这样的描述, 百思不得其解,因为Ruby中任何类的类都是Class,于是想,这肯定是作者写错了。

我给Pat发了封邮件,描述了我的困扰,并且声明做了如下修改:

[ Ruby implements extend in exactly the same way, except the included class becomes the superclass of the target class’s metaclass. Thus, extend allows you to add class methods to a class. ]

并附上了代码:

class A; end
a = A.new
module B
  def t
     puts 11
  end
end

a.extend B
A.extend B

A.singleton_class.ancestors  #=>   [ #<Class:A> , B, #<Class:Object>,  #<Class:BasicObject>,  Class, Module, Object, Kernel, BasicObject ]

a.singleton_class.ancestors  #=> [#<Class:#<A:0x007ffad2238668>>, B, A, Object,  Kernel, BasicObject ] 


今天得到了Pat的回复:

Hi Alex,

Thanks so much for the feedback - it's great to hear you like the book :)

Yes you're absolutely right. My choice of words (class's class) was confusing. Your version is simpler and easier to understand. Both are technically equivalent and correct I think.

pat


特此记录,给那些如果看此书有同样困扰的朋友。

发布了一本书「Chef之道」

Published on:
Tags: chef ruby

今天,在Gitbook发布了一本书,叫「Chef之道」。 本书的初衷因为一次对平安科技的企业培训所作。本书适合想了解Chef的Rubyist, 更适合使用Chef但苦于对Ruby没有学习之法的运维人员。

这本书压了两个月时间,直到今天才发布,是因为本不想发布的,正好今天有个朋友问我关于Chef的问题,我就想,先发布吧,虽然细节不够,但是胜在比较系统。

后续我也会持续更新,但是时间有限,会更新比较慢。

希望对大家有所帮助。

IDE、编辑器、编程语言哪家强?

Published on:

程序员的世界,永远都会出现如标题所示的问题:

  • 编程语言哪家强?
  • IDE、编辑器哪家强?
  • 数据库哪家强?

等等, xxx哪家强。今天我找到一个完美答案,来回答这些问题:

青衣人道:“你总该知道, 这「青魔手」乃是伊哭采金铁之英,淬以百毒,锻冶了七年才制成的,可说是武林中最霸道的兵刃之一。”
李寻欢道:“百晓生作「兵器谱」, 青魔手排名第九,可算珍品。”
青衣人道:“那么,我若将这青魔手送给你,你肯不肯将金丝甲让给我?”
李寻欢沉默半晌,望着手里的小刀,缓缓道:“我这把小刀只不过是大冶的铁匠,花了三个时辰打好的,但百晓生品评天下兵器,小李飞刀却排名第三。”
青衣人长叹一口气,道:“你的意思是说,兵器的好坏并没有关系,主要的是看用兵器的是什么人。”
李寻欢微微笑道:“阁下是聪明人”

-------------- 古龙《多情剑客无情剑》

您是聪明人吗?

「Linux基础」nice命令探秘

Published on:
Tags: Linux nice ruby

今天打开Ruby5扫了一眼,看到一篇文章:「Ruby & Rails: Making sure rake task won’t slow the site down」,里面谈到了使用nice命令优化rake任务,更详细的可以点进去看看。

这个命令我以前知道,以前订阅rss,某篇文章里提过,但是我只是知道而已,并没有去消化那些内容。 RSS就是有这种缺点,信息轰炸,你可以获取很多消息,眼花缭乱,但是你真正消化的能有多少? 今天还和别人在讨论rss,突然感觉,自从Google Reader停掉以后,我就没这个订阅rss的习惯了,仔细想来,我现在的习惯是自己去寻找需要的资源,而不是被动的获取。我觉得现在的方式很好,跟随自己的求知欲以及学习计划和结构,主动去寻找学习资源,不至于分散精力。这个nice命令就是这样,看过又能咋样,今天遇见还不是不了解? 不过今天的方式不一样,是我主动去寻找的资源,那我就一定得了解它的用法。

nice与进程优先级

nice命令可以改变进程的优先级。

语法:

nice [-n adjustment] [-adjustment] [--adjustment=adjustment] [--help] [--version] [command [arg...]]

说明:

  • -n adjustment, -adjustment, --adjustment=adjustment 皆为将该原有优先序的增加 adjustment
  • --help 显示求助讯息
  • --version 显示版本资讯

示例:设置程序运行时的优先级

# vi &    //后台运行

[1] 15297

# nice vi & //设置默认优先级

[2] 15298
[1]+ Stopped         vi

# nice -n 19 vi & //设置优先级为19

[3] 15299
[2]+ Stopped         nice vi

# nice -n -20 vi & //设置优先级为 -20

[4] 15300
[3]+ Stopped         nice -n 19 vi

// ls 的优先序加 1 并执行

# nice -n 1 ls


// ls 的优先序加 10 并执行
# nice ls

那什么叫进程的优先级呢?

进程cpu资源分配就是指进程的优先级(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。

进程观察

使用下面命令,我们可以观察到系统进程的优先级:

# ps -l //显示进程

F S  UID  PID PPID C PRI NI ADDR SZ WCHAN TTY     TIME CMD
4 S   0 15278 15212 0 80  0 - 1208 wait  pts/2  00:00:00 bash
0 T   0 15297 15278 0 80  0 - 2687 signal pts/2  00:00:00 vi
0 T   0 15298 15278 0 90 10 - 2687 signal pts/2  00:00:00 vi
0 T   0 15299 15278 1 99 19 - 2687 signal pts/2  00:00:00 vi
4 T   0 15300 15278 3 60 -20 - 2687 signal pts/2  00:00:00 vi
4 R   0 15301 15278 0 80  0 -  625 -   pts/2  00:00:00 ps

[4]+ Stopped         nice -n -20 vi

# ps -lA //显示全部进程

...

上面有几个重要概念需要说明:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

跟我们今天的主题相关的,是后面这两个信息:PRI和NI。

  • PRI: 是指进程的优先级(priority), 是指进程被CPU执行的先后顺序,此值越小,那么进程的优先级越高。
  • NI: 是指nice的值,这个值表示的是进程可被执行的优先级的修正数值,注意,nice命令只是用来设置NI的数值,NI的数值一旦改变,就会影响到PRI。

计算公式如下:

PRI(new)=PRI(old)+nice

可以看得出来, nice的值,越小,PRI的值就越小, 进程优先级越高,则越快被执行。

nice的值,也就是NI,可以指定的范围为[-20, 19],其中-20最高,19最低,值得注意的是,只有系统管理者(root)可以设置负数的等级。

应用

这个命令的应用,就是用来优化系统。比如文章开头说的那篇文章中,用nice命令来调整rake后台的优先级:

RAILS_ENV=:environment nice -n 19 bundle exec rake :your_task 

在执行rake命令的时候,带上nice参数,指定了NI值为19, 意思是告诉CPU,这个rake 任务优先级最低,你们先忙别的吧,忙完别的再执行我。

比如我的App后台,我会把队列任务的优先级调低, 这样后台执行任务的时候,就不会跟其他API接口抢占CPU资源。 不过这个后台任务是不那么紧急的。如果你的后台任务很紧急,那么你可以调高优先级。

值得注意的是:

nice命令,只用于启动新的进程,才起作用。

renice命令

如果你想调整已经存在的进程的优先级,那么可以使用 renice命令:

renice -5 -p 5200 #PID为5200的进程nice设为-5

语法:

renice [优先等级][-g <程序群组名称>...][-p <程序识别码>...][-u <用户名称>...]

补充说明:

renice指令可重新调整程序执行的优先权等级。预设是以程序识别码指定程序调整其优先权,您亦可以指定程序群组或用户名称调整优先权等级, 并修改所有隶属于该程序群组或用户的程序的优先权。等级范围[-20,19],只有系统管理者可以改变其他用户程序的优先权,也仅有系统管理者可以设置负数等级。更nice命令类似。

参数说明:

  • -g <程序群组名称>  使用程序群组名称,修改所有隶属于该程序群组的程序的优先权。
  • -p <程序识别码>  改变该程序的优先权等级,此参数为预设值。
  • -u <用户名称>  指定用户名称,修改所有隶属于该用户的程序的优先权。

也可以用top命令更改已存在进程的nice:

$ top

#进入top后按“r”–>输入进程PID–>输入nice值

结语

虽然我们了解了这一技巧,但是建议使用的时候,还要比较谨慎,要结合你的应用具体情况来优化。

零基础怎么学XX

Published on:

我喜欢逛知乎,所以经常会看到一些类似于标题的问题:

「零基础怎么学编程」
「零基础怎么学围棋」
「零基础怎么学吉他」
「零基础怎么学英语」
「零基础怎么学素描」
「零基础怎么学魔术」
「零基础怎么学唱歌」
「零基础怎么学拍短片」
「零基础怎么学烧菜」
「零基础怎么学医」
「零基础怎么学黑客」
「零基础怎么学车」
「零基础怎么学dota」
「零基础怎么学dota2」

更有甚者:
「零基础,如何成为一个球迷?」
「零基础,学XXX, 可能吗?」


面对这些问题,好无语。 你们的学习能力哪去了?

「CodeWars」Matrix

Published on:
Tags: ruby CodeWars

本文为「CodeWars」6ku级别的题: Matrix

我在QQ群:222359552 里发起一个每周不定时活动:答题送视频优惠码, 这是我自己的解答。。


关于题的描述

Write a function that accepts two square matrices (nxn two dimensional arrays), and return the sum of the two. Both matrices being passed into the function will be of size nxn (square), containing only integers.

How to sum two matrices:
Take each cell [n][m] from the first matrix, and add it with the [n][m] cell from the second matrix.> This will be cell [n][m] in the solution matrix.

Visualization:
|1 2 3|
|3 2 1|
|1 1 1|
+
|2 2 1|
|3 2 3|
|1 1 3|

result =:

|3 4 4|
|6 4 4|
|2 2 4|

Example function call:
matrixAddition( [ [1, 2, 3], [3, 2, 1,], [1, 1, 1] ], [ [2, 2, 1], [3, 2, 3], [1, 1, 3] ] );
returns [ [3, 4, 4], [6, 4, 4], [2, 2, 4] ]

本题是给出一个矩阵,让你计算矩阵的和。

背景知识

矩阵(Matrix),是数学中最重要的基本概念之一,关于其具体的数学概念及其应用,可以去查维基百科

在Ruby中,可以用一个二维数组来表示矩阵。比如:

[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]

而Ruby的标准库中,也实现了一个Matrix类,来帮助你方便的操作矩阵。

而对于这次CodeWars的题,我自己实现了一个简单的矩阵类。

自己实现的矩阵类Matrix

class Matrix
  class CantCreateMatrixError < ::StandardError; end

  attr_accessor :matrix

  def initialize(*arr)
    begin
      arr.each do |a|
        raise CantCreateMatrixError, "Does not meet specifications" if a.size != arr[0].size
      end
      @matrix = arr
    end
  end

  def self.[](*arr)
    new(*arr)
  end

  def +(other)
    other.matrix.each_index.inject([]) do |arr, index|
      matrix_row = self.row(index)
      arr << matrix_row.size.times.inject([]) do |new_row, i|
        new_row << matrix_row[i] + other.row(index)[i]
      end
    end
  end

  def cloumn(index=0)
    matrix.map{|arr| arr[index]}
  end

  def row(index=0)
    matrix[index]
  end

  def cloumns
    s = []
    matrix[0].size.times do |index|
      s << cloumn(index)
    end
    s
  end
end

用法:

# 创建矩阵

matrix1 = Matrix[
                 [1, 2, 3], 
                 [3, 2, 1], 
                 [1, 1, 1]
                ]
matrix2 = Matrix[
                 [2, 2, 1],
                 [3, 2, 3], 
                 [1, 1, 3]
                ]
                
                
# 如果你创建的矩阵不符合规范,则会报错

error_matrix = Matrix[
                        [1, 2],
                        [3, 4, 5],
                        [1, 3, 5, 6]
              ]
#=> Matrix::CantCreateMatrixError: Does not meet specifications



# 获取矩阵行数组

matrix1.row(0)
matrix1.row(1)
matrix1.row(2)

# 获取矩阵列数组

matrix2.cloumn(0)
matrix2.cloumn(1)
matrix2.cloumn(2)

# 按列获取矩阵,返回数组

matrix2.cloumns

# 求两个矩阵之和

matrix1 + matrix2



以上代码可以扩展各种操作, 比如乘除减等。 当然这段代码还有很多改进空间。

至此,Codewars的题就迎刃而解了:

def matrixAddition(a, b)
  Matrix[*a] + Matrix[*b]
end

Test.assert_equals(matrixAddition([[1, 2], [1, 2]], [[2, 3], [2, 3]]),[[3, 5], [3, 5]]);
#=> true

其他解法

Codewars上面很多其他解法,利用了Ruby提供的内建方法,也值得一看:

def matrixAddition(a, b)
  a.zip(b).map { |r| r.transpose.map { |c| c.reduce(:+) } }
end

利用了内建的Array#transpose方法。

def matrixAddition(a, b)
  new_array = []  
  a.each_with_index do |array, index|
    new_array << a[index].zip(b[index]).map{|pair| pair.reduce(&:+)}
  end
  new_array
end
def matrixAddition(a, b)
  (0...a.length).map do |i|
    (0...a[0].length).map do |j|
      a[i][j] + b[i][j]
    end
  end
end
def matrixAddition(a, b)
  b = b.flatten
  sum = a.flatten.map.with_index {|v,i| v + b[i] }.each_slice(a.length).to_a  
end

还有很多, 大体思路都差不多,像我那样写一个类,还是比较少的,大家的目的不同。

不知道你们的解题思路是哪一种呢?