QQ音乐电台Chrome扩展

腾讯终于抄到豆瓣电台了,推出QQ音乐电台

无聊看了下QQ音乐电台的前端代码,发现在Chrome上用了HTML5技术了,而且前端js代码写得不错,看着很舒服,赞一个。
顺手做了个Chrome扩展,QQ音乐电台Chrome扩展

暂时只实现了听歌的核心功能。


Python2.x subprocess.Popen的Broken pipe解决方法

考虑如下代码:

#!/usr/bin/env python
p1 = Popen(["cat", "somebigfile"], stdout = PIPE)
p2 = Popen(["head"], stdin = p1.stdout, stdout = PIPE)
print p2.communicate()[0]

会出现

cat: write error: Broken pipe

原因是在Unix-like系统中,Python启动就会SIG_IGN掉SIG_PIPE,致使cat获取不了SIG_PIPE信号,还向已关闭的管道写入。很久之前就有人提出这个BUG了,这个BUG在Python 3.x已经修复,但是一直没有在Python 2.x实现。

解决方法如下:
1. 下载对应版本的Python源码,找到Lib\subprocess.py(我发现2.6.x的跟2.7.x的就不同)
2. 把该文件放到项目文件夹,改名为sub.py
3. 作如下改动:

--- /usr/lib/python2.6/subprocess.py    2010-04-16 21:58:41.000000000 +0800
+++ sub.py      2011-08-26 17:45:57.576284992 +0800
@@ -429,7 +429,7 @@
     import fcntl
     import pickle
 
-__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"]
+__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError", "_eintr_retry_call"]
 
 try:
     MAXFD = os.sysconf("SC_OPEN_MAX")

4. 新建new_subprocess.py, 建立Popen_new类,继承原来subprocess的Popen,并把sub.py里面Popen类的POSIX版本的_execute_child方法copy过来。

5. 在

if cwd is not None:
	os.chdir(cwd)

后面加上如下代码:

import signal
signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ')
for sig in signals:
	if hasattr(signal, sig):
		signal.signal(getattr(signal, sig), signal.SIG_DFL)

(此处有一个示例,Python 2.6.5版本的)

6. 在项目里

from new_subprocess import Popen_new as Popen

即可


下载youku等视频网站的视频并快速转换成适合iPhone4/iPod Touch4播放的格式

想下载一些电视剧进iTouch4播放,找不到合适的片源下载,就打起视频网站的主意。优酷那些所谓“超清”的视频清晰度还不错,适合用来做片源

下载视频

下载优酷等视频网站的视频,可以使用硕鼠,一个免费,没有恶意插件的视频下载软件。支持很多视频网站。把优酷页面的链接放进去解析,得到N个链接,可以在硕鼠中下载。下载完毕后,硕鼠自带合并功能,把N个分视频合成一个视频,就得到一集的flv文件。

转换

查看flv文件,发觉编码已经是H264/AAC,音视频的编码已经适合在苹果设备播放,但在avplayer播放flv会有点卡,而且基本不能拖动播放,即使开了硬件加速。按理iTouch4是支持H264视频硬件加速的,应该是avplayer对flv格式支持不太好。其他视频播放软件没有尝试,iPad上有迅雷看看和QQ影音HD,可惜iTouch上暂时没有这种免费的多视频格式播放软件。因为视频编码已经是H264,所以想找一款软件能复制音视频流,只是换一个封装格式。

之前分别试过MediaEncoder、曦力音视频转换专家两个软件。MediaEncoder功能很强大,而且支持CUDA加速,但设置起来很繁琐,需要有一定音视频的知识。它也推出针对不同设备简化配置的版本。但用起来,即使选中复制音视频流,但还是耗时很久,中途还报错,多次反复尝试无果,只能放弃。

曦力音视频转换专家是Xilisoft对中国地区推出的为多种移动设备优化的音视频转换软件。当年用iPod Mini时就有xilisoft的软件来放歌进iPod了。可惜没找到复制音视频流的选项,而转换过程耗时很长,即使启用了CUDA加速。虽然最终转换出来的结果能顺利在iTouch4流畅播放,但耗时长仍然不可忍受,本来视频编码就是合适的啊。

经过一阵Google,找到FLV Extract这款开源小软件,有GUI和命令行版本。GUI使用很简单,把flv文件拖进去,就会生成同名的.264, .aac, .txt文件,分别是视频流、音频流和Timecodes文件。另外一款软件MP4box,则可以把之前生成的文件封装成mp4。只需要执行以下命令行

mp4box -add "filename.264:fps=25.0039" -add "filename.aac" "filename_output.mp4"

注意有fps设置,fps指的是帧率,可以在FLV Extract导出时看到。

封装过程很快。至此,一个适合在苹果设备播放的视频就制作好了,在avplayer里可以看到这文件显示成QuickTime的图标。上传,播放,流畅,拖动后立即播放。


记项目Python-MySQL访问类的优化

背景

接手一些Python项目的后续开发与维护,发现这些项目都用同一个数据库访问类,而生成的结果行竟然是用list存的,一个简单的row[‘id’]访问需要遍历整行去找,遂优化之

改写成dict

一般访问数据行的字段都是使用字段名访问,显然应该使用dict。但也有可能使用数字下标访问,例如对于select count(*)的结果集,可能就使用rs[0][0]访问了。因此还需要一个list去存结果集的字段名顺序。

其实MySQLdb Driver有DictCursor可直接获得dict结果。当结果集的列有重复字段,第二个重名字段会加上表别名前缀。例如select * FROM tableA as A, tableB as B,两个表都有字段id,tableB的id会使用B.id这个key。
对于可以用下标访问的需求,也需要额外加个list来存字段名顺序。这个list是从cursor的description生成的。问题在于cursor的description里,tableB的id的字段名仍然是id,当想用下标访问tableB的id时,查找list获得id这个字段名,得到的却是tableA的结果。暂时没有想到解决这个问题的方法。因此还是使用默认cursor,手动生成结果集。

结果测试

改写成dict后,测试耗时只有原来的50%。对于列很多的结果集,从原来遍历整行O(n)才拿到结果,变成O(1),改善还是很可观的。

使用Cython进一步加速

在纯Python的基础上,已经很难再有提高了。考虑使用Cython将其编译成C extension。对于数据库访问类这种“底层”的类,平时很少会改动,但使用非常频繁,进一步优化还是值得的。

对于能确定静态类型的地方,例如for循环中的i,自定义的Python class,都用cdef指定类型。对于参数或者返回值中有能确定静态类型的Python函数,也可以使用cpdef修饰。

编译后测试,耗时只有改成dict后版本的1/7左右,是原始未优化版本的6%左右,速度提升有15倍!

项目实测

以上的测试结果只是纯跑分的测试,实际效果还是要放到本来的项目体现。找了一个报表的调用,数据库查询结果是5k行。

$ time -p curl -s http://app08.gz/fd/data/pilfered_time_txt/?btime=2011-07-04\&etime=2011-07-11\&product=mh\&get_state= | wc -l
5737
real 4.48
user 0.01
sys 0.22
$ time -p curl -s http://localhost:8004/fd/data/pilfered_time_txt/?btime=2011-07-04\&etime=2011-07-11\&product=mh\&get_state= | wc -l
5737
real 2.42
user 0.02
sys 0.13

上面的是未优化版本的结果
下面的是使用c extension版本的结果,大约有45%的提升。

PS:
在import cython生成的so时,曾经遇到ImportError

>>> import db
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: dynamic module does not define init function (initdb)

解决方法是注意生成cython扩展的setup.py,ext_modules里面的Extension的第一个参数必须是你将来要import的名字。例如我要import db,那么就要写成:

ext_modules = [Extension("db", ["db.pyx"])]

对于生成的so,可以使用

strings db.so | grep db

来查看是否正确生成了initdb这个入口。


Python序列化库效率对比

Python内置marshal, cPickle等序列化库,但cPickle效率不太理想,marshal文档也说不保证版本兼容性。今天在列表中看到几个第三方库,故自己测试下:

测试脚本:

#!/usr/bin/env python

import sys,os,time
import cPickle
import marshal
import shelve
import tnetstring
import msgpack

def get_dict():
    d = {}
    for i in xrange(500000):
        d[i] = "abcd" * 10

    return d

import time, functools

def timeit(func):
    @functools.wraps(func)
    def __do__(*args,**kwargs):
        start = time.time()
        result= func(*args,**kwargs)
        print '%s used time %ss'%(func.__name__,time.time()-start)
        return result
    return __do__

@timeit
def marshal_dump(d):
    return marshal.dumps(d)

@timeit
def marshal_load(s):
    return marshal.loads(s)

def marshal_test(d):
    s = marshal_dump(d)
    marshal_load(s)

@timeit
def cPickle_dump(d):
    return cPickle.dumps(d, cPickle.HIGHEST_PROTOCOL)
@timeit
def cPickle_load(s):
    return cPickle.loads(s)

def cPickle_test(d):
    s = cPickle_dump(d)
    cPickle_load(s)

@timeit
def tnetstring_dump(d):
    return tnetstring.dumps(d)

@timeit
def tnetstring_load(s):
    return tnetstring.loads(s)

def tnetstring_test(d):
    s = tnetstring_dump(d)
    tnetstring_load(s)

@timeit
def msgpack_dump(d):
    return msgpack.packb(d)

@timeit
def msgpack_load(s):
    return msgpack.unpackb(s)

def msgpack_test(d):
    s= msgpack_dump(d)
    msgpack_load(s)

def main():
    d = get_dict()
    marshal_test(d)
    cPickle_test(d)
    tnetstring_test(d)
    msgpack_test(d)

if __name__ == "__main__":
    main()

在一台Ubuntu 10.04 32bit,双核Intel(R) Xeon(TM) CPU 3.06GHz机器上,有如下成绩:

marshal_dump used time 0.18213891983s
marshal_load used time 0.335217952728s
cPickle_dump used time 2.73869895935s
cPickle_load used time 0.558264017105s
tnetstring_dump used time 0.622045040131s
tnetstring_load used time 0.311910152435s
msgpack_dump used time 1.07127404213s
msgpack_load used time 0.27454996109s

结论

  • cPickle最慢,基本上可以抛弃
  • marshal虽然有不兼容隐患,但如果能保证相同的Python版本,还是可以一用
  • tnetstring无论序列化或反序列化速度都比较中庸
  • msgpack反序列化最快,如果读多写少的应用场景是最佳选择。而且msgpack支持多语言。

Python中带有显式__del__方法的对象需要手动释放循环引用

Python中有自动gc,这个gc在一般情况下也可以清除循环引用的对象。

不过有个例外的情况:一个对象显式定义了__del__方法。

例如如下的代码:

#!/usr/bin/env python

class Foo:
    def __init__(self):
        self._bar = {"test": self.test}
        print "construct"

    def test(self):
        print "test"

    def __del__(self):
        print "del"

f = Foo()
del f

运行结果是不会打印”del”的。

文档里面也有写到:
http://docs.python.org/library/gc.html#gc.garbage

gc.garbage
A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects). By default, this list contains only objects with __del__() methods. [1] Objects that have __del__() methods and are part of a reference cycle cause the entire reference cycle to be uncollectable, including objects not necessarily in the cycle but reachable only from it. Python doesn’t collect such cycles automatically because, in general, it isn’t possible for Python to guess a safe order in which to run the __del__() methods. If you know a safe order, you can force the issue by examining the garbage list, and explicitly breaking cycles due to your objects within the list. Note that these objects are kept alive even so by virtue of being in the garbage list, so they should be removed from garbage too. For example, after breaking cycles, do del gc.garbage[:] to empty the list. It’s generally better to avoid the issue by not creating cycles containing objects with __del__() methods, and garbage can be examined in that case to verify that no such cycles are being created.

当一个对象显式定义了__del__方法,而且里面有循环引用,Python不会自动回收这个对象。如果这种情况没有正确处理,会造成内存泄漏。

解决的办法是在__del__中手动解除循环引用,或者干脆避免这种有循环引用的写法。

这个问题导致数据库连接不能释放,终于在周末晚上数据库连接爆了,丢脸啊。

PS: 这次事件也再一次显示“分层架构互不信任防雪崩策略”的重要性。这个出事的DB是一个slave,它的max_connection和max_user_connection是一样的,而且连接超时竟然是8小时……如果max_user_connection设小点,顶多是使用这个用户的系统挂掉,而不会是所有使用这个DB的系统都挂掉,把影响隔离起来。


在命令行中安装KVM ubuntu 10.04虚拟机

一般虚拟机都很容易安装,尤其是使用有图形界面的虚拟机软件。
但在某些场合,只能命令行安装。

现在linux流行的虚拟机软件有Xen\KVM等。ubuntu自从9.04开始,从源中去掉Xen,转为提供KVM。本文记录下命令行安装KVM虚拟机的过程。
Host主机是在Ubuntu 11.04,安装的Guest主机将使用Ubuntu 10.04

准备

sudo apt-get install kvm libvirt-bin virtinst

你还需要有一个Ubuntu 10.04 的ISO,我选用的是server 32位版。然后把iso mount起来

sudo mkdir /media/iso
sudo mount -o loop /media/1T/ISO/linux/ubuntu-10.04.2-server-i386.iso /media/iso
cd /media/iso
python -m SimpleHTTPServer

使用python命令可以以当前目录为根目录,创建一个简易HTTP Server,留作之后的步骤使用。

创建虚拟机

将以下内容写到一个bash脚本,如build.sh:

#!/bin/bash

if [ "$#" -ne 2 ]; then
        echo "Usage: $0  "
        exit 0
fi

name=$1
file=$2

virt-install -n ${name} \
-r 512 --vcpus=2 --nographic \
--os-type=linux --os-variant=ubuntuLucid \
--disk path=${file},size=5 \
-v --arch=i386 -d \
--connect qemu:///system \
--accelerate \
--location http://localhost:8000/ubuntu  \
--extra-args="text console=tty0 utf-8 console=ttyS0,115200"

使用

./build.sh datanode1 vm1.img

即可开始创建Guest OS,过程中会有一段时间黑屏,KVM进程会占用大量CPU,这是正常的,请耐心等待。

很快会进入到命令行安装Ubuntu的界面,按照向导操作即可。其中有一步让你选安装什么包,请选上OpenSSH,当然你也可以之后再安装。(其中有一步是选择源镜像,按照某文章,是可以用iso mount起来,然后建立HTTP服务器来作为源的,但我尝试失败了,如果你知道,请告诉我)

经过漫长的从网上源下载安装后,Ubuntu Guest OS就已经装好了。

配置虚拟机

使用virsh命令可以进入虚拟机管理shell,list –all命令可以列出现有的虚拟机。
使用start datanode即可启动虚拟机,使用console datanode即可把当前console连接上虚拟机。

登录进虚拟机后,查看下IP。推荐使用ssh登录虚拟机而不是console,console模式在vi编辑时会有问题,应该是shell的问题,懒得深究了。
默认KVM的虚拟机会使用NAT/DHCP配置,这里我把它改成Static IP,方便之后ssh登录。

配置静态IP

sudo vi /etc/network/interfaces

将iface eth0 inet dhcp那行注释掉,配置如下:

#iface eth0 inet dhcp
iface eth0 inet static
address 192.168.122.101
netmask 255.255.255.0
gateway 192.168.122.1

然后执行命令

sudo ifdown eth0 && sudo ifup eth0

创建第二个虚拟机

有了第一个虚拟机,如果需要创建另一个一模一样的虚拟机,可以使用virt-clone命令。

virt-clone --connect=qemu:///system -d -f vm2.img -o datanode1 -n datanode2

复制好之后,需要修改一些地方以便与之前源虚拟机区分开来。
通过console连接上datanode2,编辑/etc/hosts和/etc/hostname,修改datanode1为datanode2
然后执行

sudo rm /etc/udev/rules.d/70-persistent-net.rules
sudo reboot

重启后即可让虚拟机生成自己的网络配置

重复上一节的配置静态IP步骤,并在Host主机内配置hosts指向Guest OS的地址。至此,多个虚拟机的配置即告完成。

参考:


《Erlang程序设计》第8章习题一解

习题1:

-module(ex1).
-compile(export_all).

main(_) ->
    start(abc, fun() -> io:format("Bing~n") end),
    start(abc, fun() -> io:format("Bang~n") end).

start(AnAtom, Fun) ->
    R = self(),
    spawn(
        fun() ->
            try register(AnAtom, self()) of
                true->
                    R ! true,
                    Fun()
            catch
                error:_ ->
                    R ! false
            end
        end
    ),

    receive
        true -> true;
        false -> io:format("false ~n")
    end.

习题2:

-module(ex2).
-compile(export_all).

main() ->
	start(1000, 10),
	start(1000, 20),
	start(2000, 10).

start(N, M) ->
	statistics(runtime),
	statistics(wall_clock),
	L = create(N, []),
	post(haha, L, M),
	{_, Time1} = statistics(runtime),
	{_, Time2} = statistics(wall_clock),
	io:format("Total time : ~p(~p) ~n", [Time1*1000, Time2*1000]).

create(0, L) ->
	L;

create(N, L) ->
	Pid = spawn(fun loop/0),
	create(N-1, [Pid|L]).

loop() ->
	receive
		Any ->
			io:format("Msg arrived: ~p~n", [Any]),
			loop()
	end.

post(_ , _, 0) ->
	void;

post(Msg, L, M) ->
	%io:format("M = ~p~n", [M]),
	[ H ! Msg || H <-L],
	post(Msg, L, M-1).

python中实例动态绑定的方法访问私有方法

python有一个编译好的模块,需要增加一个方法。由于不想修改源代码再编译,所以使用动态绑定方法来给实例增加方法。

第一印象,想到使用如下方法:

def foo(self):
	print self.name

a = A()
a.foo = foo
>>> a.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 argument (0 given)

结果是无法访问实例的变量。 比较新绑定的方法与原有的实例方法,发现原有的实例方法是bound method。只有bound method才能访问实例的变量。

要动态为实例绑定方法,可以使用new模块(http://docs.python.org/library/new.html)。 (文档中说new模块已经过期,推荐使用types模块。但我看types的文档,想不明白如何取代new模块)

import new
a.foo = new.instancemethod(foo, a, A)

问题又来了,新加的方法里有调用实例的私有函数(以双下划线开头),报了如下错误:

class A():
    def __private(self):
        print "private"

    def public(self):
        self.__private()

def foo(self):
    self.__private()

a = A()
import new
a.foo = new.instancemethod(foo, a, A)
a.foo()
Traceback (most recent call last):
  File "E:tmptest.py", line 14, in <module>
    a.foo()
  File "E:tmptest.py", line 9, in foo
    self.__private()
AttributeError: A instance has no attribute '__private'

通过观察原有方法和动态绑定方法的字节码,发现LOAD_ATTR有差别。原有方法的LOAD_ATTR是“_A__private”,动态绑定的方法的LOAD_ATTR是“__private”

class A():
    def __private(self):
        print "private"

    def public(self):
        self.__private()

def foo(self):
    self.__private()

a = A()
import dis
dis.dis(a.public)
a.public()

import new
a.foo = new.instancemethod(foo, a, A)
dis.dis(a.foo)
a.foo()
  6           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (_A__private) # a.public
              6 CALL_FUNCTION            0
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
private
  9           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (__private) # a.foo
              6 CALL_FUNCTION            0
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

这里的原因是python会对private方法进行名字粉碎(name mangling) 。因此修改foo方法里面的调用为self._A__private(),通过。但这样修改后,方法对于不同的class会不通用,有待研究更好的方法


javascript在回调中读取本地变量

有时候我们需要在回调中读取本地变量。例如:

//jQuery
function foo() {
    var a = 1;
    $.get("bar.php", function(res){
        alert(a); // undefined
    });
}

这时回调函数的上下文已经不是foo函数的内部了。想要在回调函数读取本地变量,需要为回调函数创造一个包含本地变量的上下文。

javascript的基本的上下文范围是函数作用域,我们可以新建一个函数,里面包含本地变量的副本。然后返回回调,从而创造出一个包含我们预设上下文的回调函数。

这就需要用一个函数生成一个函数。在javascript可以如此实现:

function foo() {
    var a = 1;
    var func = (function(a_copy) {
        return function(res) {
            //real callback
            alert(a_copy);
    })(a);
    $.get("bar.php", func);
}