Random Tech Thoughts

The title above is not random

各种 Spinlock 实现的性能测试

Update on 2012-12-26: 加入了跟 pthread mutex 的比较,伸缩性胜过其他 spinlock。用户态程序想要有更好的伸缩性关键还是在设计时避免共享,同步原语无特殊需要还是直接使用现成的吧。更多介绍请参考我的另一篇博文

Update on 2013-03-22: 解决问题的关键还是在避免竞争,让锁变得可伸缩只不过可以晚一点对程序中引起竞争的部分进行改写。

Update on 2014-02-14: 基于 cmpxchg 的 spinlock 性能差的原因是我实现的问题。在尝试拿锁失败之后应该等待锁被释放之后再尝试拿锁,而现在的实现会马上再次尝试拿锁。由于 cmpxchg 是写操作,每次执行都要申请独占 cache line,因此多个核竞争时会产生大量的 cache traffic,导致锁的伸缩性非常差。改成等待锁释放之后再尝试拿锁之后 cmpxchg 的性能就与 xchg 的相似了。

之前自己写的程序遇到了伸缩性问题,怀疑是自己实现的锁在使用的核数增加时会产生严重的竞争,所以对这篇 Spinlocks and Read-Write Locks 里介绍的各种 spinlock 测试了下性能。(当然,根本的办法是避免在锁上竞争,比较 spinlock 实现的性能更多是出于好奇。)

各种 spinlock 的实现和测试程序都在 github 上。(除了 cmpxchg 以外都是 copy 那篇文章里的。)为了使用方便,spinlock 所需要的原子指令等都在各自的头文件里。

首先要说明的是,下面的测试结果以及结论依赖于具体的 CPU。选择 spinlock 的实现的时候还是要自己测试一下

解决 Markdown 转 HTML 中文换行变空格的问题

Update on 2012-04-24: 看到肖之的 给中英文间加个空格 提到了 Jekyll 的 plugin 机制,因此改成用 plugin 机制避免更新 Jekyll 时需要重新 patch 的麻烦。


在 Vim 里编辑文件时我设置行宽不超过 80 个字符,因此编辑 Markdown 文件时一段话中会有多个换行。我用 Octopress 默认配置的 rdiscount 来生成 HTML 文件,这些换行都会保留在最后生成的 HTML 文件中。

浏览器对换行的处理是转成空格,这对英文来说很自然,但对中文就很讨厌了。我在 Stack Overflow 上提了这个问题,从这个详细的回答来看,这是一个历史遗留问题,短期内不会解决。

所以还是自己动手吧。我的解决方法是在生成 HTML 前把所有连续的中文行转成一行。(修改 Markdown 的实现也可以,但 rdiscount 用了 C 写的 Markdown 实现 discount,hack 起来比较麻烦。) 在 Ruby 1.9 中,连接中文行可以很方便的用正则表达式实现,注意开头的 #encoding: UTF-8 是必须的。

1
2
3
4
5
6
7
8
9
#encoding: UTF-8

class String
  han = '\p{Han}|[,。?;:‘’“”、!……()]'
  ChineseRegex = /(#{han}) *\n *(#{han})/m
  def join_chinese
    gsub(ChineseRegex, '\1\2')
  end
end

利用 Jekyll 的插件机制可以在它调用 markdown 转换工具前修改需要转换的文本。把下面的代码放在一个文件中,放在 plugins 目录下即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#encoding: UTF-8

require './plugins/post_filters'

class String
  han = '\p{Han}|[,。?;:‘’“”、!……()]'
  @@chinese_regex = /(#{han}) *\n *(#{han})/m
  def join_chinese!
    gsub!(@@chinese_regex, '\1\2')
  end
end

# Use Jekylly's plugin system to modify the content before invoking rdicount
module Jekyll
  class JoinChineseFilter < PostFilter
    def pre_render(post)
      post.content.join_chinese!
    end
  end
end

需要注意的是分类文章的 rss 需要特殊处理一下,我自己的博客分类做得不好,所以分类 rss 应该没什么价值,所以就懒得处理了。需要处理的可以参考肖之的文章。

下面是原来直接修改 Jekyll 的方法,不再推荐。

可以用来转换文件的代码在 gist 上,但我不希望把 Markdown 源文件变成有着巨长行的文本,所以我修改了 Jekyll,在调用 rdiscount 前对内容调用 join_chinese 方法。需要修改 lib/jekyll/converters/markdown.rb,patch 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--- bak-markdown.rb 2011-12-23 20:14:24.000000000 +0800
+++ markdown.rb  2011-12-23 20:13:19.000000000 +0800
@@ -1,3 +1,15 @@
+#encoding: UTF-8
+
+class String
+  def join_chinese
+    unless @chinese_regex
+      han = '\p{Han}|[,。?;:‘’“”、!……()]'
+      @chinese_regex = Regexp.new("(#{han})\n(#{han})", Regexp::MULTILINE)
+    end
+    gsub(@chinese_regex, '\1\2')
+  end
+end
+
 module Jekyll

   class MarkdownConverter < Converter
@@ -115,7 +127,7 @@
             }).to_html
           end
         when 'rdiscount'
-          RDiscount.new(content, *@rdiscount_extensions).to_html
+          RDiscount.new(content.join_chinese, *@rdiscount_extensions).to_html
         when 'maruku'
           Maruku.new(content).to_html
       end

用 Markdown 来写这种类型的 blog 真是舒服多了。

AI Class 结束了

AI Class 结束了,昨天收到了 Statement of Accomplishment。这学期的很多周末都花在了做作业上,最后看到这个还是挺开心的。

AI Class Statement of Accomplishment

总体来说课程不难,但涉及的面很广。上课的一个很大的感受是,讲课好的老师可以帮你节省很多看书的时间,而且有些方法的思想只有在视频里才可以很好的展示。我买了 Artificial Intelligence A Modern Approach (AIMA) 这本书,在课程开始前按照课程涉及内容看了一些,速度比较慢。课程开始之后比较忙,一般只遇到不太明白的地方才看,明显感觉到比没上过课时看起来轻松很多。可惜的是本科和硕士阶段我很少遇到这样的老师,上课和自己看书的效果差不多。

课程涉及内容的一个不完整的列表如下,帮助我自己记录从这门课里学到哪些东西。有同学想看 AIMA 这本大部头又不知道如何选重点可以参考。

Prevent Applications Like Vim and Less Clearing Screen on Exit

TL;DR

The content clearing behavior is caused by the smcup/rmcup capability. Here’s the methods to preserve screen content depends on what terminal you use:

  • For tmux, the alternate-screen option defaults to on. Add the following in .tmux.conf to disable it:

    set-window-option -g alternate-screen off
    
  • screen does not enable the annoying terminal capability by default. If you encounter this, add the following to .screenrc:

    alterscreen off
    
  • If you don’t use screen or tmux, I suggest you invest some time in these tools and I assure you they worth it. If you can’t use these tools, then follow the directions in smcup/rmcup: hate. This removes the declaration of the annoying capabilities in the terminfo database, so the application will not use them.

Update: If you find ssh “clears” screen after logout, check if .bash_logout on the server contains clear command.

博客转用 Octopress

很早就想用 Vim + Markdown 写博客,甚至有想过自己写一个,不过我在 HTML 上的三脚猫功夫让我打消了这个念头。最近听 Zellux 说 Octopress 可以实现这一点并且亲身实践,所以决定也转用 Octopress 了。

Octopress 的好处和如何从 wordpress 迁移数据可以参考 Zellux 的文章,我用了他的脚本,按照我的需求做了一点修改,代码在此。 Octopress 的文档还不错,这里记录下我迁移数据和 deploy 时遇到的一些问题。

13 寸 Macbook Pro 拆光驱更换 SSD

Update: 转用 Octopress 的时候不小心删除了 wordpress 的目录,备份是很早之前的,这篇文章的图片全丢了,现在的图是重新画的,有些细节记不清了……

最近买了 Intel G3 320 120G 的 SSD 换到了 Macbook Pro 上。SSD 如果看性能的话 OCZ 的上一代产品 vertex 2 比这一款还好(从评测来看 sandforce 的 controller 性能很突出),价格还便宜一点。不过看到有人说 vertex 2 在 MBP 上有遇到无法 sleep 之类的问题,为图省心还是买了 Intel 的。

网上拆光驱换 SSD 的教程很多,不过很多都是写 15 寸的 MBP。Apple4us 的 干掉光驱、拥抱 SSD 虽然是拍的 13 寸 MBP 的照片,但他不是写详细过程的。13 寸 MBP 因为体积小,内部更紧凑,拆光驱比 15 寸麻烦,要拆卸的东西更多。这篇文章就上些图,说明下那里要拆吧。光驱位硬盘架,USB 光驱盒淘宝上搜下有很多。(淘宝上的都能用,不过建议资金充裕的还是买质量好点的。我买到的光驱盒很山寨,而即使是看起来做工还行的硬盘支架跟 Apple4us 里的 MCE OptiBay 的照片比起来细节上也差一点。)

Reeder: Fast, Polished, Simply Beautiful (vs. MobileRSS)

Update: 尝试转用 octopuses 的时候不小心删除了 wordpress 的目录,备份是很早之前的,这篇文章的图片全丢了,之前的截图也没了,所以不打算恢复图片了……另外,这篇文章写的是 MobileRSS 3.4,最新的版本是否存在那些问题未知。Reeder 用的很舒服,没有兴趣再尝试 MobileRSS 了。

我个人非常喜欢 Reeder,所以决定写这篇文章对 Reeder 和 MobileRSS 做一点比较。虽然 Reeder 的功能比 MobileRSS 少,但在阅读这个核心功能上做得非常精致,贴心,用着很舒服。但 Reeder 比较有个性,你更可能喜欢哪个还是要自己决定。(Zellux 在评论里提到 MobileRSS 抄袭 Reeder 的界面设计的事,当时有些应用直接封禁了 MobileRSS,MobileRSS 后来改了。有兴趣的读者可以自行Google。)

Building Tunnelblick With Openvpn-ipv6 on Snow Leopard

Update: I will no longer compile Tunnelblick directly with openvpn-ipv6. Instead I will just compile openvpn-ipv6 and replace the openvpn executable shipped by Tunnelblick (located under Tunnelblick.app/Contents/Resources/openvpn/openvpn-<version>). This would be much easier to catch up with the new releases of Tunnelblick.

You can download my compiled 64bit openvpn binary for OS X on this github downloads page. I only use it on Lion, not sure if it works on Snow Leopard.

As my university’s network is in CERNET, we can only access foreign web site directly with ipv6. Because the stock Tunnelblick does not support ipv6, I’ve been using tunnelblick-ipv6 nearly everyday recently. The problem with Tunnelblick-ipv6 on google code is that it’s uploaded in Dec 2009 and can not run on 64bit OS X kernel, so I decided to compile an ipv6 enabled Tunnelblick by myself.

Tunnelblick is actually a GUI front end to openvpn. To enable ipv6 support we need an ipv6 enabled openvpn. Thanks to jjo, he has provided an ipv6 enabled openvpn on github. The Tunnelblick project only includes official openvpn releases due to security reasons and that’s why they do not include this version of openvpn. (Explained in this Google code issue.)

It’s not quite difficult to build Tunnelblick actually, but some problems arise during the process. I’ll briefly describe the process below. The final modified code is on github so you don’t need to do it again. I’ve also uploaded compiled binary for you if you trust me :) Here’s the link Tunnelblick-3.2beta25build2647.dmg

在 C 语言中包装函数 — 用 Libtcc 动态生成代码

上一篇文章的最后我提到尝试拷贝二进制代码然后修改函数跳转地址来包装函数,但是失败了。这种做法其实是在动态的创建出新的代码,每个 wrap 函数其实对应了一个新的函数。(用 nested function 来实现 closure 时,函数只有一个,只不过环境不同。)

如果可以在运行时编译代码,就可以通过动态生成代码的方式来包装函数了。

当我发现可以用 libtcc 动态生成代码来包装 C 函数的时候忍不住再次感慨,Fabrice Bellard (QEMU,FFMPEG 的创建者) 真是神一样的程序员!运行时编译代码听起来很唬人,但 libtcc 作为Tiny C Compiler (TCC) 的一个部分真的是非常简单易用!

TCC 是 Bellard 从 2001 年的 C 混淆代码比赛上写的一个 C 编译器 OTCC 发展而来的。(OTCC 只能编译 C 的一个子集。)TCC 本身非常小,x86 上的可执行文件才 100k,但是完整支持 C99。由于 TCC 的编译速度非常快,你可以在 C 文件的第一行加上 “#!/usr/local/bin/tcc -run”,然后把 C 程序当成脚本来执行。

本文的重点是用 libtcc 来动态生成代码,从而在 C 语言中包装函数。

在 C 语言中包装函数 – Closure 和 GCC Nested Function

NOTE:gist 上的代码是不是没法输出到 RSS?如果看不到代码麻烦直接在浏览器打开吧。

最近在项目中遇到一个问题,我希望对程序中的一些函数添加日志记录功能(只需记录类似参数这样与函数内部逻辑无关信息),这些函数都存放在一个函数指针数组中,所有对这些函数的调用都通过从这个数组中取出对应的项来完成。

为完成这个任务有如下几种方案:

  1. 修改每一个需要添加日志记录的函数,这需要修改很多的函数
  2. 找到所有调用函数的代码,记录日志。这个方案可行性很差,由于函数指针可能被赋值给其他变量,找出所有的调用很困难,非常容易遗漏
  3. 在将函数存入函数指针数组时,对原先的函数进行包装(wrap function),添加日志记录的功能。之后通过函数指针数组的调用其实都是调用这个包装过的函数。由于函数指针数组赋值在项目代码中只有一处,所以这个方法需要进行的改动最小。

第三个方案有点 AOP 的影子,可以做的很漂亮,但是很难在 C 语言中实现。如果 C 提供 closure,下列代码即可对函数进行包装达到目的:

1
2
3
4
5
6
7
8
9
10
11
12
// 被包装函数的原型
typedef int (*func_t)(int arg);

// 创建包装过函数
func_t create_wrap_function(func_t f) {
    int wrapped(int arg) {
        int val = f(arg); // 调用原函数
        // 记录日志
        return val; // 返回原函数的返回值
    }
    return wrapped; // 返回包装过的函数
}