Random Tech Thoughts

The title above is not random

Pthread Mutex,其实它飞快

Update on 2012-12-27: 知道 xchg 实现的 spinlock 竞争严重时变慢的原因后可以对它做一点优化。拿锁失败,发现锁还没有释放时应该尝试等待一段时间而不是不停的去检查,这样就可以避免同时有太多的核进行拿锁操作。优化之后的 xchg spinlock 性能在单个 CPU chip 上明显超过 pthread mutex,核数变多时也还是会胜过一些。加入 exponential backoff 的 spinlock 实现也放在 github 上了。实现简单 spinlock 时 waiter 一定要加 wait backoff,这个技术对 spinlock 的伸缩性影响巨大。


严格的说应该是 pthread mutex 在 Linux 上其实非常快,用户态程序没有特殊需求就不要自己去实现 spinlock 了。

今天受实验室同学的启发,把以前测试 spinlock 性能的 micro benchmark 用 pthread mutex 也试了一下,结果发现这货真是逆天了!

基于 Mmap 的二进制 Log 库

之前的项目里写过一个用 mmap 实现的二进制日志库,把代码放到了 github。使用的例子代码可以看这个目录下的测试文件

用 mmap 写日志的麻烦之处是 log 内容超过文件大小时必须要把文件变大才能继续往后写。这个库主要做的事情除了创建文件,调用 mmap 完成初始化之外就是增大日志文件了。

增大文件现在的处理方式是当文件不够大时通过 truncate 将文件变大,然后将扩大的部分 mmap 上来,同时归还之前 map 的内存。

其实 bprint 就是在写完这个 log 库之后写的。或许有同学会觉得有用。

调整 Srt 字幕延迟的 Python 脚本

偶然发现自己 2010 年的时候写的一个 python 脚本。当时应该是为了看 TED Talk,从 dotsub 上下载的字幕跟 TED 官网下的视频不同步,然后写了这个调整字幕延迟的脚本。

代码在 github 上。这个脚本应该是我为数不多的使用管道风格写的 python 脚本。(从 10 年起不做生物信息学之后基本上就没有再写过 python 代码,日常使用的脚本转向 ruby 了。)

这个脚本主要就一些日期解析和格式化的代码,然后由一个 delaysrt 函数把这些代码整合起来,

1
2
def delaysrt(srtfile):
    cat(srtfile) | tr(delayline) | printlines

看到这行代码时感慨了下自己居然写过这样的 python 代码。cat, tr 之类函数的实现在我 2009 年的一篇文章中有介绍,没想到一晃已经到了 2012 年的末尾了,时间过得好快。

COW 0.3.5

COW 0.3.5 发布。主要改动:

  • 性能提升
  • 允许从命令行指定配置文件
  • 更好的 windows 支持
    • 配置文件从当前目录读取,后缀名为 txt 以便于编辑(嗯,换行符也是 CRLF)
  • 支持 HTTP 1.0 web server
    • COW 会将 HTTP 1.0 的 response 转为 HTTP 1.1 的发回给 client

github 不支持上传文件了,所以把 binary 放到 google code 上了,下载时可能会撞墙。

后面打算先实现简单的用户认证,然后会考虑加入类似 pydnsproxy 的防 DNS 污染的机制(是否会最终引入要看效果)。

C 语言头文件的问题

今年 11 月的 LLVM 开发者会议上 Apple 工程师 Doug Gregor 给出了一个为 C 引入 module 系统的提案,演讲视频幻灯片均可下载。

在看这个演讲之前,我并没有意识到 C 的头文件存在如此之大的问题。我对 C (以及 C++) 头文件最大的抱怨在于需要额外维护一份函数接口文件。

从 Gregor 的演讲来看,C 头文件最大的两个问题有两个:

  • 易于出错
  • 导致巨大的编译开销

这两个问题的根源都在于 C 头文件的工作方式:#include 只是简单的将指定文件包含进来,而没有程序任何的语义信息

COW 0.3.4

Update 2012-12-11: 之前引入的 bug 修复了,新的二进制文件已经上传。处理 chunked encoding 的代码在抽取重复代码时漏掉了最后的 CRLF,原来的代码稍微有点 tricky,把自己给坑了……

Update 2012-12-10: 发布之后发现引入了一个新的 bug,会导致有些网站访问不正常。所以把下载页面的二进制文件删除了,等 bug 修完再重新上传。

新功能:加入 shadowsocks 支持,允许指定 ssh server 端口。

除臭虫:修正了一个 crash bug,还有一个 http chunked encoding 处理的 bug。

推荐更新。

COW 0.3.2

COW 更新到 0.3.2。主要改动:

  • 提供预编译的二进制文件及安装脚本
  • 修复了重试 HTTP POST 的 bug
  • 添加了一个不可靠的检查 SSL 连接被墙的机制

预编译的二进制文件提供了 OS X 和 Linux 的版本,用下面的命令即可安装:

curl -s -L https://github.com/cyfdecyf/cow/raw/master/install-cow.sh | bash

希望可以方便没有安装 Go 的人使用。

前几天用 HTTPS 访问 Google 经常出错,所以才想到去检测除 timeout 和 reset 之外的 HTTP CONNECT 的错误。对 SSL 没有了解,不过觉得 SSL 错误代理服务器没法可靠的检查出来(除非像 GoAgent 一样做中间人)。现在的检测基于这样的观察:浏览器发现 SSL 错误会马上关闭连接。COW 在发现这种情况时会把请求的域名加到被墙列表中。其实这个观察有一个明显的漏洞,用户主动停止请求也会遇到这样的情况。好在用户主动停止请求应该是少数操作,而且只有在关闭连接的时间小于 1 秒的情况下才会认为被墙。

这个机制看起来不是非常可靠,但实际工作效果还可以。为了能正常访问 Google,COW 默认开启这个检测机制。

用 Go 写了个代理服务器:COW

最近在做两个比较无聊的项目时抽空用 Go 写了个 HTTP 代理服务器,主要用来正常访问 Google 等网站。给这个代理服务器起名字的时候头疼了下,最后决定叫 COW (Clime Over the Wall)

我的需求是只有访问被墙网站时走其他通道,其他网站直接访问。使用 VPN 需要通过路由表来实现这个目的,因此需要被墙网站 ip 列表,或者反过来拿到国内网站 ip 列表。不过我不太喜欢在系统上添加一堆路由表,所以以前一直是通过 socks 代理加 PAC 文件来达到目的。socks 代理加 PAC 的问题有两个:

  1. 需要手动维护被墙网站列表
  2. Google 这种被抽风型网站只能一直走代理,不抽风时访问速度变慢

COW 解决的就是这两个问题。对客户端的 HTTP 请求,COW 会首先尝试直接连接,如果发现被墙则记录下访问网站的域名,然后尝试使用 socks 代理完成请求。以后再次访问这个域名下的主机时就会直接走 socks 代理。对于抽风型网站,被墙后每过一段时间会再次尝试直连。

COW 还支持生成 PAC 文件,其中包含所有访问过的可以直接连接的域名。通过 PAC 来配置代理时,client 在访问这些域名下的主机时会直接连接而不通过 COW。所以使用 COW 一段时间后,经常访问的那些未被墙网站会直接连接而不会有走代理的开销。(PAC 解析开销可以忽略。)

目前 COW 版本号为 0.3.1,只支持 socks 作为二级代理。我日常在 MBP 上将 COW 设置为全系统代理,实验室 Linux 台机上也开着 COW 同时提供给实验室多位同学使用。肯定还存在一些 bug,不过就日常使用来说还算稳定了。用 socks 代理的同学可以试试看,欢迎提供 bug report 和建议。

PS: 写 COW 的时候越来越觉得 Go 是个不错的语言。

用 Dnsmasq 搭建自动更新的 DNS 服务器

最近实验室搬家,网络环境全都要自己搭建。技术帝同学vyatta 把我们的一台 x86 服务器配成了 NAT server。vyatta 本身基于 Debian,但做了很多修改,可以通过命令使用统一的方式来配置 DHCP, DNS, NAT, PPTP/L2TP VPN 等等服务,不用自己去学习每一种服务应该如何配置,所以使用起来很方便。(如果你自己配置过 NAT, PPTP 等服务,在 vyatta 上只用几条命令就可以完成配置就会有此体会了。)

vyatta 的问题是对于它设计时支持的网络环境配置起来的确非常方便,但是如果需求超出它的范围,则需要绕过它来自己配置,但由于它不是标准的 Debian,有些事情做起来不太方便。我们使用 vyatta 时就遇到了这样的问题。

我们希望实验室的每台机器配置好自己的 hostname,通过 DHCP 获取 IP 时自动更新 DNS 服务器中对应 hostname 的记录,从而通过名字来实现机器间的互相访问。ISC 提供的 DHCPBIND 就提供了这样的功能。原理是 DHCP 服务器在分配 IP 给 client 之后发送更新 DNS 记录的请求给 BIND 服务器,当然 BIND 需要配置成允许更新 DNS 记录。(动态更新 DNS 在 RFC 2136 中有描述。)

为 Linux 程序打包

最近有个项目需要把编译好的 Linux 程序打包后安装到多种 Linux 发行版上执行。由于是不同的发行版,所以不适合使用各个发行版自己的软件包格式。即使针对特定发行版,还是会因为不同版本的系统库版本不同而无法创建通用的软件包。(程序既需要安装到 Fedora 6 这样“古老”的版本上,也需要安装到这两年发布的发行版上。)比较几种解决方案后最终选择了打包开发系统上的动态链接库,用脚本指定 dynamic linker 和动态链接库目录的方式来执行打包后二进制文件的方案。

最先考虑的是静态链接,但开发用的 Ubuntu 并没有提供所有依赖库的静态链接版本。自己重新编译所有的库比较麻烦,所以没有用这个方案。

LD_LIBRARY_PATH 和 dynamic linker

之后尝试过 CDE (后面会具体介绍),由于一些限制最后还是决定自己打包,这样不依赖其他工具,可以获得完全的控制。最开始的时候以为只要简单的把程序依赖的动态链接库找出来全部放到一个目录下,在其他系统上指定 LD_LIBRARY_PATH 即可正常使用,但实际上这么做还差了一步。