博文

目前显示的是 十月, 2011的博文

ThreadLocalRandom的BUG浅析

Java中有一个用来生随机数的类,java.util.Random。用起来很简单,java.util.Random rand=new java.util.Random(); int v=rand.nextInt(); 众所周知,这样生出来的随机数是伪随机数,于是需要一个种子(seed),所以如果把这个对象在多线程共享,就会产生同步开销。 jdk 1.7引入了一个新类,ThreadLocalRandom。用起来更简单了,啥时候想用就用:int v=ThreadLocalRandom.current().nextInt(); 不过,悲剧来了,我测了一下,发现它每次产生的值都是一样的。这是JDK一个尚未修复的BUG。下面将详细讲述这个BUG如何产生的,希望对所有使用面向对象设计模式的人有所警示。 先从java.util.Random这个类说起,它有两个构造函数:publicRandom(long seed); publicRandom(); 如果你提供了seed,它就用你的seed,否则,它就随机生一个seed。所以,不带参数的版本,代码是这样的:publicRandom(){ this(seedUniquifier() ^ System.nanoTime()); } 带参数的版本,本来是这样实现的: (我推测)publicRandom(long seed){ setSeed(seed); } privatefinal AtomicLong seed=new AtomicLong (); synchronizedpublicvoidsetSeed(long seed){ this.seed.set(initialScramble(seed)); } 然后我们实现一个子类ThreadLocalRandom,继承自RandompublicclassThreadLocalRandomextendsRandom{ privatelong rnd; //用普通的long就可以了,不需要AtomicLong。 ThreadLocalRandom() { super(); } publicvoidsetSeed(long seed){ rnd = seed ; } } 你想,既然这个类的实例是ThreadLoc…

非一致性内存访问模型与内存分配器

CPU主频涨不上去了,一直停留在2-3G。前端总线的时钟频率也涨不上去了,我现在用的这个小黑,Intel Core2 P8600,前端总线的时钟频率只有266MHz。于是,虽然内存越来越便宜了,但是没有那么大的高速带宽来连接CPU和内存啊。于是NUMA出现了。CPU组成node,每个node各自管理几十G内存,然后node和node之间通过Point-to-Point的方式建立高速直连。于是系统总线就没了,出现一个新名词,QPI,指那个快速访问通道,它不光连接内存和CPU,还连接其它外设如显卡。但是有个重要的结果是:CPU到每根内存条的"距离"是不相等的。有的是直连,所以速度很快,而有的需要绕到另一个node去拿,这样不仅速度慢,而且很容易把node之间的那个互连通道堵死。想想看,如果你工作在天津,但是偏偏要住在北京,每天上班下班是不是很痛苦?为什么呢?为什么会这样?为什么我想访问的这个内存不在我身边?那它在哪?从C语言的malloc说起。首先强调一点,malloc分配的是"地址空间",而不是内存!同理,free释放的也只是地址空间,而不是内存。当你访问某个内存地址,而这个地址没有映射到任何物理页的时候,就会发生缺页中断,然后此时,操作系统才分配内存。简单点说,"内存的分配发生在第一次访问的时候!"。总的来说,内存分配器此时有三种策略:糊里糊涂。什么都不知道,随便分。就近,找离当前CPU最近的node分配。round-robin。把你要的东西尽可能的平均分到每个node上。我不清楚你用的C Runtime到底是哪一种实现,反正以上三种都有可能。喜欢C的人大多都是追求高效,那么自然喜欢第二种咯?于是就有人提出,服务器的启动过程应该做成并行化的,比如IO的buffer就让IO线程去初始化,各自做各自的。这样听起来很有道理,但是!亲爱的,如果这个线程被调度到另一个CPU上怎么办?所以我们不光得控制内存怎么分配的,还得控制线程调度策略,把这个线程绑在固定的CPU组上。更复杂的是,执行任务的是一个线程池怎么办?请问,我是在写Application,还是Operating System? 无论如何,"谁要用谁分配"依然是一个有效的优化策略。那么说Java吧,它比较清晰。Java有4种垃圾回收策略 …

关于java.nio.ByteBuffer的一些杂七杂八。

任何网络程序框架都会面临一个问题:如何提供一个高效的buffer?比如我们想写一个http server,那么就需要不断的从文件中读入数据,然后写入到socket中,如: byte[] buf=newbyte[4096]; while(file.read(buf)){ mysocket.write(buf); } java.nio中引入了一个重要的类:ByteBuffer,来做这件事情。(我的直觉是它应该和ACE的MessageBlock作用很像,但是后来发现接口迥异。)
ByteBuffer是一个抽象类,它有两种实现:HeapByteBuffer 和 DirectByteBuffer。java.nio.ByteBuffer.allocate(int)返回的是HeapByteBuffer,java.nio.ByteBuffer.allocateDirect(int)返回的是DirectByteBuffer。
HeapByteBuffer分配在jvm的堆(如新生代)上,和其它对象一样,由gc来扫描、回收。DirectByteBuffer则是通过底层的JNI向C Runtime Time通过malloc分配,在JVM的GC所管理的堆之外。下面讨论HeapByteBuffer。
每个HeapByteBuffer内部有一个byte[]存储数据。这个byte[]在构造HeapByteBuffer的时候分配好,长度不会自动增长。HeapByteBuffer内部有四个指针(offset):
capacity:内部这个byte数组的大小(byte[]的length)。
mark:相当于书签,初始值为-1。需要设置的时候mark()一下,需要跳回去的时候用reset()方法。
position:指向下一个读取/写入位置。初始值为0,读/写 数据的时候自动往后挪这个指针。
limit:初始值等于 capacity。
它们始终满足这样的关系:mark <= position <= limit <= capacity。这4个指针经常把我绕的晕乎乎的。flip操作:用在读写操作转换的时候。limit = position; position = 0; mark = -1; //清理掉书签示例用法:buf.put(magic); // 先往buffer里面写入一个包头(pack…

三段序列化代码的测试:比较protocol buffers的CodedOutputStream和java自带的DataOutputStream

最近一段时间在写一个小东西,一个很简单k-v数据库。我并没有像MyISAM那样把每个表放在一个单独的文件中,而是用一个总的大文件来放所有的表。(类似于InnoDB默认的方式)。我需要在这个硕大无比的文件的开头放一个map,key是表名,value是这个表的第一个页在此文件中的偏移地址。即这样一个结构:Map < String, Long > headers。那么我就需要为这个Map写一个序列化方法,把它从Object转化成byte[]。写完第一个实现,并用junit测试完正确性之后,我准备再写2个实现,测试下性能。三种实现的思路分别是:
1、用google protocol buffers的CodedOutputStream,手写序列化。先计算序列化之后需要多大空间,然后new出这个byte[],然后往里填。这是protoc生成的代码所采用的方式。
2、先new一个ByteArrayOutputStream,然后用它构造一个DataOutputStream,然后往里写,最后用ByteArrayOutputStream的toByteArray返回。其中字符串以UTF8的方式写入。
3、先new一个ByteArrayOutputStream,然后用它构造一个CodedOutputStream,手写序列化,最后用ByteArrayOutputStream的toByteArray返回。ByteArrayOutputStream的默认buffer大小是32字节,如果DataOutputStream/CodedOutputStream往里面写的时候遇到它满了,就需要对现有的内存做一次copy来grow一下。这就是为什么我首先写的是方案一。但是方案一的缺点是,它需要把这个Map遍历2次。测试环境:Core i3-2100,8GB内存,sun jdk 7。google protocol buffers的版本是2.4.1。
测试方式:首先往这个hashmap里面添1000条记录,key是长度为10的随机字符串,value是64位随机整数(0x0-0x7fffffffffffffffL之间均匀随机)。先warm up一下,然后执行1000次序列化方法。
测试结果:
方案1执行1000次花费时间=170ms-180ms左右。
方案2执行1000次花费时间=75ms-95ms左右。
方案3…

关于QPI的一些数据

Intel从2008年,core i7,开始引入QPI,QuickPath Interconnect。core i7采用的是Nehalem架构(第一款?),QPI的主要作用是取代原有的前端总线,QPI的时钟频率主要有两种标准:2.4GHz或3.2GHz。每个时钟频率内可以传输两次,所以Intel手册中所说的bit rates是指每秒能传输多少次,单位GT(G Transfters per second),如前面两种分别对应着4.8GT和6.4GT。前端总线QPI拓扑BUSLink信号技术GTL+differential signalingRx Data Samplingsource synchronous data samplingforwarded clock总线宽度(位)6420最大数据传输宽度6416双向总线NoYes物理层的传输单位是Phit,一个Phit是20位或者10位或者5位。链路层的传输单位是Flit(flow control unit),每个Flit是80位。每80位里面应当包含8位的CRC,这就是为什么一个Phit即便装满了20位,但实际上也只有2个字节。传统的前端总线的带宽很好计算。比如我的电脑是Intel Core2 P8600,前端总线的时钟频率是266MHz,倍频是9,所以CPU主频是2.4G,前端总线速度是266*4=1066MHz,而总线宽度是64位,那么总线带宽就是1066Mhz*8Byte=8.5GB/s。Intel号称它的QPI带宽可达到25.6GB/s。但是这个数据是怎么计算出来的呢?维基百科上给出的答案是:3.2 GHz
乘以 2 bits/Hz (double data rate)
乘以 20 (QPI link width)
乘以 (64/80) (data bits/flit bits)
乘以 2 (unidirectional send and receive operating simultaneously)
除以 8 (bits/byte)
= 25.6 GB/s
但是我觉得这个并不对。实际上计算方式是:假设时钟频率是3.2GHz,那么bit rates就是6.4GT,由于物理层每秒能传输20位,但是实际上是2个字节,所以12.8GB/s,但是又因为是双向传输,所以double一下,就是25.6GB/s。假设CPU的每个ca…

把site搬回国内

最近因为一些普遍的原因,在godaddy注册的很多域名无法正常解析(非.cn的),于是我只好把nameserver从godaddy搬到dnspod。顺便,思索再三,觉得SAE是个好东西不用太可惜,于是就把blog也搬到了sina app engine。访问速度差别还是很明显的。在北京ping SAE的前端机,延迟在9-40ms左右。而linode在日本的机房的ping值在100ms左右,我的prgmr.com的ping值则是在250-400ms左右。比较遗憾的是域名绑不上,虽然我已经做了CNAME。于是就只好改以前的代码,把网址做302转发过来。SAE还有一个特别吸引我的地方呢,就是服务很好。嘿嘿。因为很多都是以前的同事,msn上喊一声就行了。我觉得SAE现在的访问统计功能太简单,做丰富点,像google analytics那样,把访问来源、时段、搜索关键字、agent、排除robot这些都做了。现在的统计信息除了能看一下pv,什么都看不到。或者至少,做到awstats那样吧。反正每个网站主肯定都很关心访问统计的,尤其是这个做的越智能越好。最近真是巨星接连陨落啊。哀悼一下。Steve Jobs 1955-2011Dennis Ritchie 1941-2011Robert Galvin 1922-2011

诺顿真没法用……我自己从源代码编译的mysql也杀

图片
昨天把MSE卸掉,装了诺顿NIS 2012。然后悲剧就开始了。首先,当我准备用SUN JDK的visualvm给我的程序做性能测试的时候,诺顿果断的认为这是木马,干掉了。接着,我做了下全盘扫描。诺顿默默的帮我删除了:我自己从源代码编译的mysql。理由是根据它的云平台,极少人信任并使用了这个文件。qt的demo的fftreal.dll。理由同上ida的数个dll星际争霸的blood.exegamemaster 9的主程序当然,这些还没完。最后他想删掉我的邮件客户端的Thunderbird的Inbox文件,但是考虑到这个文件太大,于是问了我一下。嗯,以前它也总是这样,隔一阵弹一次,“是否删除?确定取消” 直到某一次,我不小心点了确定。它就直接删了,也不做备份,因为太大。2012版的比以前的好处就是,它终于能告诉我为什么删这个文件,它给出了可能含有病毒的附件名。但是Thunderbird没有搜索附件名这个功能,我搜正文搜不到这些文件。最终,我只能手动的一个个找。我觉得很徒劳,因为它藏那么深,又不能主动发作,我找它干嘛?

MSM人群HIV携带率统计

我从互联网上搜集了一些关于AIDS的数据。首先,我们一定要非常重视AIDS这个问题。2010年,全国甲乙类传染病(共28种)共报告死亡14289人,其中AIDS病7743人,占了一半多。第二位是肺结核,3000人。第三位是狂犬病,2014人。搜集的过程中发现在Gay当中感染率非常高。下文的统计数据中将不会提到Gay,而是一个专门的名词,MSM。MSM:是men who have sex with men的缩写,包括gay、bisexual等等。根据美国Centers for Disease Control and Prevention的统计数据显示,MSM估计约占了美国总人口的2%,MSM的HIV感染率接近1/5。"A recent CDC study found that in 2008 one in five (19%) MSM in 21 major US cities were infected with HIV, and nearly half (44%) were unaware of their infection."在中国,想要获得类似的数据不大容易,因为我国CDC很显然不会对流行病的发病情况做种族、肤色、性取向这样的划分并公布出来。我们强调团结嘛。但是网上依稀的能找到一些零散的公开数据:全国:MSM人群艾滋病诊断阳性率从2005年的0.4%上升到2008年的4.9%。郝阳说,最近几个重点城市在男男性行为人群中开展的一些典型调查发现,艾滋病诊断阳性率最高的达到了15%。(2008年) http://www.chinaids.org.cn/n16/n1193/n4073/118347.html6城市MSM人群综合统计:2009-2010年,沈阳、济南、上海、南京、重庆、长沙、昆明中的六个,统计人数2500人,HIV阳性率5.2%,梅毒10.0%。http://www.docin.com/p-62580381.html北京东城区:2006年,MSM人群HIV阳性率6.5%。http://www.chain.net.cn/document/20080805171749598318.pdf四川:2007年,MSM人群HIV阳性率10.6% http://www.chain.net.cn/document/2008080517174959…