qyb的博客

dup2.org 恢复访问

由于原来的 vmmatrix 服务调整,现在把dup2.org切换到新的一个 VPS 上,感谢老韩友情赞助服务资源。

顺便把 drupal 也升级到了最新的 6.x

在从 5.x -> 6.x 的升级过程中,导出的 sql 文件有 1 个G!! 检查后发现都是 watchdog 这个表的内容。

watchdog 在 5.x 是核心模块还不能 disable,现在升级到 6.x,可以把"Database logging"这个模块禁用了,hoho

扩展模块现在只剩下 4 个: GeSHi Filter (满足时不时贴代码的需求)、XML Sitemap (搜索引擎优化)、CAPTCHA(对付垃圾留言)、Similar entries (提高网站的可阅读性). 别的东西我觉得都没有用,否则每次升级 drupal 都是一个麻烦事。

说实话,如果不是已经熟悉了 drupal 以及国外的 blog 服务容易被盾,真想切换到 blogspot 上. 要么使用 blog.sohu.com?? 好像很不严肃的样子...

文本聚类研究

最近一直在研究文本分类。到今天,终于搞出一个收敛的单词聚类算法。

最主要的时间是在磨刀上——前前后后总共差不多有 1 周的时间在看 sogou 分词库的文档,以及给它开发一个 python-binding

然后是差不多花了半天的时间写爬虫,获得最初的文档库

利用"[PDF] WRM :一种基于单词相关度的文档聚类新方法"里介绍的概念计算单词两两之间的相关度

最麻烦的就是利用相关度对单词聚类,论文里介绍的方法好像并不好用。从周二到周日,都在不停的找论文,试验新算法,运行测试,修正... 的循环里度过

失败无数次后,最后在 Fuzzy C-Means Clustering 的指导下,投机取巧的设计了一个方法以及判断收敛的机制,今天下午已经成功的跑完一次聚类到 250 个集合的过程,另一个聚类到 100 个集合的过程虽然比较慢,但在 2 个小时前也已经结束。Oh, my god, 看起来是可以正常工作了。

感谢分词库开发者,感谢搜狗新闻、关键字广告 TEAM 曾给的帮助

【尤其是 sogou 的开发包,能返回词的词性... 虽然还有瑕疵,但已经对我帮助很大了】

感谢python, 没有它不可能短时间内把算法体无完肤的改上10几次

感谢《鬼吹灯》,周末的晚上是它让我保持清醒,时不时的去看一眼后台跑的计算过程.

感谢家里人忍受我最近的加班

Topic: 技术

在 PyS60 上使用 ctypes 修改 WIFI 的 MTU

自从换了 DELL 无线路由后,俺的 E61i 也出现无法正常连接 WLAN 上网的情况,而且和当初 Vista 的故障一样——可以访问 google,但大部分网站的连接不上,据以往经验,估计还是因为手机的 MTU 大于这个路由器最大支持的能力。

在网上搜了半天,发现在 S60 2nd 机型上,有一种改 genericnif.ini 的方法可以改变 MTU,但是到了 S60 3rd 机型,貌似就只能自己动手写程序了。

但 Symbian 下的 C++ 开发俺不熟啊,PyS60 没有接口啊,怎么办。正好在查相关开发信息的时候,发现 Symbian 出了一个 POSIX 兼容层(S60 平台上的称呼是 Open C),PyS60 社区有人据此移植了 libffi_ctypes 过去;又有人包装了兼容 Python2.5 的 ctypes 提供一些外围函数;wiki 上还有一个 open c 下执行 setsockopt 的例子

于是安装了 Open C 的 SDK, 然后在它的文档里找到这么一段:
The following flags will work only on hardware because it is supported by the underlying symbian API.

  • SIOCGIFMETRIC
  • SIOCGIFMTU
  • SIOCGIFNETMASK
  • SIOCGIFBRDADDR
  • SIOCGIFFLAGS
  • SIOCGIFDSTADDR
  • SIOCGIFPHYS
  • SIOCSIFMETRIC
  • SIOCSIFMTU
  • SIOCSIFFLAGS

只要确认 SIOCSIFMTU 是可以用的就好办了,对一个熟练的 C 程序员且有一定 ctypes 使用经验的人来说,除了平台移植问题外,开发是没有什么困难的。

开发过程如下

  1. 在手机上安装 libffi、Ctypes (_ctypes.pyd)、ctypes
  2. 在 Windows 下安装 S60 C++ SDK,以及 Open C plugin。可以找到 pips_nokia_1_3_SS.sis 等一共 4 个 .sis 文件,都安装到手机上
  3. 找到 winscw\udeb\libc.lib,然后用 dumpbin.exe /exports libc.lib 寻找函数名对应的序号,PyS60 的 ctypes 必须得用序号去定位函数,比如这样
    1. from _ctypes import *
    2. from ctypes import *
    3. libc = dlopen("libc.dll")
    4.  
    5. socket  = lambda x,y,z: call_function(dlsym(libc, "339"),   (x,y,z))
    6. ioctl   = lambda x,y,z: call_function(dlsym(libc, "180"),   (x,y,z))
    7. close   = lambda x:     call_function(dlsym(libc, "57"),    (x,))
    8. perror  = lambda x:     call_function(dlsym(libc, "255"),   (x,))
    9. dup     = lambda x:     call_function(dlsym(libc, "67"),    (x,))
    10. close(2)
    11. dup(1) #把标准错误重定向到标准输出,这样 perror 就输出到标准输出了
    12.  
    13. ntohl   = lambda x:     call_function(dlsym(libc, "168"),   (x,))
    14. ntohs   = lambda x:     call_function(dlsym(libc, "169"),   (x,))
    15. print ntohs(1), ntohl(256)  #简单的测试脚本
  4. 没有 dumpbin.exe 这个工具(VC++)的,也可以用 MinGW 的 nm.exe 来处理 libc.lib,只不过输出结果还需要再处理一下
    1. f = open("c:\libc.txt")
    2. idx = 1
    3. for i in f.readlines():
    4.     if i.startswith("00000000 T "):
    5.         if i[11] == '.':
    6.             continue
    7.         print idx, i[11:].strip()
    8.         idx += 1
  5. 根据 SDK 的头文件,找出 SIOCSIFMTU 这样的常量定义写成 python 语句
    1. IOCPARM_MASK = 0x1fff
    2. _IOC = lambda inout,g,n,l: (inout | (l & IOCPARM_MASK) << 16 | ord(g) << 8 | n)
    3. _IOR = lambda group, num, len: _IOC(0x40000000L, group, num, len)
    4. _IOW = lambda group, num, len: _IOC(0x80000000L, group, num, len)
    5. _IOWR = lambda group, num, len: _IOC(0x40000000L|0x80000000L, group, num, len)
    6.  
    7. SIOCGIFCONF     = _IOWR('s', 3, SIZEOF_IFCONF)
    8. SIOCGIFFLAGS    = _IOWR('i', 17, SIZEOF_IFREQ)
    9. SIOCGIFMTU      = _IOWR('i', 51, SIZEOF_IFREQ)
    10. SIOCSIFMTU      = _IOW('i', 52, SIZEOF_IFREQ)
    11.  
    12. IFF_UP          = 0x1
    13.  
    14. AF_INET         = 0x0800
    15. SOCK_DGRAM      = 2
    16. IPPROTO_UDP     = 17
  6. 还要去找 struct ifconf, struct ifreq 是怎样定义结构体的,结构体大小是多少
    1. SIZEOF_IFREQ = 84
    2. SIZEOF_IFCONF = 8
    3. IFREQ_IDXFMT = '52sH'
    4. IFREQ_MTUFMT = '52sI'
    5. IFREQ_FLAGSFMT = '52sBB'
    6. autopadding = lambda start, size: start + str(size - calcsize(start)) + 's'
    7. ifreq_idxfmt = autopadding(IFREQ_IDXFMT, SIZEOF_IFREQ)
    8. ifreq_mtufmt = autopadding(IFREQ_MTUFMT, SIZEOF_IFREQ)
    9. ifreq_flagsfmt = autopadding(IFREQ_FLAGSFMT, SIZEOF_IFREQ)
  7. 真正的操作很简单:
    1. sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    2.  
    3. result = create_string_buffer(SIZEOF_IFREQ * 20)
    4. ifc = create_string_buffer(pack("II", SIZEOF_IFREQ * 20, addressof(result)), SIZEOF_IFCONF)
    5.  
    6. ret = ioctl(sockfd, SIOCGIFCONF, ifc)
    7. ifc_len, ifc_buf = unpack("II", ifc.raw)
    8. count = ifc_len / SIZEOF_IFREQ
    9. for i in range(0, ifc_len, SIZEOF_IFREQ):
    10.     ifreq = result.raw[i:i+SIZEOF_IFREQ]
    11.     ifr_name, ifr_idx, padding = unpack(ifreq_idxfmt, ifreq)
    12.     ifr = create_string_buffer(ifr_name, SIZEOF_IFREQ)
    13.     ret = ioctl(sockfd, SIOCGIFFLAGS, ifr)
    14.     ifr_name, ifr_flags, ifr_flagshigh, padding = unpack(ifreq_flagsfmt, ifr)
    15.     if not (ifr_flags & IFF_UP):
    16.         print "%s: DOWN" % ifr_name
    17.         continue
    18.     ret = ioctl(sockfd, SIOCGIFMTU, ifr)
    19.     ifr_name, ifr_mtu, padding = unpack(ifreq_mtufmt, ifr)
    20.     print "%s: UP, MTU: %d" % (ifr_name, ifr_mtu)
    21.     if ifr_mtu > 1400: #凡是 MTU > 1400 的,都改成 1400
    22.         ifr = create_string_buffer(pack(ifreq_mtufmt, ifr_name, 1400, padding), SIZEOF_IFREQ)
    23.         if -1 == ioctl(sockfd, SIOCSIFMTU, ifr): foo = perror("why")
  8. 但...为什么 perror 会报 Permission denied 呢? 后来查了半天,发现是 SIOCSIFMTU 需要有 NetworkControl 权限。于是去 这里 申请一个自己手机 IMEI 对应的证书(免费的),证书对应的 key 在这里
  9. ensymble 这个工具来给 libffi/Ctypes/PythonScriptShell(unsigned_testrange) 重新签名,加上 NetworkControl 权限
    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --execaps="ALL-TCB-ALLFILES-DRM" PythonScriptShell_1_4_4_3rdEd_unsigned_testrange.SIS imei_PythonScriptShell_1_4_4_3rdEd.sis

    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --dllcaps="ALL-TCB-ALLFILES-DRM" libffi.sisx imei_libffi.sis

    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --dllcaps="ALL-TCB-ALLFILES-DRM" ctypes.sisx imei_ctypes.sis

  10. 这下大功告成,我....我终于又可以在家里坐在马桶上用手机上无线了!!
Topic: 技术

关于牛奶

From blog 配图

十一期间亲戚们一起吃饭,收到一条彩信报,说是北京市决定给每头奶牛每天补贴伙食费15元。我把这条新闻当笑话讲给同桌的人听——这也太扯了,算下来每个月伙食补贴比搜狐午餐补贴还高——但正好我老婆的表哥曾经养过奶牛,他给我说:

奶牛的伙食是很好的,比如一头奶牛一天 50 斤奶的话,大概要吃 25 斤的粮食。光吃草,是产不出奶来的。

见到这里有一个奶牛专家,我一下来了兴趣,于是又从他那里了解到好多奶牛知识。

  1. 牛奶这条供应链上是三个环节,奶农、奶站、生产企业。
  2. 奶站以1.2元/斤的价格从奶农那里收奶,然后以1.3的价格卖给企业
  3. 奶站必然是要掺水的,氮化合物也随之添加
  4. 企业,或者说企业的原料采购部门对这个猫腻是很清楚的,据说曾有企业向奶站提出过以1.4的价格收购,前提是不要掺水,但奶站不干
  5. 假如收购价格从1.3提高到1.4还达不到掺水的利润的话,可见掺水的比例之高,或者说有毒物质比例之高
  6. 现在由于奶制品销量剧减,需求不足,最新的模式是奶站收了奶去卖,然后按卖的价钱计算给奶农的付款
  7. 养奶牛的成本是很高的,一头牛大概1w元。因为一年才一胎,就是说一只奶牛两年才能产一只新奶牛。奶牛也不是随时都有奶,还是在哺乳期内奶量大。
  8. 供应链还有一种优化的模式是"托牛所",就是养户出钱购买奶牛,集中交给奶站养,饲料什么的都由奶站负责

好啦,科普结束。

问题是,企业明知道它的供应商提供不合质量的产品,为什么长时间以来无法解决(我家一本十年前的书上就描述了如何检查牛奶中是否含有尿素)

我更关心的是:是不是在现有体系下,跨农业和工商业的(以及跨地域的)供应链就根本无法优化?比如"托牛所"这个东西,乳品企业能参股,或者是自己组建吗?县级/乡级政府在这里面的角色是什么?

如果供应链上游不能真正市场化,我觉得最后还是要出问题。

Topic: 商业 社会

我要抵制蒙牛了

本来我是一个很和谐的人,虽然平时有很不和谐很不主流的思想,但还不至于到网上发表右派言论或愤青言论。。。

但这次不做点什么也太对不起蒙牛了...

先抵制5年再说,不知道到 2013/9/28 ,这家企业还在不在

最后,马上就是共和国59年大庆。在生日之际像鲁迅那样说:"这孩子将来是要死的" 太恶毒。我的祝愿是:再坚持15年,就比苏联(1917—1991)更长寿了,hoho

Topic: 社会

给奥组委做的邮件系统容灾方案

据说一共有5家厂商提供了方案,俺代表搜狐提的方案最后得到采纳,日后要说起bj2008,我可以对达达说:当年你老爸也是为奥运建设出过一份力的...

现在残奥会也已经结束,按照当初商定的合同,搜狐的支持服务在一周后也就是9/30将正式终止,我想现在讲一下技术细节应该不会有什么影响了。

要解决的问题简单说就是:万一奥组委的邮件系统瘫痪,怎样才能让邮件系统尽快可用。

系统出问题有很多种,除了硬件故障、软件故障外,网络故障和机房故障也是需要考虑在内的。我根据搜狐邮件中心所能做的工作,提出的是一个异地容灾方案:

灾备系统在我们这里,把它设为低级别的 MX,平时不启动 25 端口,一旦奥组委确认需要启动该灾备系统,则启动 25 端口。将所有收到的邮件,比如说发给 foo@beijing2008.cn 的,利用 Milter 协议的 addRcpt 功能,增加一个 foo@vip.beijing2008.cn 的收件人。vip.beijing2008.cn 是本地 host 的一个邮件域,在紧急情况下,奥组委邮箱的原用户可以登录到这里查看并回复新收到的邮件。而发给 foo@beijing2008.cn 的邮件会在我们的 postfix 队列里等待,一旦奥组委的故障恢复,自然就能重新投递过去。

技术核心就是 Postfix 的 Milter 支持能力,以及针对 beijing2008.cn 的投递不是 DNS 查询而是 transport_table 指定。实施上最麻烦的是用户数据问题,和邮件没任何关系,却花了大量的时间在上面。

灾备准备好了,奥运期间也没有出现突发事件需要考验俺的这个方案。算是好消息,但也有点小遗憾。

最后要说的是,奥组委的IT部门,业务能力还是很不错的,比我以前做项目时接触的几个甲方单位强多了;还有一个首信的同仁,不知道是做项目监理还是负责整个的项目集成,几个电话打下来对他的敬业精神和认真态度也是很佩服。从小见大,奥运会本身怎么怎么样不去评论,但确实是把中国最优秀的人才集中在了一起去支持这个 big project。。。咳咳,上句所说的最优秀人才不包括我.

Topic: 技术

python 性能调优一则

最近几天新写的一个日志处理程序深受性能不足所困扰(每5分钟处理一次httpd日志,统计用户的连续访问和停留时间)。显然每5分钟处理过的数据必须至少保留 30 分钟后才能销毁,否则所谓连续访问无从算起。

问题是第一次处理的时候,程序很快就结束了,大概30秒;但第二次时间就增加到60秒左右,第三次时间增加到90秒上下...

很容易想到这是由于数据没有释放,导致在做查找操作的时候时间也随之延长...但我这里明明用的是 dict,应该是 o(1) 的复杂度,为什么这里表现出了线性增长关系呢??

代码很简单,我检查了又检查,函数封装了一个又一个(为了便于profile),从 hotshot 换到 cProfile,就是找不出程序慢在何处。

从 profile 结果看,最让人惊讶的就是主函数所调用的各个子函数的用时之和远远小于主函数所消耗时间(主函数内的计算量可以忽略不计),比如这样:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000  180.979  180.979 :1()
        1  114.522  114.522  180.979  180.979 test.py:33(main)
  5521410   32.147    0.000   32.147    0.000 {method 'split' of 'str' objects}
  5521409   16.107    0.000   24.152    0.000 test.py:7(process)
 11042818    6.586    0.000    6.586    0.000 {method 'has_key' of 'dict' objects}
  5521413    4.577    0.000    4.577    0.000 {method 'readline' of 'file' objects}
        3    3.594    1.198    3.602    1.201 test.py:20(commit)
  5521409    1.979    0.000    1.979    0.000 {method 'strip' of 'str' objects}
  3854644    1.459    0.000    1.459    0.000 {method 'append' of 'list' objects}
        3    0.000    0.000    0.008    0.003 {time.strptime}
        1    0.001    0.001    0.006    0.006 /usr/local/lib/python2.5/_strptime.py:12()
       10    0.000    0.000    0.004    0.000 /usr/local/lib/python2.5/re.py:186(compile)

总共 180 秒执行时间,但其它所有函数加起来也不超过 80 秒,另外的那 100 秒去哪里了呢?

然后就去翻 python 手册的 profile 部分,在看 timeit 的时候无意中扫过这么一段:
Note: By default, timeit() temporarily turns off garbage collection during the timing. The advantage of this approach is that it makes independent timings more comparable. This disadvantage is that GC may be an important component of the performance of the function being measured. If so, GC can be re-enabled as the first statement in the setup string.

我突然福至心灵,在代码里面禁止了自动垃圾收集,然后去测试,哈,问题立刻解决。不仅仅是丢掉的 100 秒找回来了,所有的操作速度都大大提高。原来分析5分钟数据要30秒的,现在10秒就搞定了!

这下就明白刚才的问题出在哪里了——在日志分析过程中创建出了大量的对象,而这些对象还都不能销毁,等待以后使用,结果就导致 python 频繁的 gc(陈儒的《Python源码剖析》里介绍是缺省每创建700个对象就执行一次 gc),而gc遍历队列是在线性增长的,这就表现为大量的时间被消耗掉,而且执行时间越来越长。

以前从来没有深入了解过 java,今天是第一次感受 gc 的作用 :PP

附:感觉还是 cProfile 比 hotshot 好,用 hotshot 很多系统内置函数的执行时间(比如字典的has_key, 字符串的 split)是看不到的。

Topic: 技术

实践证明,水杯是笔记本第一大杀手

上一次笔记本损坏是5年多前了,那时候还是租房住,给宝宝洗澡的时候把水弄到键盘上了,好像就此彻底挂掉。

周六,不小心把水泼在了俺的 vostro 1000 上。立刻关机。等它干了以后,能正常启动,就是键盘上有若干键不能使用。

首先尝试自己动手,拆下键盘,把出问题的几个键都拔下来,拿着电吹风吹啊吹(这里要附带说一下,它的键盘是防水设计——就是把水尽量积在键盘上,而不是往主板上流;这样的设计还是很科学的),吹完以后问题照旧。

于是拿起电话打400,人家周末只有电话应答。只好周一再打电话,就说是键盘坏了,支持工程师问了问安全模式和BIOS设置模式下键盘是否仍然有同样问题,然后就决定给我换键盘。

很快今天上来工程师过来,把键盘换了,然后说12层还有另一个搜狐的员工笔记本报修就上楼了——汗...

这半年 vostro 1000 用下来,觉得还成。足够稳定,Vista 也运行挺好。除了硬盘温度比较高,让人怀疑如果在炎热的湖北使用它,就要天天开空调了

上一台 Thinkpad R51e 已经安然用了2年多,实践证明大厂出的廉价机种还是很可靠的,现在希望这台 DELL 也能坚持 3-4 年的寿命。

Topic: 生活
订阅 RSS - qyb的博客