Friday, October 22, 2010

离开

这个十月,我离开了那个熟悉的城市。经历了一些事情,认识了一些人,想说的太多,但是在这个离别的时候,却又什么都不想说。就如散伙饭上接连的23杯酒一样,意识模糊却又感受深刻...

朋友们,珍重!虽然写在这里,可能你们都看不见.

这个十月,我来到了这个陌生的城市,陪伴可爱的兜兜。这个城市很大,但是也很疏离...给自己定了两年的目标,希望一步一步脚踏实地能够走好.越发的意识到自己肩上的责任和压力是那么的清晰和重大。

这份新生活的开始,我需要对伟铭大哥道一声谢谢,虽然你也可能看不见:)

说点和标题相关的吧,这个博客将不再更新.新的博客地址是 http://guangfeng.blogspot.com 依旧在伟大的"X"之外...

END

Friday, May 14, 2010

并行计算框架 ParallelPython

如何在有限的硬件条件下,使程序耗时更少,或者处理数据量更大?

这个问题在进入多核时代以来,似乎多了一种解决途径,那就是充分利用多处理器(核)来并行的完成计算任务.

这里的多处理环境可以是指一台拥有多核处理器甚至多颗处理器的服务器,也可以是指一个拥有多节点的计算机群。


针对开篇提出的那个问题,解决办法其实都是"分而治之"。从数据分割入手,我们可以得到类似MapReduce的解决方案,类似的Python实现也有不少;从程序角度去看,目前主要是以下几种途径:
  • 大规模并行处理系统(MPP,Figure 1)
  • 对称多处理(SMP,Figure 2)
  • 分布式计算(集群/网格计算,Figure 3)



 Figure 1 MPP
Figure 2 SMP
 
 Figure 3 Cluster

本文主要介绍的ParallelPython(简称pp)框架可以有效支持SMP和集群方式进行并行计算。

根据官方介绍,pp提供了在SMP(多CPU或多核)和集群(通过网络连接的多台计算机)上并行执行Python代码的机制,具有以下特性:
  • 在SMP和集群上并行执行Python代码
  • 易于理解和实现的基于工作的并行机制,便于把穿行应用转换成并行的
  • 自动构造最佳配置(默认时工作进程数量等同于系统处理器数量)
  • 动态处理器分配(允许运行时改变工作处理器数量)
  • 函数的工作缓存(透明的缓存机制确保后续调用降低负载)
  • 动态负载均衡(任务被动态的分配到各个处理器上)
  • 基于SHA的连接加密认证
  • 跨平台移植(Windows/Linux/Unix)
和传统的线程模型不同的是,thread和threading模块无法在字节码一级实现并行。因为Python解释器使用GIL(全局解释器锁)来在内部禁止并行执行。这个GIL限制你在SMP机器上同一时间也 只能执行一条字节码指令。而pp在内部使用进程和进程间通信来组织并行计算。并隐藏了所有内部的细节和复杂性,应用程序只需要提交工作任务并取回结果就可以了。

代码说话:
import pp
nodes = ('10.0.0.1',)
jober = pp.Server(ppservers=nodes)
f = jober.submit(func,args,depfunc,module)

submit函数接受worker具体执行的函数func,参数args以及func内部调用的函数depfunc和涉及到的模块module

相应的节点机器上执行:
./ppservers.py

不过节点机器上可能会报错"Socket connection is broken".解决办法如下:

class Close(Exception):
    pass

def send(self, data):
        bufsz = self.bufsz
        t_size = len(data)
        size = struct.pack('!Q', t_size)
        p_size = self.socket.send(size)
        if p_size == 0:
            raise Close('end connection')

        s_size = 0L
        while s_size < t_size:
            nd_sz = min(bufsz, t_size - s_size)
            p_size = self.socket.send(data[s_size:s_size+nd_sz])
            if p_size == 0:
                raise Close('end connection')
            s_size += p_size

Thursday, May 13, 2010

并发编程利器Eventlet

Eventlet是由第二人生(Secondlife)开源的高度伸缩性的Python网络编程库.


根据官方介绍大致特性如下:
  • 非阻塞I/O模型
  • 协程(Coroutines)使得开发者可以采用阻塞式的开发风格,却能够实现非阻塞I/O的效果
  • 隐式事件调度,使得可以在Python解释器或者应用程序的某一部分去使用Eventlet


关于协程,大致可以理解成允许子程序可以多次暂停和恢复执行,是实现多任务的一种有效手段,具体见这里


在Python的世界里,实现了nonblocking I/O的产品并不算少.比如内置的Asyncore和著名的Twisted.相比之下,Eventlet是更容易上手和使用的。


举个例子

import eventlet
pool = eventlet.GreenPool()
while True:    pool.spawn(func,args)

上面这段代码,几乎就是使用eventlet的范式:
  • GreenPool 用来实现协程,保证并行;
  • Spawn     用来调用相应的函数,完成具体业务.
每个func之间切换,实施“你运行一会、我运行一会”,并且在进行切换时必须指定何时切换以及切换到哪,当出现阻塞时,就显式切换到另一 段没有被阻塞的代码段执行,直到原先的阻塞状况消失以后,再人工切换回原来的代码段继续处理.


Eventlet内置提供了一个基于上述原理实现的数据库连接池,目前仅支持MySQL和PostgreSQL.为了测试其性能如何,我参考了gashero的这篇文章,并简化了测试方案.


测试对象分别是MySQLdb(MySQL驱动的Python封装),Eventlet.db_pool,DBUtils


测试代码如下:
import time
import random
import MySQLdb
import eventlet.db_pool as db_pool
from DBUtils.PooledDB import PooledDB

conn_kwargs={'host':'192.168.8.84','user':'root','passwd':'','db':'logs'}
sql="""SELECT * FROM test WHERE id=%d"""
pooled=db_pool.ConnectionPool(MySQLdb,**conn_kwargs)
pooldb=PooledDB(MySQLdb,**conn_kwargs)

def query(conn):
    cur=conn.cursor()
    cur.execute(sql%(random.randint(1,1000)))
    data=cur.fetchall()
    return cur

def print_now():
    print time.strftime("%H:%M:%S")
    return

def test1(times):
    print_now()
    for i in range(0,times):
        conn=MySQLdb.connect(**conn_kwargs)
        r = query(conn)
        r.close()
        conn.close()
    print_now()
    return

def test2(times):
    print_now()
    for i in range(0,times):
        conn=pooled.get()
        try:
            query(conn)
        finally:
            pooled.put(conn)
    print_now()
    return

def test3(times):
    print_now()
    for i in range(0,times):
        conn=pooldb.connection()
        r=query(conn)
        r.close()
        conn.close()
    print_now()
    return


然后进入Python解释器交互环境
Python -i db-pool-test.py
>>> test1(10000) //MySQLdb
16:04:34
16:11:25
>>> test2(10000) //Event
16:12:35
16:15:22
>>> test3(10000) //DBUtils
16:15:28
16:18:09

总体来看,和传统的MySQLdb相比,性能有了很大的提升,和DBUtils差别并不是很明显.


协程凶猛啊!

Tuesday, April 27, 2010

通过权重评分得到更美味的番茄炒蛋

哼哼,我首先声明这不是一篇cookbook文:)

在今天互联网上,我们可以广泛的看到用户反馈(评分)系统的身影.评价区间有1-10分的心理测评,自然也有超级简单的+/-法:譬如很赞一个美女的时候,你可以送个玫瑰或者番茄啥的,不以为然的时候更可以砸个鸡蛋过去.

举个例子,如果一篇文章写得很不错,有100个人参与评价,80个人给了1,20个人给了0,那么这篇文章的评分(rating)应该是0.8.越是很有可能以加粗高亮的方式推荐给其他用户,得到了很不错的点击率.这时,问题就出来了:rating值很高的资源就一定真的评价高么?

好吧,请允许我邪恶的想一下,如果某用户写了一篇文章,然后自己先给打几个1分.于是,史上最满意文章(rating值是1)就这样来到了我们的网站!

想象一下大多数新产生的资源不是得分极高(评价少,分数普遍高)就是得分极低(无人问津).这也是上述评分机制一个非常突出的缺陷.我们意识到:对于那些只有极少数评价的资源,他们的rating相较于那些有着极多评价的资源缺乏一定的certainty” or “believability

因此,我们需要"修正"一下这个计算方法:
  • 对于评价很少的资源,可以认为其本身既不受欢迎也没有啥争议(不能引起话题).它的rating更应该去接近去整体评价的平均水平;
  • 那些评价很多的资源,通过修正也更多的体现出"相对的高",也就是更多的接近其本来的rating.
那么,我们可以给所有资源的平均rating和特定评价资源的平均rating各引入一个权值.这个权值就是整体平均评价人数和资源评价人数.这里的整体平均评价人数的设定可以根据真实的计算得出(动态值),当然也可以自己根据喜好设定一个.值越小说明你对评分要求越不严格,影响rating所需要的票数越少;值越大说明你越在意样本过少所带来的负面影响,同时说明资源必须保证一定的人气.

br = ( (avg_num_votes * avg_rating) + (this_num_votes * this_rating) )/(avg_num_votes + this_num_votes)

其中:
  • avg_num_votes:整体平均评价人数
  • avg_rating:资源平均rating
  • this_num_votes:待评价资源人数
  • this_rating:待评价资源rating
    这也就是江湖上赫赫有名的贝叶斯评价(Bayesian probability),更多信息可以围观维基百科.
    实际上这种评分系统广泛适用于很多知名的WEB2.0站点.

    目前正在火热举行的 花儿朵朵等选秀活动,可以考虑在此基础上增加一个"人的可信度"(来鉴别社区、论坛的马甲号,具体可以采用积分等手段),这样或许对提高选手评价的准确性和有效性起到一定的积极作用,也可以衍生出更多有趣的现象.

    Saturday, April 24, 2010

    Thursday, April 15, 2010

    芒果圈SNS用户社交分析图谱[上]

    SNS(Social Networking Services,传说中的社会化网络服务)是当今互联网上很潮的一个应用.专指旨在帮助人们建立社会性网络的互联网应用服务.

    几乎各大互联网公司都推出了类似的产品,公司的芒果圈就是其中之一.上线运营小半年以来,各项指标都还不错,基本保持平稳增长趋势. 某个月黑风高的夜晚,我突然心血来潮,想看看社区当中用户的交往情况如何,也可以为下一阶段若干产品开发提供相关思路.

    从用户评论关系入手,通过数据库将数据做了格式化(csv),通过对偶来表示用户之间的交互:

    3,14822   //3号用户对14822号用户评论了一次哦
    4,14822
    14822,4
    5,14880

    这个简单的数据表示的关系图是这样的


    最终得到的数据大约有20万个左右这样的点,将所有的节点和边的关系映射出来之后得到:

    中间那个蓝色的点是我们的客服,围绕在周围的几乎就是社区的意见领袖了,大量的3-4节点自组织结构说明社区存在很多彼此熟悉的小圈子.看来初步验证了有人的地方就有江湖啊

    Tuesday, March 16, 2010

    I don't wanna miss you

    兜兜去了广州.


    I don't want to miss a thing

    I could stay awake just to hear you breathing

    Watch you smile while you are sleeping

    While you're far away and dreaming

    I could spend my life in this sweet surrender

    I could stay lost in this moment forever

    Every moment spent with you is a moment I treasure



    I don't wanna close my eyes

    I don't wanna fall asleep

    Cause I'd miss you, baby

    And I don't wanna miss a thing

    Cause even when I dream of you

    The sweetest dream will never do

    I'd still miss you, baby

    And I don't wanna miss a thing



    Lying close to you feeling your heart beating

    And I'm wondering what you're dreaming

    Wondering if it's me you're seeing

    Then I kiss your eyes and thank God we're together

    And I just wanna stay with you

    In this moment forever, forever and ever



    I don't wanna close my eyes

    I don't wanna fall asleep

    Cause I'd miss you, baby

    And I don't wanna miss a thing

    Cause even when I dream of you

    The sweetest dream will never do

    I'd still miss you, baby

    And I don't wanna miss a thing



    I don't wanna miss one smile

    I don't wanna miss one kiss

    Well, I just wanna be with you

    Right here with you, just like this

    I just wanna hold you close

    Feel your heart so close to mine

    And stay here in this moment

    For all the rest of time



    Don't wanna close my eyes

    Don't wanna fall asleep

    Cause I'd miss you, baby

    And I don't wanna miss a thing

    Cause even when I dream of you

    The sweetest dream will never do

    Cause I'd still miss you, baby

    And I don't wanna miss a thing



    I don't wanna close my eyes

    I don't wanna fall asleep

    Cause I'd miss you, baby

    And I don't wanna miss a thing

    Cause even when I dream of you

    The sweetest dream will never do

    I'd still miss you, baby

    And I don't wanna miss a thing



    Don't wanna close my eyes

    Don't wanna fall asleep, yeah

    I don't wanna miss a thing



    ''世界末日''主題曲中文翻譯

    I don't want to miss a thing(我不願錯過這一切)

    為了聽見你的呼吸,我可以不睡

    在你沉睡時,注視著你的笑容

    當你夢見遠方

    我願用一輩子甜蜜的臣服於你

    永遠迷失在這片刻間

    和你在一起的每一刻都是我所珍愛的時刻



    不願閉上眼睛

    不願入睡

    因為我可能會錯過你,寶貝

    我不願錯過這一切

    因為即使我夢見你

    最美的夢也無法取代

    我依然想念你,寶貝

    我不願錯過任何事



    躺在你身旁,感覺著你的心跳

    我想知道你夢見了什麼

    你是否在夢中遇見了我

    於是,我吻了你的雙眼

    感謝上帝讓我倆在一起

    我要永遠和你停留在這個時刻

    生生世世



    我不願錯過任何一個笑容

    我不願錯過任何一個吻

    我就是要和你在一起

    像現在一樣

    我要緊緊的抱著你

    感受你的心貼近我的心

    在此時此地

    和我倆的餘生

    Monday, February 1, 2010

    22岁,人生第一个十万....

    05年大一,看着宿舍楼下"IT培训成就十万年薪"的商业广告,我嘴角的微笑告诉别人,我的不屑.作为ACM教练的我,觉得这个数字很容易得到...

    07年大三,退学的我,为了生计每日奔波,做着一个普通的埃踢民工,会因为每月10号多出来的2k而欢呼不已,会在周末的时候带着兜兜出去"吃大餐",那年我看到一套房子的首付也不需要十万...

    09年正式工作刚好一年,出来混社会差不多两年,我每日在忙碌工作之余,常常在想啥时候能年薪十万丫,兜兜告诉我不急,生活的意义不在于此...另一方面我开始留恋数码新品,开始讲究生活质量,却没有为兜兜做过什么让她可以放下担心的事情....这一年,我的东西多了,兜兜的没变多少

    10年2月1日,财务的变动让我开始关心今年自己的总收入到底有多少,在网银的总计一栏我赫然发现那个数字超过了十万,而我似乎开始麻木了...

    再过几个小时就是我23岁的生日,十万,一个曾经的梦想对我来说承载了太多的回忆和承诺.比这些更重要的是 感谢兜兜从那个时候开始就一直在我身边,无论是几平米的小屋,抑或是下雪天去雪地里刨食,不论是我赋闲待业,也不论我是连续加班,不离不弃.而我欠她的太多太多...
    如果说男人天生需要奋斗,那么世俗的金钱和权利即是一座座里程碑,那么这样一个开始或许意味着前方将有更难走的路和更难爬的山,但是这些之后的风光是否更加美丽,更加绚丽?

    默默许下人生第二个心愿,希望在这个路标之后,到达下一个真正意义上的里程碑的时候,我可以看到兜兜在我身边幸福的微笑.

    Tuesday, January 5, 2010

    使用awk处理网站访问日志[上]

    对于一个程序来说,Loging是一件非常有利的武器,其可以帮助程序员快速的找到BUG,分析性能瓶颈等等...甚至还可以在技术社区彰显一番代码的华丽,实乃死coder居家旅行必备之宝.而网站访问日志除了对开发者有修改缺陷,提升功力之良效以外,对待运营人员,也是分析用户行为的第一手宝贵资料.

    本博打算通过若干系列文章来讲讲如何有效运用访问日志来改善程序和用户行为分析:)也就是说:站在程序员的角度来审视程序的运行状态;通过数字手段来分析用户访问状况

    既然一切都从访问日志开始,那么首先就来parser weblog吧.让数据变得更加可读些,嘿嘿,自然也更方便处理些.
    这里的日志格式如下:
    124.205.30.210 - - [14/Dec/2009:16:02:46 +0800] "GET /HLRelationLog/ent.hunantv.com/y/l/20090306/225172.html H
    TTP/1.1" 404 252
    124.205.30.210 - - [14/Dec/2009:16:04:51 +0800] "GET /HLRelationLog/ent.hunantv.com/y/l/20090901/410652.html H
    TTP/1.1" 404 252
    211.152.32.122 - - [14/Dec/2009:16:15:10 +0800] "GET /manager/html HTTP/1.1" 404 210
    124.205.30.210 - - [14/Dec/2009:17:02:18 +0800] "GET / HTTP/1.1" 200 44
    124.205.30.210 - - [14/Dec/2009:17:02:19 +0800] "GET /favicon.ico HTTP/1.1" 404 209
    124.205.30.210 - - [14/Dec/2009:17:24:59 +0800] "GET /HLRelationLog/ent.hunantv.com/x/20090616/331092.html"

    格式很简单:来路IP,本地时间,以及访问的路径.实际情况下往往还有一个User-Agent.但是在本篇我们无视这个参数.先将这些数据"取"出来存入数据库再说.

    awk -F ' ' '/HLRelationLog/ {printf("insert into logs (url,ip,time) values (\"%s\",\"%s\",\"%s\");",$7,$1,substr($4,2))}' access.log > target.sql

    然后将数据导入
    mysql -u root -p -h 8.8.8.8 < target.sql

    这里简单再来看看大致的访问情况:
    awk -F ' ' '/HLRelationLog/ {print $7}' access.log |sort|uniq -c|sort -nr > sort.txt

    结果摘抄如下:
     13102 /HLRelationLog/ent.hunantv.com/e/h/20080917/49514.html
       7112 /HLRelationLog/ent.hunantv.com/e/h/20080719/23308.html
       6539 /HLRelationLog/ent.hunantv.com/m/20091231/536520.html
       6451 /HLRelationLog/ent.hunantv.com/m/20090427/281233.html
       5739 /HLRelationLog/ent.hunantv.com/m/20091222/527487.html
       5248 /HLRelationLog/ent.hunantv.com/x/20091224/529517.html
       4863 /HLRelationLog/ent.hunantv.com/y/20091219/524512.html
       4597 /HLRelationLog/ent.hunantv.com/m/20091221/526802.html
       4168 /HLRelationLog/ent.hunantv.com/m/20091221/526782.html
       4162 /HLRelationLog/ent.hunantv.com/y/20081028/87956.html
       3951 /HLRelationLog/ent.hunantv.com/m/20091222/527342.html
       3729 /HLRelationLog/ent.hunantv.com/e/20080716/22250.html
       3631 /HLRelationLog/ent.hunantv.com/e/h/20080728/26581.html
       3561 /HLRelationLog/ent.hunantv.com/m/20091222/527785.html
       3372 /HLRelationLog/ent.hunantv.com/e/h/20080903/42076.html
       3339 /HLRelationLog/ent.hunantv.com/y/20091216/522056.html
       3163 /HLRelationLog/ent.hunantv.com/y/l/20091221/525879.html
       2998 /HLRelationLog/ent.hunantv.com/y/20091216/521082.html
       2914 /HLRelationLog/ent.hunantv.com/x/20091221/526583.html
       2889 /HLRelationLog/ent.hunantv.com/y/20081231/154998.html
       2851 /HLRelationLog/ent.hunantv.com/z/20091225/530766.html

    下面来处理下频道的访问分布,每个link的倒数第三段[x,y,e...]:首先通过awk得到每个link的频道的识别符和访问数量,丢进一个文件

    map(lambda x:[x,sum(map(lambda y:int(y[1]),filter(lambda z:z[0]==x,t)))],set(map(lambda x:x[0],t)))
    >>>
    [['x\n', 48173], ['y\n', 266146], ['d\n', 4035], ['z\n', 40631], ['e\n', 98009], ['ent\n', 558], ['m\n', 174542], ['t\n', 59660]]

    来看看并发的情况:
    tail -10000 access.log | awk '{print $4;}' | sort | uniq -c | sort -nr | head

    =====我是分割线====

    33 [31/Dec/2009:22:03:18
         16 [25/Dec/2009:18:16:07
         14 [25/Dec/2009:18:16:06
          9 [31/Dec/2009:22:03:19
          8 [25/Dec/2009:18:13:35
          7 [31/Dec/2009:22:06:38
          7 [31/Dec/2009:19:22:40
          7 [25/Dec/2009:19:55:54
          7 [25/Dec/2009:14:39:06
          7 [02/Jan/2010:18:37:54