Mac OS X 下与 SSD 相关的优化

最近刚入手一块 Intel X25-M G2 80G SSD,使用效果不错,于是整理了下面这些跟 SSD 有关的优化经验:

#### 安装

因为 SSD 比较小,而有些数据又不是那么需要快速的访问,所以我保留了 MacBook Pro 里自带的 320G 7200 RPM 硬盘,做了如下改动:

1. 买了一个光驱位硬盘托架,最有名,也是最贵的一种要 $99,但山寨版可以在淘宝找到,对于 Unibody 的机器来说,找 9.5mm 高的那种型号就行,比如 Fenvi 的,建议买 100 以内的。

2. 把 MBP 拆开,SuperDrive 取下,把原来的硬盘取出,放入光驱位硬盘托架,把 SSD 接在原来的硬盘所接的 SATA 口,然后把硬盘托架安装回原来光驱的位置。这种搭配是为了保证用 SSD 做系统盘 (挂载在 `/` 上) 时系统能正确的让硬盘休眠。当然,如果你比较奢侈地买两块 SSD,也可以让他们组成 RAID0,这样性能就更夸张了。

3. 给在 SSD 所在卷分区,只需要分一个区,安装系统。新的系统会把原来那块硬盘也列出来。这时修改 `/etc/fstab`,加入:

/dev/disk1s2 /Users/jjgod/Downloads hfs rw

这个做法是把原来那块硬盘挂载在我自己这个用户的 `Downloads` 目录,这样所有下载的内容都会放在这块比较大的硬盘上 (当然有用的我以后会转移到 SSD 上),考虑到 Downloads 主要起的就是一个临时的缓冲作用,这样还是比较方便的。然后重启机器,就能发现这块硬盘只被挂载在上述目录下了。

4. 除了下载内容,我还在这个目录放以下内容:

* VMware Fusion 的虚拟机文件,这是从别的机器复制过来的,直接放在这个卷的 `/Virtual Machines` 目录下,然后在 VMware Fusion 里打开就行了。需要注意的是以后 VMware 里创建新虚拟机的时候也得记住选择目录,别放在自己的 `$HOME/Documents` 下面了。

* iTunes 媒体库,这个可以在 iTunes 的 Preferences -> Advanced -> iTunes Media folder location 选择。我是先在这里选好了,然后从原来的机器里导入以前的 iTunes 数据。

* 其他下载软件的默认下载位置,包括 uTorrent, Transmission 和 aMule。

* [Steam](http://store.steampowered.com/) 的游戏目录,这个可以通过创建符号链接到 `$HOME/Documents/Steam Content` 实现。

#### 优化

上述安装过程后,系统已经能保证数据各自放在比较合适的地方了,参考 [Mac OS X SSD Tweaks](http://blogs.nullvision.com/?p=275) 这篇文章的介绍,我们还可以做如下优化:

1. 用 `noatime` 方式挂载系统盘,这样可以减少不必要的 I/O 次数,虽然 SSD 做这些操作非常快速,但考虑到**最后访问时间**这个属性其实很少用到,大家关心的一般都是最后修改时间和创建时间,所以完全可以关闭这个属性,这在 Unix/Linux 下是非常常见的文件系统优化选项。一个简单的方法是,修改 `/etc/fstab`,加入:

/dev/disk0s2 / hfs rw,noatime

重启后,系统盘的挂载就带上 `noatime` 选项了:

$ mount | grep ” / ”
/dev/disk0s2 on / (hfs, local, journaled, noatime)

2. 禁用冬眠 (hibernate) 模式以节省空间。在 Mac 耗尽电池时,会进入“冬眠”模式,将内存中的所有内容写入磁盘,下次唤醒后从这些内容恢复状态。所以系统会在 `/var/vm` 维护一个和内存等大的 `sleepimage` 文件,考虑到 SSD 空间宝贵,而一般绝少会遇到耗尽电池的情况,可以禁用掉这个功能以节省空间:

$ sudo pmset -a hibernatemode 0
$ sudo rm /var/vm/sleepimage

3. 减少临时文件的读写。

* RAMDisk 是常见的性能优化手段,对于内存充足的机器,把频繁读写的内容放到一个用内存为存储的虚拟磁盘中,能大大加快速度。虽然 Macintosh Performance Guide [最近的一个研究](http://macperformanceguide.com/OptimizingPhotoshop-RAMDisk.html)表明 RAMDisk 并不能很大地提升如 Photoshop 这类软件操作的性能,至少投入产出比不是很经济,但至少用它来保存一些本来就可以随意丢弃的数据是个很好的思路。[Mac OS X SSD Tweaks](http://blogs.nullvision.com/?p=275) 提供了创建 RAMDisk 的脚本,他的做法是在系统启动时创建一个 256M 的 RAMDisk,挂载在 `/private/tmp` 上面。他还建议把 `~/Library/Caches` 也放到 RAMDisk 里。最大的问题是,如果你长期不重启 (我一般的重启周期是 80 ~ 100 天),那有的临时文件,比如 `~/Library/Caches/com.apple.Safari/Webpage Previews/` 会增长到数百 M 甚至上 G。如果被它占满了 RAMDisk 的空间,那其他缓存数据就写不进去了。

* 考虑到 RAMDisk 是个比较有风险的优化手段,也可以用以下方法禁用掉 Safari 的 Webpage Previews 以减少临时文件读写。如果你像我一样从来不用 Safari 的“Top Sites”功能,一定会很讨厌它凭空占用大量的空间。

$ defaults write com.apple.Safari DebugSnapshotsUpdatePolicy -int 2

* 关闭 Spotlight 索引也是一个有用的优化手段,如果你像我这样从来不用 Spotlight 的话。

#### 结语

* [Macintosh Performance Guide](http://macperformanceguide.com/) 还提供了一些更奢侈的优化手段,这里不一一介绍了,因为我自己也没试过,有兴趣可以自己尝试。值得尝试的是它提供的 DiskTester 工具有一个 [recondition](http://macperformanceguide.com/Software-DiskTester-UserManual-recondition.html) 功能,通过大量写空白块来优化长时间使用后的 SSD 性能。

* “安装”部分感谢[草莓数码](http://www.berrydigi.com)的 iBook 和 MacBook 的帮助。

* “优化”部分大部分思路来自 [Mac OS X SSD Tweaks](http://blogs.nullvision.com/?p=275)。

用来修正错误编码的文件名的 Safari 插件

我之前[讨论过](/2008/02/17/cocoa-nsstring-decoding-error/)一次这种文件名的错误编码,为了在浏览器下载时的不必手工修正这个问题,这里提供一个 Safari 的 SIMBL 插件: SafariURLFix。

使用步骤如下:

1. 如果没装过,先[安装 SIMBL](http://www.culater.net/software/SIMBL/SIMBL.php);

2. 下载 [SafariURLFix.zip](http://jjgod.org/program/SafariURLFix.zip),解压后,放到 `~/Library/Application Support/SIMBL/Plugins` 目录 (如果没这个目录就自己创建);

3. 在 Terminal 中输入:

`defaults write com.apple.Safari JJURLsToFix -dict newsmth.net GBK`

其中 newsmth.net 为你希望应用修正的网站域名。也可以打开 `~/Library/Preferences/com.apple.Safari.plist` 文件自己编辑 `JJURLsToFix` 这个 Dictionary,自行添加新的,见附图。Edit Safari Preferences Manually

4. 重新启动 Safari,尝试下载[这样](http://att.newsmth.net/att.php?p.719.275418.308.png)的文件,看看文件名是否被正确纠正了。

如果还有什么问题,欢迎在下面提出。

Cocoa 的 NSString 解码错误处理

在使用 [Safari](http://www.apple.com/safari) 的时候,我们会注意到一个很常见的乱码问题,如下图:

Safari Decode Error

这是在打开 [http://att.newsmth.net/att.php?p.719.214628.536.png]() 这样的图片链接时,Safari 错误的判断了这个图片文件的文件名造成的。而为什么会有这样的错误判断呢?

其实 Safari 使用的是 [Cocoa](http://developer.apple.com/cocoa) 框架 [URL Loading](http://developer.apple.com/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html) 架构中的 NSURLResponse 类的 [suggestedFilename](http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSURLResponse_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLResponse/suggestedFilename) 方法实现的。

而这个方法,其实就是解析 HTTP 首部中的 [Content-Disposition](http://www.ietf.org/rfc/rfc2183.txt) 域里的 filename 部分完成的,比如下面这个首部:

$ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png
HTTP/1.1 200 OK
….
Content-Disposition: inline;filename=ͼƬ_6.png
….

显然这是乱码,可奇怪的是,这和我们在上面的图中看到的乱码又不一样,这是为什么呢?

假如将它作为 GBK 来解码就清楚了:

$ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png | iconv -f gbk -t utf-8
HTTP/1.1 200 OK
….
Content-Disposition: inline;filename=图片_6.png
….

哦,原来是 GBK 编码的“图片_6.png”,可是这个文件名怎么会变成开头图片中那种形式的乱码呢?其实写一段 Cocoa 程序就可以发现:

#import

int main()
{
const char *bytes = “图片_6.png”;
NSString *str = [[NSString alloc] initWithCString: bytes
encoding: NSASCIIStringEncoding];
NSLog(@”str: %@”, str);
[str release];

return 0;
}

(用 GBK 编码保存) 这个程序的执行结果就是输出开头那段乱码,原来 NSURLResponse 把 Content-Disposition 中的 filename 当成 ASCII 处理了,怪不得会乱码。

可是也不能怪 NSURLResponse,毕竟服务器没有提供任何编码的信息,而 RFC 2183 中也明确说明,不应该在 filename 中使用任何 ASCII 以外的字符,用了就是后果自负了。

那假如我们要写一个自己的客户端 (或者尝试修正 Safari 的错误行为),该怎么修正已经被按照 ASCII 错误解码的 NSString 呢?

因为 NSString 本身是按照 UTF-16 编码的,所以如果逐个字符地观察这个错误解码后的 NSString:

int max = [str length];

int i;
for (i = 0; i < max; i++) { unichar ch = [str characterAtIndex: i]; printf("%x ", ch); } 我们可以得到: cd bc c6 ac 5f 36 2e 70 6e 67 这样一串输出,`5f 36 2e 70 6e 67` 就是 `_6.png`,比较好认,前面的 `cd bc c6 ac` 是什么呢?一查,原来是“图”和“片”这两个字的 GBK 编码。 这就好理解了:NSString 一开始把一段 GBK 编码的字节流*逐个字节地*按照 8bit-ASCII 处理了,原本 10 个字节对应的是 7 个字符,结果被错误地解码为了 10 个字符,所以我们要把它转换回去,首先是要还原回原来的那段字节流: int max = [str length]; char *nbytes = malloc(max + 1); int i; for (i = 0; i < max; i++) { unichar ch = [str characterAtIndex: i]; nbytes[i] = (char) ch; } nbytes[i] = '\0'; 然后再将这段字节流按照正确的编码 (GB18030) 处理: NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding( kCFStringEncodingGB_18030_2000); NSLog(@"nstr: %@", [NSString stringWithCString: nbytes encoding: enc]); 结果果然得到了正确的输出: 2008-02-17 06:09:46.408 test[14095:10b] nstr: 图片_6.png 同样的逻辑可以用在很多类似的乱码情形中。完整的代码可以在这里下载: [test-str-encoding.m](http://jjgod.org/code/test-str-encoding.m)