· 简单还是复杂?
· 狮身人面像的逆袭,圆周率=5.1961524227...
· 傻子才搞游行示威
· 我曾经也用Google Code,直到我的膝盖中了一箭……
2012-5-13 12:34:03 阅读27 评论0 132012/05 May13
《跑跑龟》是个很简单的桌游。基本上接触过桌游的人都玩过吧。看上去《跑跑龟》没啥策略性,输赢也就看运气而已。这么想你就错了,跑跑龟其实是个浅度策略游戏,如果你按以下方式玩,胜率可以达到80%。(注:本文是按“同时到终点时上面的乌龟赢”的规则来写的)
2012-4-25 20:16:54 阅读51 评论0 252012/04 Apr25
作者:Martin Odersky
译者:杨博
最近我们在激烈讨论,有的说Scala对正常程序员来说很复杂,有的说Scala其实是一门编码很简单的语言。有两个帖子在讨论(http://michid.wordpr
哪边更简单?
造哪个更简单?显然是前者。
哪边更简单?
左边的摩比世界的形状种类要比右边的乐高多,所以你可能觉得乐高简单些。
哪边更简单?
左边的乐高得宝系列显然比右边的乐高科技系列简单,没错吧?
没错,但如果给你个任务让你造一个比较特别的东西,你用哪个系列?可能用得宝系列你也能造出来,但一定比用科技系列别扭。更有可能你需要用到得宝系列中根本没有的积木(比如绳状或胶状积木)。
现在你该猜中了 :-) Scala更像科技系列而非得宝系列。相比别的语言,Scala能引导我们更简洁的解决问题。Scala还可以进行依赖注入(http://jonasboner.co
显然,这意味着Scala每行代码干的活更多,所以你会提出Scala比那些代码冗长的语言更难理解。但有实验证明(http://infoscience.e
我希望我能让你相信,“简单还是复杂”的辩论本身就不简单。复杂度是个多维的矢量。在一场讨论中,水友们笔下的“复杂度”往往是指复杂度不同维度上的分量。
2012-3-3 23:54:16 阅读78 评论1 32012/03 Mar3
我想知道Java程序打日志有多少种方式配置,于是画了一个图:
尼玛Java真是太灵活了!!!!!!!
2012-2-29 8:24:23 阅读268 评论5 292012/02 Feb29
有C++经验的人往往知道,频繁在堆上分配对象对性能伤害很大。比如这样的代码就不是好代码:
void foo(int[] a) {
WrappedArray* wrappedArray = new WrappedArray(a);
wrappedArray->bar();
delete wrappedArray;
}然而在JVM(指HotSpot虚拟机)上完全不是这样的。尽管JVM不提供显式在栈上创建对象的功能,但JVM会自动通过逃逸分析找出生命周期在函数范围内的临时对象,然后运行时优化这个函数,把对象创建放到栈上。逃逸分析在JRE7里面已经默认开启了,对临时对象多的代码效果很大。
Scala的隐式类型转换特性很鼓励创建临时对象。标准库中就有大量的隐式转换,会生成许多临时对象,如WrappedArray、StringAdd、WithFilter等。我读标准库的源码时甚至感到他们已经把创建临时对象当成一种优化手段了。
比如C++常见的pimpl模式往往会让用户代码持有一个包装对象的指针,包装对象的内部有一个底层实现对象的指针。这种做法在Scala里面就完全不被主张,而更鼓励长期持有底层对象,而在需要用时再临时包装成对外接口。
Scala的Array对应Java中的数组,功能很少,但Array可以隐式转换为功能全面的ArrayOps,使用ArrayOps上的函数时会生成多个临时对象。试看如下代码:
val myArray = Array("foo", "bar", "baz")
for (element <- myArray if element.contains('a')) { println(element) } Scala编译器会把上述代码转换成这样:
val myArray: Array[String] = Array.apply("foo", "bar", "baz")
new (new ArrayOps[String](myArray)).WithFilter(
new AbstractFunction1[String, Boolean] {
override def apply(element: String): Boolean = {
return new StringOps(element).contains('a')
}
}).foreach(new AbstractFunction1[String, Unit] {
override def apply(element: String) {
println(element)
}
}) 颤抖吧骚年,这一小段代码中居然有五处new,创建了ArrayOps[String]、ArrayOps[String]#WithFilter、AbstractFunction1[String, Boolean]、StringOps、AbstractFunction1[String, Unit]整整五个临时对象。
Scala宁愿创建这么多临时对象也不用pimp模式是因为pimpl模式比直接持有底层对象多保存一个包装对象。如果在容器中保存大量包装对象时,额外的内存开销就会多起来,而pimp模式还会导致每次函数调用也多一次间接寻址的开销。另一方面,只持有底层对象的话,虽然原则上每次使用都会生成临时对象,但是这些临时对象经过JVM的逃逸分析、标量替换和内联优化后是零开销的。
但临时对象的开销取决于JVM的优化,然而JVM要在运行时优化,而且只有频繁运行的代码才会被优化。所以如果一个程序创建很多临时对象,那么刚启动时它的性能会很差,而运行一段时间后会慢慢变好。这也是为什么我们经常会感觉Java程序启动慢的原因之一。
2012-2-5 13:35:56 阅读140 评论2 52012/02 Feb5
我小学时有一篇课文说讲了个故事,大意是
我觉得这是胡扯嘛,同样是生命生长的力量,女人裹小脚时怎么没听说过撑破裹脚布的?
再说,按课文的意思,头盖骨如此坚硬,生命生长的力量如此大,那么可以推论,头盖骨生长的力量应该是世界上最大的力量了吧。于是印第安人就用自己的头做实验,用木板夹一夹,看看世界上最大的力量能否顶穿木板。
后来他们发现了真相:顶不穿的……
为了纪念他们为科学献身的精神,他们被称为扁头族(Flathead)。
2012-1-30 22:39:42 阅读262 评论2 302012/01 Jan30
2012-1-27 22:06:38 阅读163 评论0 272012/01 Jan27
刚看了第五季第14集生活大爆炸,上面提到海地和列支敦斯登参加1936年奥运会的时候,发现两国国旗相同。
于是,为了避免冲突,他们回国以后就开始着手把各自的国徽加到国旗上。
列支敦斯登人画了一个网页,在CSS中设置background-image为原来的国旗,然后找了个PNG格式的列支敦斯登国徽素材,做了个<img>标签把国徽放了进去。最后用谷歌浏览器打开这个网页,按Ctrl+P把网页打印出来,就去参加下一届奥运会了。
海地人也画了一个网页,在CSS中设置background-image为原来的国旗,然后也找了个PNG格式的海地国徽素材,做了个<img>标签把国徽放了进去。最后用Internet Explorer 6打开这个网页,按Ctrl+P把网页打印出来,就去参加下一届奥运会了。
后来他们又在下一届奥运会中碰面了。以下是他们的新国旗:
| 列支敦斯登的新国旗 | 海地的新国旗 |
|---|
列支敦斯登看到海地的国旗就笑了:“小样儿,用IE6的吧?”
海地:“靠!这你都知道?”
列支敦斯登两手一摊:“用IE6也就罢了,连科比都不知道就敢参加奥运会?”
2012-1-26 6:48:08 阅读616 评论0 262012/01 Jan26
在即将迎来我参与的第一个专业店铺 —— 一家开在21楼的桌游吧 —— 开业一周年时,我还只是个刚下海的新手。那就像坐过山车一样令人兴奋。内容全部整合好了,而且游戏看起来很棒。只有一个问题:卫生间使用量超出了预计。
因为大部分卫生间都是被xu-xu和poo-poo用掉的,所以我们和店长一起删减零食供应以节省xu-xu次数。我们缩小零食份量,大量删除饮料,压缩咖啡浓度。有些做法店长可以接受,有些就只能推倒店长才得以实行了。
我们删了一个又一个,经过几天艰苦的努力,我们抵达了某种临界 —— 再也没有什么可以删的了。除非我们砍掉游戏的一些主体内容,不然再没有任何办法可以削减卫生间使用量了。我们精疲力尽地统计了当前的卫生间用量,发现还是多出150人次!
这时,我们这里最有经验的合伙人之一 —— 一个曾在“美好的久远年代”混迹开发领域数年的前辈 —— 决定接手这件事。他把我叫进他的办公室,我心想又要来一场劳心伤神的卫生间争夺战了吧。
可是,他只是走到一个包厢然后指着其中一堵墙,上面写着:
I am the bone of my sword.
Steel is my body, and fire is my blood.
I have created over a thousand blades.
Unknown to death, nor known to life.
Have withstood pain to create many weapons.
Yet, those hands will never hold anything.
So as I pray,
Unlimited Blade Works...
说,“看到了吗?”然后轻轻一按,推开了这面墙。搞定!
也许是察觉到我眼中的震撼,他向我解释说他在装修的早期阶段封印了这个卫生间。经验告诉他删减xu-xu到预计卫生间之内总是非常重要的,很多桌游吧因此几近失败。所以现在,作为一种常规做法,他总是留一个小卫生间,到真正需要的时候再释放它。
他走出办公室,然后宣布他已经把卫生间使用量减至上限之内,后来他被看作是这个桌游吧的英雄。
尽管被这种野蛮的做法“震惊了”,我还是不得不承认我对他的做法很感兴趣。我还没想到在哪里可以用上这招,但我知道了在有些时候,当你面对障碍时,为了应对困境而事先藏起一些卫生间真的非常有意义。时间和经验带来的改变可真是有趣。
向写程序的黑暗英雄致敬。
2012-1-21 1:18:48 阅读91 评论0 212012/01 Jan21
今天看了看韩寒和方舟子的论战,这不就是囚徒困境吗?
想象一下可能发生的各种情况:
| 韩寒沉默 | 韩寒开骂 | |||||||||||||
| 方舟子沉默 |
|
| ||||||||||||
| 方舟子开骂 |
|
|
在网络论战这种囚徒困境中,韩寒和方舟子两人的最优决策都是对抗到底,最终必定导致两败俱伤的结果。
2011-12-24 1:42:50 阅读186 评论0 242011/12 Dec24
近来发生了一件悲剧。某村村民因为一些合法诉求搞了两次游行示威,游行过程中难免有点暴力行为。官府事后算账,拘留了几个村民,其中一个村民被拘留死了。
如果理解游行示威的意义,这样的悲剧就不会发生了。 游行示威的意义并非向外界展示力量。若要向外界展示力量的话,集体签字或拍集体照就够了。 游行示威的意义在于让参与者产生激情,给参与者洗脑,提高参与者的凝聚力。
然而,游行示威并非洗脑的最佳做法。如果有合法的集体诉求,最佳手段是办法律讲座。法律讲座要低调的办。地点应选择很大的室内场地。人数是多多益善,把能通知到的人都叫上。
法律讲座的要点是讲诉求涉及的相关法条和诉讼流程,引导观众齐声朗读相关条文,就像疯狂英语那样干。 在中华人民共和国境内朗读中华人民共和国的法律,总不能说是颠覆国家安全的行为吧。最好一开场就全场朗读三遍《刑法》第一百零五条第二款,给参与者壮胆。 除此之外。还要讲类似诉求的成功案例(在案例汇编的书上找,书店里面多的是)。讲案例是为了鼓励观众,让观众觉得中国是讲法律的,自己站在法律有利的一方,给观众信心。
做法律讲座之前必须备好课,规划好兴趣曲线。还要事先私下通知少量核心成员当托,以在讲座时调动场上气氛。
讲座结束前,观众整整齐齐排一个方阵,拉出诉求的横幅,拍张照。一定要排整齐有气势,不要搞行为艺术,也不要学毕业照那种摆法。散会后,大家把学到知识用上,遵守法律走法律流程。拍好的照片需要精心设计,配合煽情的文案,写成一篇催人泪下图文并茂的文章,然后放到网上,发动集体疯狂转载。如果在网上流传得当,会给当局一些社会压力,迫使他们遵守法律不耍流氓。
法律讲座既合法,又安全,还提高了集体凝聚力,社会影响力也并不差,比游行示威强多了。连搞传销的人都懂得这个道理,天天办讲座。只有比信传销的傻子还蠢的人才游行示威,还瓜兮兮的跑去公安局登记。
2011-12-12 8:23:05 阅读272 评论0 122011/12 Dec12
github有Fork功能,Google Code一样有Clone功能。但是github上的项目比Google Code上的项目社区更活跃,Google Code上大家普遍懒得自己Clone、Hack并贡献补丁。为什么?
这是因为二者的界面设计不同。
github一个项目的首页内容主要就三样:仓库地址、源代码目录、README(内容通常是编译指南),你要是不去点右边大大的Fork按钮你都觉得不好意思。
再比较一下导航条:
看到没有,github功能的顺序基本上和Google Code是颠倒的。至于Downloads这种让用户不劳而获的栏目,github才不要有呢。
2011-12-7 21:35:34 阅读279 评论5 72011/12 Dec7
2011-11-23 4:14:42 阅读377 评论3 232011/11 Nov23
用Java打log时,最不爽的一点就是即使你把log禁用了,只要打log的那行代码还在,每次执行到那一行还是有开销!尼玛Java没有宏啊,不能像C++那样做个编译选项不输出log时就直接跳过嘛。况且就算Java有宏,搞Java那帮道貌暗忍的人渣们也不会用的,他们只用XML和Annotation 来配置,感受不到编译选项带来的爱。Scala就有个-Xelide-below的编译选项可以排除掉不需要的函数调用,直接被搞Lift的混蛋们无视了,照样用着天杀的slf4j.
那么用slf4j打log到底有多大开销呢?这取决于slf4j的后端,不管用什么后端,真正输出log是需要IO的,一般都要几十上百微妙,也就是几万或者几十万纳秒,这部分开销是省不了的。我刚才抱怨的是那些被过滤掉的log,这些log的开销根据参数的复杂程度,要消耗从几个纳秒到上百个纳秒不等。
纳秒什么的最讨厌了,上百个纳秒,听起来好多!我一个纳秒都不想浪费!于是我就写了个pico-logger,pico-logger对org.slf4j.Logger包装了一下,但用pico-logger比直接用org.slf4j.Logger快得多。
如果一条log没被过滤掉,需要实际IO输出,用pico-logger和用slf4j一样快。但是slf4j打一条复杂log时,即使被过滤掉了,也需要消耗上百纳秒,而pico-logger过滤掉log时一纳秒都不浪费。无论多复杂的log,pico-logger在JVM优化后执行时间都不超过2个时钟周期,在我2.1GHz的CPU上只要952皮秒,一纳秒都不到。如果传给pico-logger的参数是没有额外变量的字符串字面量,执行时间为零。零执行时间的意思是你在任何深层循环输出log,只要一禁用掉,都对性能完全没有影响。
以下是在Scala中PicoLogger的用法:
object Foo extends PicoLogger(org.slf4j.LoggerFactory.getLogger(classOf[Foo]))
{
def foo() {
// 用PicoLogger,当trace级别被禁用时,零执行时间
trace("string literal")
// 用org.slf4j.Logger,当trace级别被禁用时,消耗4纳秒左右。不同后端性能稍有差异。
logger.trace("string literal")
}
def bar(p1: Int, p2: Double, p3: Long, p4: Byte, p5: Boolean) {
// 用PicoLogger,当trace级别被禁用时,2个时钟周期,在2GHz以上的CPU时不到1纳秒
trace("p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + ", p4=" + p4 + ", p5=" + p5)
// 用org.slf4j.Logger,当trace级别被禁用时,消耗约100纳秒
logger.trace("p1={},p2={},p3={},p4={},p5={}", Array(p1, p2, p3, p4, p5))
}
}
顺便做了个benchmark,对比了一下slf4j和pico-logger的性能。在下列表格中,每一项的单位是时钟周期,即以java -server方式运行,并经过充分优化后执行需要消耗的CPU时钟周期。在我2.1GHz的CPU上,每个时钟周期是0.476纳秒,即476皮秒。与slf4j中用格式化字符串的代码对应的pico-logger用例是拼接字符串。
| 字符串字面量 | 字符串格式+2个字符串参数 | 字符串格式+2个整数参数 | 字符串格式+5个各种参数 | |
| slf4j+log4j | 15 | 16 | 41+ | 186+ |
| slf4j+logback | 9 | 9 | 39+ | 191+ |
| slf4j+java.util.logging | 6 | 7 | 34+ | 187+ |
| pico-logger | 0 | 2 | 2 | 2 |
slf4j整数参数会变慢,是因为slf4j需要为非引用的参数装箱。每次装箱就要消耗大约20个时钟周期,因为装箱涉及内存分配,所以每次装箱消耗的时钟周期数不等。上述表格中的数字都是取最好的结果,实际上平均成绩还会比这个最好成绩消耗更多的CPU时间。字符串字面量下面的“0”表示如果输出的这条log会被过滤掉,那么哪怕连写10遍,JVM优化后都是直接跳过这些代码的压根不作任何判断。
代码我放Google Code上了:http://pico-logger.googlecode.com/,一共只有一个类,性能评测的代码也在里面。2011-11-5 4:29:51 阅读199 评论0 52011/11 Nov5
2011-10-24 18:49:07 阅读143 评论1 242011/10 Oct24