从140秒到2秒的优化

November 12th, 2009

从2亿个0~2,000,000,000之间的数字样本中找出不重复的记录总数,首先想到的是bloom filter,转念一想既然全都是数字,bloom filter有点太重,bitarray也许更有效,于是第一个版本出来,部分代码如下:

ba = bitarray(212**4)
cnt = 0
for i in data:
    if (not ba[i]):
        cnt += 1
        ba[i] = True
print cnt

大概需要140s左右,觉得if (not ba[i]):这个比较费,改了第二版:

for i in data:
    ba[i] = True
print ba.count()

速度有所提升,到了120s左右,开始打起多核运算的主意了,山寨了一个map-reduce,首先通过maper把数据按照除4得余分成4份:

def maper(data):
    map_data = (array('I'),array('I'),array('I'),array('I'))
    for i in data:
        m = i % 4
        map_data[m].append(i)
    return map_data

然后起了一个4个进程的woker pool分别计算,最后把结果汇总:

def worker(data):
    counter = bitarray(256**4)
    for i in data:counter[i] = True
    return counter.count()
 
p = Pool(4)
result = p.map(worker, data)

速度提高明显,到了50s左右,这个做法的问题是两次遍历:map的时候一次、reduce的时候又一次,于是开始想办法解决,把文件直接分开运算,不再map,把最后的结果做一下位或再计数:

p = Pool(4)
result = p.map(worker, data)
print (result[0] | result[1] | result[2] | result[3]).count()

到了26s左右,可能Python在进程间交换大数据量效率不是太好,再优化的空间有限,想起之前用Python的科学运算库做过数据挖掘,能不能用那个库试试,于是有了NumPy的版本:

import numpy as np
print len(np.unique(np.fromfile('/path/to/data.dat', np.uint32)))

全部程序就这两行,速度到了12s,让人崩溃,NumPy的底层大多是C的实现,对代码做了一个profile,发现NumPy用了sort,有点浪费,如果我用C实现一部分功能的话效果应该会不错,注意到代码中有for i in data,data中有2亿条,就循环调用了2亿次,尝试把这个调用都封装在C里面,使用C级别的循环,于是用C扩展了一下bitarray包:

static PyObject *
bitarray_fromarray(bitarrayobject *self, PyObject *pyo)
{
    unsigned int *l;
    idx_t n1;
    Py_ssize_t nbytes, nitems, i;
    if (PyObject_AsReadBuffer(pyo, (const void **)&l, &nbytes) != 0)
        return Py_False;
    nitems = nbytes/sizeof(unsigned int);
    for (i=0; i<nitems; i++) {
        *(self->ob_item + l[i] / 8) |= ((char) 1) << (l[i])%8;
    }
    n1 = count(self);
    return PyLong_FromLongLong(n1);
}

直接读取文件buffer到bitarray,python程序就变成了:

from bitarray import bitarray
counter = bitarray(212 ** 4)
fp = open('/path/to/data.datbk', 'rb')
un = counter.fromarray(fp.read())
print un

一共5行代码,速度到了2s内,收工。

Tags: ,

ColaPHP-0.3-alpha发布

November 7th, 2009

开发工作很久以前就基本完成了,一直没来得及整理,今天发布0.3alpha,相比较0.2alpha,比较大的修改如下:

  • Helper中增加了验证码模块Captcha.php、HTTP访问模块Http.php、数据校验模块Validate.php
  • 修订DB模块中result($sql)函数,如果$sql是select语句则返回结果集,如果是insert语句则返回最后插入ID,如果是update或者delete语句,则返回受影响行数(有可能为0行),其他语句则返回query句柄
  • 完善框架的易用性:增加了统一配置文件;可以指定models、views、controllers目录;支持默认模版名称
  • 大量的代码重构以及bug fix

下载0.3alpha,不过建议随时跟进我们的SVN://colaphp.googlecode.com/svn/trunk/,ColaPHP一直在活跃开发。

0.4alpha版本开发代号:20 lines,目标是把所有的函数都控制在20行以内以及代码的持续重构。

继续招募PHP极客加入Cola,联系fuchaoqun#gmail.com。

Tags:

jQuery表单校验

November 2nd, 2009

最近有个项目,用到很多的表单校验,尝试了一下jQuery.validator,甚是顺手,地址:http://plugins.jquery.com/project/validate,基本的用法:

$('#formId').validate({
    debug:true, // 打开debug模式,不会真实提交,适合测试
    rules:{
        uName:"required", // 需要录入
        uNick:{
            required:true
        }, // 需要录入的另一种写法
        uBlog:{
            required:true,
            url:true
        } // 需要输入,且录入的必须是url
    },
    messages:{ // 自定义提示语文字
        uName:"请输入用户名"
    },
    submitHandler:function(form) { // 表单提交,需要jqueyr.form插件
        form.submit();
    }
}

阅读一下文档差不多就会了解,支持的校验格式有:

required:必填字段

email:电子邮件

url:合法的网址

date:日期

dateISO:日期(ISO)

number:数字

digits:整数

creditcard:信用卡号

equalTo:相同的值

accept:拥有合法后缀名的字符串

maxlength:最多长度的字符串

minlength:最少长度的字符串

rangelength:一个长度介于最小值和最大值之间的字符串

range:一个介于最小值和最大值之间的值

max:最大值

min:最小值

相对高阶一点功能:

  • 自定义错误提示信息

    当然,你可以通过定义messages来修改提示信息,但你想偷懒又不想用默认的英文提示,直接引入默认提示文字

    <script src="path/to/localization/messages_cn.js" type="text/javascript"></script>
  • IE6下不工作的bug

    传闻IE6下,jquery.validate.js有乱码,不能正常工作,解决办法:

    <script src="path/to/jquery.validate.js" type="text/javascript" charset="iso-8859-1"></script>
  • 控制错误信息显示位置

    有的时候默认的错误信息显示有问题,比如多个单选框,默认的会把错误信息显示在第一个单选框后面,页面就乱了,解决办法:

    errorPlacement: function(error, element) {
        if (element.is(":radio")) {
            error.appendTo(element.parent().parent("td"));
        else {
            error.appendTo(element.parent());
        }
    }

    当然,还可以重载invalidHandler来实现

  • 依赖校验

    有的时候,一个输入框的校验依赖于其他条件,比如登录的时候可以通过用户名或者邮箱登录,只有当用户选择用邮箱登录时,才对邮箱输入框验证,rules中可以这样写:

    uMail:{
        required:{depends: function(element) {
            return true == $('#isMail').val();
        }},
        email:true
    }

    这样只有用户选择了用邮箱登录才会校验 邮箱项,否则不校验。

Tags: ,

PHP处理Etag、lastModified和Expires

September 5th, 2009

之前看到robbin基于资源的HTTP Cache的实现介绍,想到这是一个很有意思的功能,原理很简单,但很多人都会忽略,于是乎打算集成到ColaPHP框架中来,让浏览器缓存动态内容,对于一些由动态脚本生成、更新不频繁但又会被用户重复访问的页面内容,还是很有意义的。

如果在服务器端设置了Etag、lastModified和Expires之后,下次再访问同一资源的时候,一个典型的HTTP头是这样的:

Host            127.0.0.1
User-Agent        Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 QQDownload/1.7
Accept            text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language        zh-cn,zh;q=0.5
Accept-Encoding        gzip,deflate
Accept-Charset        GB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive        300
Connection        keep-alive
If-Modified-Since    Sat, 05 Sep 2009 12:44:56 GMT
If-None-Match        foobar
Cache-Control        max-age=0

关于lastModified、Etag和Expires的工作原理,可以参看http://longrujun.name/index.php/2009/03/04/etag%E5%92%8Cexpires/,简单来说:

lastModified:设定一个最后修改时间,浏览器下次访问的时候,发送一个”If-Modified-Sinc”的头信息,如果内容在这个时间之后没有更新,服务器直接返回一个304 Not Modified而不传输详细内容,可以节省带宽。

Etag:设定一个标记,浏览器下次访问时,发送一个”If-None-Match”的头信息,如果服务器内容还是这个标记没变,也直接返回一个304 Not Modified而不传输详细内容,同样可以节省带宽。

Expires:设定一个过期时间,如果当前请求在这个过期时间之类,则不发送HTTP请求,不仅可以节约服务器带宽,还可以减少服务器HTTP请求数

主要通过header函数来发送,比较简单,直接上代码:

public static function etag($etag, $notModifiedExit = true)
{
    if ($notModifiedExit && isset($_SERVER['HTTP_IF_NONE_MATCH']) && $etag == $_SERVER['HTTP_IF_NONE_MATCH']) {
        self::statusCode('304');
        exit();
    }
    header('Etag: ' . $etag);
}
 
public static function lastModified($modifiedTime, $notModifiedExit = true)
{
    $modifiedTime = date('D, d M Y H:i:s', $modifiedTime) . ' GMT';
    if ($notModifiedExit && isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $modifiedTime == $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
        self::statusCode('304');
        exit();
    }
    header("Last-Modified: $modifiedTime");
}
 
public static function expires($seconds = 1800)
{
    $time = date('D, d M Y H:i:s', time() + $seconds) . ' GMT';
    header("Expires: $time");
}

如果你是用ColaPHP,可以直接在controller里面写上:

// 设定最后修改时间,通常是数据库中内容修改时间
$this->helper->response->lastModified(1252154696);
// 设定内容标记,自己可以按照一定的规则来生成,当然也可以用内容最后修改的时间戳
 $this->helper->response->etag('foobar');
// 设定失效时间
$this->response->expires(10);

PS:ColaPHP近期发0.3alpha,主要改进Model和DB设计,加入了一下小功能(比如验证码),以及bugfix。

Tags: , , ,

ColaPHP-0.2-alpha发布

July 28th, 2009

相比较0.1alpha,比较大的修改如下:

  • 增加了Cache处理模块,支持APC、eAccelerator、Memeched、File、Xcache、Dba缓存
  • 增加日志处理模块,支持Echo、File、Null三种类型
  • 去掉了Cola.php中404处理,建议用户在url规则最后的位置加上’/.*/’来处理其他任何没有匹配的请求
  • demo做了一点小的修改,有一个演示不需要定义url规则、跳过Router.php的方法(参见demo/norouter.php)
  • 一些bug的修订

同时欢迎Python.Han, darkredz加入到项目中来,有你们ColaPHP会更精彩。

下载0.2alpha,不过建议随时跟进我们的SVN://colaphp.googlecode.com/svn/trunk/,ColaPHP一直在活跃开发。

继续招募PHP极客加入Cola,联系fuchaoqun#gmail.com。

ColaPHP-0.1-alpha发布

June 19th, 2009

非常简陋的一个PHP框架,只是把架子搭起来了,地址:http://code.google.com/p/colaphp/

现在PHP框架已经很多了,为什么还要去”重复的发明轮子”?

  • 你和我一样不想重新学习一门”框架语言”
  • 你和我一样希望规范的MVC开发
  • 你和我一样希望一个高性能的框架
  • 你和我一样不希望改变已有的PHP开发方式

现在,ColaPHP还很不成熟,暂且当做一个”玩具”试试,有兴趣的可以阅读一下代码,品味好的代码和指出坏味道的代码都是一个好的过程。

文档方面现在还很不全,我希望只用一个Tutorial就能讲明白,以后也不会有别的新的文档(文档越多,表明系统越复杂,学习成本也越高),当然,Tutorial会是一个持续完善的过程。

ColaPHP是写给PHP程序员的一个框架,信奉KISS的同学可以试试

Tags: , ,

智能推荐系统

May 18th, 2009

断断续续的做了大半年歌曲相关性方面的试验,有一些肤浅的体会,工程方面多些,学术上仍很不足,与大家分享,望后来之君少走弯路,抑或被我带入岐途,不当之处,请指正。

PS:前段时间申请了个独立域名:http://www.fuchaoqun.com,烦请订阅feed者更新:http://www.fuchaoqun.com/feed/,原http://chaoqun.17348.com作了301跳转,将来一段时间内亦能访问。

Tags: , , ,

高效的MySQL分页

April 29th, 2009

PERCONA PERFORMANCE CONFERENCE 2009上,来自雅虎的几位工程师带来了一篇”Efficient Pagination Using MySQL“的报告,有很多亮点,本文是在原文基础上的进一步延伸。

首先看一下分页的基本原理:

mysql> explain SELECT * FROM message ORDER BY id DESC LIMIT 10000, 20\G
***************** 1. row **************
id: 1
select_type: SIMPLE
table: message
type: index
possible_keys: NULL
key: PRIMARY
key_len: 4
ref: NULL
rows: 10020
Extra:
1 row in set (0.00 sec)

limit 10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里,如果是limit 100000,100,需要扫描100100行,在一个高并发的应用里,每次查询需要扫描超过10W行,性能肯定大打折扣。文中还提到limit n性能是没问题的,因为只扫描n行。

文中提到一种”clue”的做法,给翻页提供一些”线索”,比如还是SELECT * FROM message ORDER BY id DESC,按id降序分页,每页20条,当前是第10页,当前页条目id最大的是9527,最小的是9500,如果我们只提供”上一页”、”下一页”这样的跳转(不提供到第N页的跳转),那么在处理”上一页”的时候SQL语句可以是:

SELECT * FROM message WHERE id > 9527 ORDER BY id ASC LIMIT 20;

处理”下一页”的时候SQL语句可以是:

SELECT * FROM message WHERE id < 9500 ORDER BY id DESC LIMIT 20;

不管翻多少页,每次查询只扫描20行。

缺点是只能提供”上一页”、”下一页”的链接形式,但是我们的产品经理非常喜欢”<上一页 1 2 3 4 5 6 7 8 9 下一页>”这样的链接方式,怎么办呢?

如果LIMIT m,n不可避免的话,要优化效率,只有尽可能的让m小一下,我们扩展前面的”clue”做法,还是SELECT * FROM message ORDER BY id DESC,按id降序分页,每页20条,当前是第10页,当前页条目id最大的是9527,最小的是9500,比如要跳到第8页,我看的SQL语句可以这样写:

SELECT * FROM message WHERE id > 9527 ORDER BY id ASC LIMIT 20,20;

跳转到第13页:

SELECT * FROM message WHERE id < 9500 ORDER BY id DESC LIMIT 40,20;

原理还是一样,记录住当前页id的最大值和最小值,计算跳转页面和当前页相对偏移,由于页面相近,这个偏移量不会很大,这样的话m值相对较小,大大减少扫描的行数。其实传统的limit m,n,相对的偏移一直是第一页,这样的话越翻到后面,效率越差,而上面给出的方法就没有这样的问题。

注意SQL语句里面的ASC和DESC,如果是ASC取出来的结果,显示的时候记得倒置一下。

已在60W数据总量的表中测试,效果非常明显。

Tags: ,

开心网(kaixin001)的首页为什么这样设计?

April 24th, 2009

不知大家有没有发现,就算你选择了”记录登录状态”,下次访问kaixin001.com的时候还是官方静态首页,过一会才跳转到个人首页,这样的用户体验反正我是不太舒服,查看kaixin001首页源代码,发现个中蹊跷:

function _bodyonload()
{
    ....
    var v_kx = getCookie('_kx');
    if (v_kx.length)
    {
        ....
        v_timeid = setTimeout("gotohome()", 2000);
    }
}
 
function gotohome()
{
    if (v_timeid)
    {
        window.location = "/home/?l=a";
    }
}

代码翻译成白话就是:页面加载完毕后,检测是否有’_kx’的cookie,如果有的话2秒后跳转到’/home/?l=a’页面,有的时候页面加载时间就很长,再等个两秒跳转,你都恨不得重新登录。

不知道kaixin001设计的时候是如何考虑的,从用户体验的角度来说:囧;从技术的角度来说:相当囧,仅仅是增加了服务器请求数(第一次请求首页白瞎),不知道kaixin001慢是不是这个原因,可能有更囧的。

很多时候我们会需要处理登录状态和非登录状态,一般可以在程序层面判断,比如PHP:

<?php
if (empty($_COOKIE['_kx'])) {
 
    // 显示官方首页
 
}else {
 
    // 显示个人首页,记得校验cookie
 
}
?>

kaixin001也是一个很大的SNS网站了,关于她的架构,未曾见诸网上,也许应该更开放一些,不过kaixin001网站整体的速度不太好,不知道是人太多、服务器太少,还是说存在伸展的问题。

用YSLOW测试了一下kaixin001″我的首页”,得分是F(49),很多js没有压缩,js文件数有18个之多,css文件数5个,js文件放头部等等,同期测试豆瓣的”我的豆瓣”,得分C(79),一直觉得douban的技术还是挺牛的,也比较开放。

Tags: , ,

解决Pidgin掉线、自动退出、假死、无响应

April 14th, 2009

最近几天,被pidgin折腾疯了,一会上线,一会自动退出,一会假死,看到好友发过来消息,可阅读不了具体内容,折腾的身心疲惫,升级到最新版本也无济于事。

由于在pidgin上登录了msn和gtalk帐号,初步怀疑是MSN插件的问题,貌似MSN之前升级了版本。禁用MSN帐号后,pidgin恢复稳定。

郁闷的不行了,找到一个第三方的MSN插件:http://code.google.com/p/msn-pecan/

wget http://msn-pecan.googlecode.com/files/msn-pecan-0.0.18.tar.bz2
tar xjf msn-pecan-0.0.18.tar.bz2 -C /usr/local/src
cd /usr/local/src/msn-pecan-0.0.18/
make
make install

安装之前记得安装libpurple-devel

yum install libpurple-devel

安装好了之后,pidgin中会出现WLM协议,用这个做MSN登录,世界总算清静下来了。

我这边是CentOS 5.3,Pidgin 2.5.5-1.el5,其他版本安装说明,阅读http://code.google.com/p/msn-pecan/wiki/HowToInstall