Random Tech Thoughts

The title above is not random

MyPass: A Password Generation Tool

我日常的密码管理是通过一个 Lua 脚本为不同网站生成不同密码。Lua 脚本的好处是电脑和 iPhone 上 (通过 iLuaBox) 都可以使用,不过切换到终端、调用脚本、拷贝粘贴密码的过程不太方便,所以最近实现了一个 Chrome 插件来实现这个功能。

这次实现的算法跟 One Shall Pass 的一致,可以指定密码长度、包含多少特殊字符等选项。插件用 Chrome 来同步密码选项。这个算法依赖 PBKDF2 生成 key 的开销来增加暴力破解 passphrase 的代价,所以可以公开算法,也因此可以用 JavaScript 实现然后放在网上以便随时在浏览器中使用。

除 Chrome 插件外实现了两个独立的页面,其中一个专为 iOS 优化,添加到主屏幕后可离线使用。有兴趣的同学可以移步 github 获取更多技术细节,体验地址如下:

InstapaperBookmarklet 及 Safari 扩展推荐

Update 2013-04-20: 原先的名字 InstapaperBookmark 容易误解成只是添加一个书签,现在改名为 InstapaperBookmarklet。

写了个 Safari 扩展 InstapaperBookmarklet,在工具栏添加一个按钮来执行 Instapaper bookmarklet。如果当前标签是空白页面的话点击会访问 Instapaper 网站。

如果你跟我一样使用 SafariTabSwitching 这个基于 SIMBL 的插件(实现 cmd+num 切换标签页),习惯隐藏书签栏,又喜欢官方 bookmarklet 的视觉反馈,那么可以试试看这个扩展。

Safari 上其实有很多不错的扩展。canisbos.com 这个网站上有很多,我目前使用如下几个:

  • gDirectLinks 移除 Google 搜索结果中的跳转 URL,这样 Safari 记录的历史记录更有意义
  • QuickStyle 比 Stylish 方便多的定制网页 css 的工具
  • PopSearch 添加自定义搜索引擎
  • QuickScript 在网页中执行任意的 JavaScript,InstapaperBookmarklet 就可以用这个实现,不过不能添加专用按钮,要多点击几次鼠标
  • CustomReader Safari Reader 的效果远比 Chrome 扩展实现的效果好,这个扩展可以定制 Safari Reader 的背景色、字体等

其他推荐扩展:

  • AdBlock 注明的拦广告扩展
  • Ultimate Status Bar 实现类似 Chrome 在页面下方显示链接 URL 的插件
  • YouTube5 用 HTML5 播放器而不是 Flash,而且可以方便下载视频
  • 微博急简 新浪微博默认页面我觉得过于混乱,这个 user style 扩展可以让微博页面简洁很多

写 InstapaperBookmarklet 的时候了解了 Safari 扩展的工作机制。简单来说扩展包含一些 html 页面和 JavaScript 代码。html 负责扩展界面,JS 实现逻辑。扩展的 JS 执行时只能访问扩展页面的 DOM,要在用户打开的网页添加功能则必须向这些网页注入 JS。有些扩展需要向所有页面注入 JS,如果注入的 JS 文件很大则可能对浏览器性能造成影响。

iPhone/iPad 屏幕坏点检测网页

上周去 Apple Store 维修我用了快两年的 iPhone 4,换回来的机器屏幕上有一小块区域有亮点。于是写了个显示单色页面帮助测试坏点的网页,方便下次去维修时检查屏幕。

测试网页链接在此

其实写这个测试页面更多是尝试下写网页。Twitter Bootstrap 对我这样几乎完全没有前端经验的程序员来说的确是好东西。顺便还尝试了下 CoffeeScript,这语言看起来真不错,融合了 Ruby, Python 很多好的 feature。

P.S.

Update: 因为 iPhone 屏幕亮斑昨天再次去 Apple Store 更换,得知现在的电池跟以前的不同,如果拿下来会导致变形,所以现在维修都是机头和电池一起更换。

很多人应该知道今年央视 315 是故意黑苹果。即使我这次换到的屏幕有亮点(苹果会同意再次更换),我还是想说这次去 Apple Store 维修的经历很愉快。维修时我只是随口提了一下,Apple Genius 就额外给我换了新的电池和耳机。这样大度的售后或许是苹果产品较高价格的另一个合理之处。

Memory Consistency Model

Memory consistency model 是个不好想的东西,但出来混总是要还的,写并发程序的总有一天会遇到这个坑,如果不知道它的存在连怎么掉坑里的都不知道。这东西不是三言两语能解释得清,我自己对此也不是弄得一清二楚,写这篇文章更多是让我自己做个总结,所以只做简单介绍,另外推荐一些我觉得介绍这方面的好文章。欢迎指出错误 :)

什么是 Memory Consistency Model

并行程序在多个 CPU 上同时执行代码,如果每个 CPU 访问的都是自己私有的数据,那世界将非常美好,我们可以既不需要同步也不需要考虑 memory consistency model 的问题。但实际情况是总会有一些数据需要共享,有些 CPU 修改共享数据,有些读取。

Memory consistency model 定义了多个 CPU 同时对共享内存进行读写操作时内存的行为

汽车为什么要有刹车

Why do cars have brakes? 这篇文章不错,继续往下读之前可以先想一想这个问题。

没有刹车,意味着不能很快减速,出于安全考虑不得把车开慢些。刹车给了我们减速的能力,因此让我们能够驾驶的更快。

原文作者将此类比到软件开发中,如果将软件构建的过程类比为开车,那测试就好比是刹车。

  • 如果有好的自动化测试,我们可以比较放心地去修改代码,尽早发现 bug,停下 coding 修复 bug
  • 没有测试则要么写代码的时候总是很小心,每次 commit 之前检查很多遍;要么依然全速前进,偶尔发现一堆 bug 之后花更多的时间 fix
    • 我现在的习惯是每次 commit 代码之前先在 Source Tree 中检查几遍,但改动一多依然难免引入 bug

最近优化 COW 性能的过程中对此非常有同感。之前写 COW 的时候偷懒,没有写很多测试。最近做性能优化的过程中引入过一些 bug,没有测试代码即使 fix 了也不是非常放心。几次之后索性修改哪部分代码就先把测试补上。虽然写测试需要花额外的时间和精力,但有测试之后可以放心修改代码、尽早发现 bug,总的来说应该反而节省更多的时间。

这次我才是切身体会到测试对于重构以及优化代码的重要性。

最后,顺带宣布一下 COW 0.6 版发布。主要变化:

  • 允许用户指定 PAC URL,方便通过端口映射将 COW 提供到外网
  • 性能优化
  • 对 HTTP client/server 更加宽容,参考 RFC2616 19.3
  • 一些 bug fix

感谢 go test 以及 Travis CI 在测试上提供的帮助。

COW 0.5

放寒假之后花一周多的时间改进 COW,修复了些 bug,增加了一些新功能。改动比较大,所以跳过 0.4 版直接发布 0.5。使用说明和配置样例的注释都改成了中文,配置文件选项有些变化,请参考 github 上的说明修改;二进制文件下载还是在 Google Code。(发布之后才想起来 Google Code 貌似也被墙,使用安装脚本的话需要配置代理环境变量。)

感谢 glacjay 的建议,让我把 COW 做的更加自动化一些。0.5 版本最大的变化也在此。

由于没法准确判断超时是由于被墙还是连接状况差导致,被墙网站的判断没法做到非常准确。COW 现在的做法是:

  • 统计访问的 host 被墙和直连访问的次数,两者的差超过一定次数后才判断为被墙或者直连
    • 只有判断为直连后才会加入到 PAC 中
    • 如果原先的直连网站哪天被墙,不使用 PAC 再用 COW 访问一遍即可
  • 对不确定是否被墙的网站总是尝试直连,失败后两分钟内总是用二级代理,过后再尝试直连
  • 如果某个网站直连访问失败过,会把判断被墙的连接和读取的超时时间减半,避免等待太久
  • 内置一些直连和被墙网站列表

我自己用下来这个实现的效果还不错。原先的 updateBlocked, updateDirect, autoRetry 这些需要人工干预的选项都去掉了,名字很囧的 chou 文件也不需要了。

其他改动:

  • 支持 HTTP 二级代理(今天刚加的功能,仅用 goagent 测试过)
  • 用户可以自己指定超时时长,COW 也会自动估算
  • 支持使用 host 指定被墙和直连网站
  • 改进了 Windows 支持(仅仅在 XP 上测试,Win 7 不知道是否能正常工作)
  • 支持监听多个地址
  • 支持 IP 地址和用户名密码认证

之前想过的解决 DNS 污染的功能还是不打算做了,像 twitter 即使拿到了正确的 IP 其实还是不能访问,加入这个功能或许意义不大。使用 Go 的 ssh 实现来内建 ssh 代理支持的功能暂时也不做了,一来不清楚这个实现的稳定性,二来我觉得 shadowsocks 比 ssh 代理更加好用,而且除了 windows 平台以外加入这个功能的好处不大。

欢迎更新使用,发现 bug 麻烦在 github 上给我发 issue report。

反汇编 X86 指令

Update on 2013-01-16:Dawson Engler 的主页上看到一篇论文,Reverse-Engineering Instruction Encodings,利用系统的汇编器自动生成指令 encoding 信息。有了这个信息就可以写 code generator, disassembler 了。Dawson 在这篇 paper 的说明里写道:

It’s genesis was the fact that writing a binary encoder for the x86 is an unredeemably disgusting experience.

我想说,写 x86 的反汇编器是一样的感受。


折腾了 QEMU 两年多,看到 Bellard 大神Javascript 写的 x86 模拟器后也曾想用 Go 写一个 x86 的模拟器。

2012 年的时候花了些时间从反汇编 x86 指令开始写,写着写着就没有动力继续下去了。想要模拟 x86 指令集非常麻烦,光是反汇编和 condition code 处理就包含了大量无趣的体力活。暂时不会有兴趣继续这个项目了。(或许开始时应该采纳实验室同学的建议选一个简单一些的体系结构来模拟,例如任天堂的游戏机,不过因为懒得去学其他体系结构而没有采纳。)

不过这个半途而废的项目倒是让我更加了解 x86 的指令。这篇博客就分享一些当时找到的关于 x86 指令集的资料和发现吧。

Valgrind 中用 Pipe 实现的 GIL

Valgrind: A Framework for Heavyweight Dynamic Binary Instrumentation 这篇论文时看到了一种实现 GIL 的方式。

先介绍一下为什么 Valgrind 需要 GIL。Valgrind instrument 二进制文件并且执行采用的方式叫做 Disassemble-and-resynthesise (D&R)。其大概过程如下:

  • 将原二进制代码反汇编到中间代码
  • Valgrind 插件对得到的中间代码插入做检查的中间代码
  • 对中间代码做优化之后,生成 native 代码

由于中间代码是 RISC like 的,一条 CISC 的读写指令操作可能对应多条中间代码,最后生成的对应原先读写指令的 native code 有多条指令组成。在多线程的情况下这会使得读写操作不再是原子性的。(即使在单核机器上也会有这个问题,因为线程可能在执行一条读指令的过程中被调度走。)

Valgrind 解决这个问题的方式是让多线程程序在任何时候都只有一个线程在执行,这就需要一个类似 Python/Ruby 解释器里 GIL 的东西,在 Valgrind 中这个机制实现很巧妙。

Valgrind 利用一个 pipe 来实现这个机制。尝试拿锁的线程都尝试从全局的 pipe 中读,由于 pipe 中没有内容可读而 block 住。锁拥有者在解锁时往 pipe 中写一个字节,OS 会保证只有一个线程被唤醒,读出 pipe 中内容然后往下执行,其他线程则继续 sleep。这个方案里究竟那个线程被唤醒还是由 OS 决定,但可以严格保证任何时候都只有一个线程执行。(用 pthread mutex 的话还是会在 spin 的时候有一些线程同时执行,虽然对 Valgrind 的应用场景来说应该没有什么问题。)

Valgrind 的论文还是挺有意思的。虽然也是 binary translation,但它没有使用 code chaining,而是用手写的汇编来提高从 translated code 执行跳出后再次进入 translated code 的速度;翻译的单元也不是通常使用的 basic block 而是 super block。论文里提到的很多机制现在实现方式都是已经修改过多次,写出这样一个成熟的工具真是非常不容易。

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 库之后写的。或许有同学会觉得有用。