博文

目前显示的是 一月, 2012的博文

Linux和FreeBSD在使用非系统自带的gcc时的区别

下面拿CentOS 5和FreeBSD 9.0做下比较:
CentOS 5 自带的gcc是gcc (GCC) 4.1.2,通过yum可以安装gcc44 (GCC) 4.4.4
FreeBSD 9.0 自带的gcc是 gcc (GCC) 4.2.1,通过ports可以安装gcc 4.6 (目前是4.6.2)
我们用C++写一个非常简单的C++程序:
intmain(){ return0; } 然后用g++编译:
# g++44 main.cpp -o main
然后用ldd查看,Linux下的输出结果为:
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x000000336f000000)
libm.so.6 => /lib64/libm.so.6 (0x000000336cc00000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x000000336e400000)
libc.so.6 => /lib64/libc.so.6 (0x000000336c400000)
/lib64/ld-linux-x86-64.so.2 (0x000000336c000000)
FreeBSD下的输出结果为:
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x800849000)
libm.so.5 => /lib/libm.so.5 (0x800b59000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x800d7a000)
libc.so.7 => /lib/libc.so.7 (0x800f87000)
其中有两行我故意标红了。因为他们是来自于gcc。那么就有这么一个问题:不同版本的gcc,这两个库,一样吗?或者我这么问,gcc 4.4、gcc 4.6、gcc 4.2、gcc 4.1相比,他们的C++标准库(libstdc++.so)的接口一样吗? 实现一样吗?(此处指需要被编译的那部分,如非模板类)
我们来看看CentOS怎么做的:
CentOS 5的gcc44-c++这个包,只带了两个so。/usr/lib/gcc/x86_64-redhat-linux6E/4.4.4/32/li…

今天试用了一下mysql connector/C++

今天试用了一下mysql connector/C++,感觉很不错。这个函数库的目的就是,仿照JDBC的接口,给mysql做一套C++的。这样就让我感觉很爽。因为我有时候需要写C++,有时候需要写JAVA。这种跨语言的接口规范真是让我感觉很爽。再比如XML解析器,我就比较喜欢xerces而不是libxml2,因为xerces的接口尽力的遵守W3C DOM规范。嗯,我明天找找文档,看如何把代码模块化,动态载入so。

Websocket协议简介

今天@julyclyde 在微博上问我websocket的细节。但是这个用70个字是无法说清楚的,所以就整理在这里吧。恰好我最近要重构年前写的websocket的代码。众所周知,HTTP是一种基于消息(message)的请求(request )/应答(response)协议。当我们在网页中点击一条链接(或者提交一个表单)的时候,浏览器给服务器发一个request message,然后服务器算啊算,答复一条response message。主动发起TCP连接的是client,接受TCP连接的是server。HTTP消息只有两种:request和response。client只能发送request message,server只能发送response message。一问一答,因此按HTTP协议本身的设计,服务器不能主动的把消息推给客户端。而像微博、网页聊天、网页游戏等都需要服务器主动给客户端推东西,现在只能用long polling等方式模拟,很不方便。OK,来看看internet的另一边,网络游戏是怎么工作的?我之前在一个游戏公司工作。我们做游戏的时候,普遍采用的模式是双向、异步消息模式。首先通信的最基本单元是message。(这点和HTTP一样)其次,是双向的。client和server都可以给对方发消息(这点和HTTP不一样)最后,消息是异步的。我给服务器发一条消息出去,然后可能有一条答复,也可能有多条答复,也可能根本没有答复。无论如何,调用完send方法我就不管了,我不会傻乎乎的在这里等答复。服务器和客户端都会有一个线程专门负责read,以及一个大大的switch… case,根据message id做相应的action。while( msg=myconnection.readMessage()){ switch(msg.id()){ case LOGIN: do_login(); break; case TALK: do_talk(); break; … } } Websocket就是把这样一种模式,搬入到HTTP/WEB的框架内。它主要解决两个问题:从服务器给客户端主动推东西。HTTP协议传输效率低下的问题。这一点在web service中尤为突出。每个请求和应答都得带上很长的http header!websock…

还是说Memory Model,gcc的__sync_synchronize要慎用。

当我们在做多线程编程的时候,会涉及到一个称为memory order的问题。例如int x=0,y=0; x=4; y=3; 请问,实际执行的时候,这两条赋值语句谁先执行,谁后执行? 会不会有某个时间点,在某个CPU看来,y比x大?答案很复杂。本文的目的是从非常实践的角度来考虑这个问题。首先,它分为两个层面。在编译器看来,x和y是两个没有关联的变量,那么编译器有权利调整这两行代码的执行顺序,只要它乐意。其次,CPU也有权利这么做。如果我非要严格要求顺序,那么就应该插入一个memory barrierint x=0,y=0; x=4; //在此插入memory barrier指令 y=3; 下面要论述,中间那行怎么写。请耐心看下去,因为大多数人都在瞎整。gcc的手册中有一节叫做"Built-in functions for atomic memory access",然后里面列举了这样一个函数:__sync_synchronize (...)
This builtin issues a full memory barrier.来,我们写段代码试下:intmain(){ __sync_synchronize(); return0; } 然后用gcc4.2编译,# gcc -S -c test.c然后看对应的汇编代码,main: pushq %rbpmovq %rsp, %rbp movl $0, %eaxleaveret嗯?Nothing at all !!! 不信你试一试,我的编译环境是Freebsd 9.0 release, gcc (GCC) 4.2.1 20070831 patched [FreeBSD]。 好,我换个高版本的gcc编译器试一试,gcc46 (FreeBSD Ports Collection) 4.6.3 20120113 (prerelease)main: pushq %rbpmovq %rsp, %rbpmfence movl $0, %eax popq %rbpret看,多了一行,mfence。 怎么回事呢?这是gcc之前的一个BUG: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36793 。 2008年被发现,然后修复的。其实它之所以是一个BUG,关键在于g…

开始学习Libevent

虽然基于reactor模式的异步IO被吹的神乎其神,但是我一直对这些东西不怎么感冒。因为我一直是在写后台服务,我所需要处理的并发连接数也就10来个,那么干嘛要用它呢?我还是比较偏好于thread-per-connection模式。我想把ACE抛掉,换一个轻量级一点的网络库。看了一圈,都在推荐libevent/libev。然后就想,要不就拿libevent写一个简单的http server吧。其实也不是我写,就是把网上现成的一些库,组装起来而已。http: https://github.com/ellzey/libevhtp
getopt: http://code.google.com/p/google-gflags/
log: http://code.google.com/p/google-glog/
smarty for C++: http://code.google.com/p/google-ctemplate/可以把这些东西组装起来写一些简单的小页面。libevent的代码我用VC++编译过去了,但是libevhtp却不行。比较令我头疼的是,想找一个C++的、http的协议实现,真的很不容易。ACE自带了一个,主要是给client使的,但是header parser之类的可以挪来用。libevent自带了一个,被骂的狗血淋头,那东西似乎就不应该被编译到Lib里面,应该放到一个example目录里,仅供观赏。其实,在想另外一个问题:如果一个请求的处理时间非常短,那么reactor模式非常好。如果一个请求的处理时间相对较长,那么reactor模式+thread pool也可以work。虽然多了一次线程切换,但是相对总体的处理时间来说这不算什么。于是,它就是一个万能模式啊。

用Sawzall在map-reduce框架下做数据统计

(这是一篇草稿,记下来给自己备忘。如果有人感兴趣,我整理下写详细点)。Sawzall是7-8年前,google所用的一套的数据统计系统。在今天看它虽然已经过时了,但是瘦死的骆驼也比马大,它的很多设计点依然非常值得我们学习参考。下面举一个例子:有一个视频网站,它有很多注册用户。每个用户每看一个视频,它就会记录下这个用户看了什么视频。日志文件的每一行都是这样的形式:33433,5454545前面一个数字是userid(哪个用户),后面一个数字是program_id(看了哪个视频)。现在要求根据这些日志文件,产生一个新的数据文件,使得我能根据userid能快速的查到这个用户看过哪些视频。只记录视频ID,不用这个视频管看了几次。例如:假设输入为:10,555
20,666
30,777
10,344
30,554那么输出的结果应该是:10, (555,344)
20, (666)
30, (777,554)如果这些日志文件不大,比如,总共加起来也就1-2G,那么太简单了,把所有文件传到同一台机器上,然后随便写段代码,在内存中放一个std::map<int,std::set<int>>这样的结构,把日志文件逐行处理一遍就行了。可是如果这些日志文件非常大,分散在几千台机器上,总共加起来有几十T。以至于即便产生的最终结果,也有好几T。下面展示一下,Google如何用sawzall解决这个问题。一共分三个阶段:Map、Reduce、Final。一、Map:这个阶段就是在存储数据的机器上(或者离它较近的机器),对原始日志文件做逐行扫描,提取出有用的数据。发送给Reducer。为了实现这个功能,sawzall代码如下:"t: table set(100)[int] of int;"
"fields: array of bytes = splitcsvline(input);"
"index: int = int(string(fields[0]),10);"
"value: int = int(string(fields[1]),10);"
"emit t[index] <- value;";每行的输入在input变量中,是byte[]类型。sawzall语言中,唯一的输出方…

拿leveldb在FreeBSD下和Linux做了一些对比测试

我找了两台硬件环境基本一样的机器(同一个刀片里面的两台),分别装了FreeBSD和Linux,来测试leveldb在这两个环境下的性能差异。编译环境:FreeBSD 9.0: gcc 4.6,libunwind-1.0.1 google-perftools-1.8.3(libunwind-0.99-beta编译不过去,google-perftools-1.9.1虽然能编译过去,但是最后和db_bench链接完后,运行会core dump)CentOS Linux: gcc 4.4,google-perftools-1.9.1,libunwind-0.99-betaleveldb的代码是从git上取的主干的最新版,最后修改时间 2011.11.30。rev c8c5866a86c8。操作系统的详细信息:一个是FreeBSD 9.0 release# uname -aFreeBSD test27.localdomain 9.0-RELEASE FreeBSD 9.0-RELEASE #0: Tue Jan 3 07:46:30 UTC 2012 root@farrell.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64文件系统是zfs,da0和da1做的mirror,没有用SAS卡自带的raid控制器,因为没驱动。sysctl的参数都是系统默认的,未做任何tunning。一个是CentOS release 5.7 (Final)# uname -a
Linux test26 2.6.18-194.el5 #1 SMP Fri Apr 2 14:58:14 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux文件系统是ext3。所选用的benchmark程序是leveldb自带的db_bench。这个程序其实有一定的问题,因为在同一个机器上反复运行,性能结果会越来越好。运行参数: ./db_bench --db=/home/t3 --write_buffer_size=107374182 --cache_size=107374182 --threads=4测试说明:下面如果无特别说明,key的长度是16字节,Values的长度是100字节,测试条数N = 1百万。名字说明fil…

假如一个网站的userid是从1开始依次递增,如何估计这个网站的用户数?

假设有这么一个网站,它的userid是从1开始依次递增(step=1),我们想知道这个网站有多少用户,那么最简单的方式就是,马上去注册一个新用户,然后看看userid是多少。假设这个方法不可行,比如,这是一个需要邀请才能注册的论坛。我们只能从这个论坛的帖子中,提取userid,来估计。那么该怎么估计?假设用户的总数为N,那么userid的取值范围就是[1,N]。假设我们取到n个样本,这n个样本的userid的最大值是maxU,最小值是minU,那么用maxU估计N行不行?当然可以。但是毫无疑问,maxU肯定是小于等于N的,那么到底小多少呢?如何从样本中把这个差值估计出来?假如我们把样本的userid按照从小到大排列,可以得到下面这个公式:result=maxU + (相邻两个userid的间隔)的均值。例如样本是: 1,3,5,8,那么result= 8 + {(8-5)+(5-3)+(3-1)}/3。上面那个公式,化简后可以得到:result=max+(max-min)/(n-1)-1。可以证明,上面这个式子的数学期望恰好就是N。北大数院的郑忠国教授,给出了另一个公式,result2=(n+1)*max/n -1,这个式子的数学期望恰好也是N,但是是所有估计公式中,方差最小的一个。我写了一段程序试了一下,让N=1000万左右,然后取1000个样本做估计,结果如下:总体=10007351,样本=1000,估计方式1=10016532,估计方式2=10016523,max=10006518,min=962
总体=10005331,样本=1000,估计方式1=9994145,估计方式2=9994140,max=9984157,min=4534
总体=10008413,样本=1000,估计方式1=10005253,估计方式2=10005248,max=9995254,min=4678
总体=10003869,样本=1000,估计方式1=10005421,估计方式2=10005413,max=9995419,min=1482如果把样本数减少到10:总体=10001094,样本=10,估计方式1=10669213,估计方式2=10608249,max=9643864,min=415708
总体=10002488,样本=10,估计方式1=10456242,估计方式2=1041992…

我准备用WebSocket协议来承载RPC

先说RPC的始祖,sun rpc。它的协议很简单。首先找rpcbind问下,这个用户服务在哪个端口监听。然后连上去,每个请求和答复都是 (length、type、序列化后的二进制)这样的形式。首先,length是必须的,因为tcp是一个流,我们需要对它切片,切成一个个的message。其次,type也是必须的,接收方要根据type去找,调用哪个反序列化函数。rpcbind解决了一个问题,如何在一个服务器上启动多个网络服务,但是只占用一个固定端口。嗯,这个在今天看起来不是很必要。也许架个naming service会解决的更彻底一点。而完美在北京开发的所有游戏,除了某个还未上线的外,全采用的是这样的消息格式。Good? Or More Better?WebSocket,作为HTML5的一部分,主要为了解决一个问题:如何在http服务器和客户端之间进行双向的socket通信。它经过的很多变迁,现在的版本也是Length-prefixed。但是,和前面所说的相比,它做了两个改进:对length做变长编码。这个完美也做了。但是我绝对不认为这是一个好的设计。为这个我专门去找过panpan。因为你的length字段是变长编码,所以我必须先序列化body,然后得到长度。然后往流里面写长度,然后把序列好的body copy过来。于是这就多了一次memcpy操作。如果长度是固定的,我可以先留好了空,最后再填啊。其实,除非大部分协议都很碎小,否则根本省不了多少流量,连1%都不到。何苦呢,搞这么麻烦,不如选一个好一点的协议层压缩算法。Framing。回忆一下,传统来说,HTTP的header都会有一个Content-Length字段。指明Body的长度。但是为什么要有chunked编码?因为有时候回复的body很大,写header的时候根本不知道body有多大,而body是一边处理一边往外发。所以在websocket协议的第一个字节的最高位,标识这个包是不是最后一个,FIN。如果不是最后一个,接收的人要负责组装。啊 !这下好了。我可以分配一个64K的buffer,拿一个object往里塞。如果buffer塞满了,但是object还没序列化完,没关系,我把header一填,扔出去。继续写。这是非常高效的做法。如果没有这个功能,那么我们就得先分配一个128K的buffer,然后把这64K复…