<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>我自然</title>
	<atom:link href="http://www.yankay.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.yankay.com</link>
	<description>颜开的博客</description>
	<lastBuildDate>Fri, 16 Mar 2012 10:39:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
	<atom:link rel='hub' href='http://www.yankay.com/?pushpress=hub'/>
		<item>
		<title>Java使用&quot;指针&quot;快速比较字节</title>
		<link>http://www.yankay.com/java-fast-byte-comparison/</link>
		<comments>http://www.yankay.com/java-fast-byte-comparison/#comments</comments>
		<pubDate>Fri, 16 Mar 2012 10:30:54 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[软件技术]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Java字节数组比较]]></category>
		<category><![CDATA[Java字节比较]]></category>
		<category><![CDATA[字节]]></category>
		<category><![CDATA[比较]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26437</guid>
		<description><![CDATA[如何才能快速比较两个字节数组呢?我将问题描述成下面的接口： public int compareTo&#40;byte&#91;&#93; b1, int s1, int l1, byte&#91;&#93; b2, int s2,int l2&#41;; 最直观的做法是同时遍历两个数组，两两比较。 public int compareTo&#40;byte&#91;&#93; buffer1, int offset1, int length1, &#160; byte&#91;&#93; buffer2, int offset2, int length2&#41; &#123; &#160;// Short circuit equal case &#160;if &#40;buffer1 == buffer2 &#8230;<p class="read-more"><a href="http://www.yankay.com/java-fast-byte-comparison/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<p>如何才能快速比较两个字节数组呢?我将问题描述成下面的接口：</p>
<div class="geshi no java">
<ol>
<li class="li1">
<div class="de1"><span class="kw2">public</span> <span class="kw4">int</span> compareTo<span class="br0">&#40;</span><span class="kw4">byte</span><span class="br0">&#91;</span><span class="br0">&#93;</span> b1, <span class="kw4">int</span> s1, <span class="kw4">int</span> l1, <span class="kw4">byte</span><span class="br0">&#91;</span><span class="br0">&#93;</span> b2, <span class="kw4">int</span> s2,<span class="kw4">int</span> l2<span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
</ol>
</div>
<p>最直观的做法是同时遍历两个数组，两两比较。</p>
<div class="geshi no java">
<ol>
<li class="li1">
<div class="de1"><span class="kw2">public</span> <span class="kw4">int</span> compareTo<span class="br0">&#40;</span><span class="kw4">byte</span><span class="br0">&#91;</span><span class="br0">&#93;</span> buffer1, <span class="kw4">int</span> offset1, <span class="kw4">int</span> length1,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">byte</span><span class="br0">&#91;</span><span class="br0">&#93;</span> buffer2, <span class="kw4">int</span> offset2, <span class="kw4">int</span> length2<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="co1">// Short circuit equal case</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw1">if</span> <span class="br0">&#40;</span>buffer1 == buffer2 <span class="sy0">&amp;&amp;</span> offset1 == offset2</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="sy0">&amp;&amp;</span> length1 == length2<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw2">return</span> <span class="nu0">0</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="co1">// Bring WritableComparator code local</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw4">int</span> end1 = offset1 + length1<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw4">int</span> end2 = offset2 + length2<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw1">for</span> <span class="br0">&#40;</span><span class="kw4">int</span> i = offset1, j = offset2<span class="sy0">;</span> i <span class="sy0">&lt;</span> end1 <span class="sy0">&amp;&amp;</span> j <span class="sy0">&lt;</span> end2<span class="sy0">;</span> i++, j++<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">int</span> a = <span class="br0">&#40;</span>buffer1<span class="br0">&#91;</span>i<span class="br0">&#93;</span> <span class="sy0">&amp;</span> 0xff<span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">int</span> b = <span class="br0">&#40;</span>buffer2<span class="br0">&#91;</span>j<span class="br0">&#93;</span> <span class="sy0">&amp;</span> 0xff<span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw1">if</span> <span class="br0">&#40;</span>a <span class="sy0">!</span>= b<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw2">return</span> a - b<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw2">return</span> length1 - length2<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#125;</span></div>
</li>
</ol>
</div>
<p>如果事情这么简单就结束了，就没有意思了。</p>
<p>如果要提升性能，可以做循环展开等等优化，但这些优化应该依赖JVM来做，新的JVM可以做的很好。那还有什么办法可以提高性能呢？<br />
可以将<strong>字节数组合并</strong>!!上面的例子中，每个byte被迫转型成了int，再比较。其实我们可以将8个byte转换成一个long，在比较long，这样效果会不会好些？用什么方法转换才是最优的？</p>
<div class="geshi no java">
<ol>
<li class="li1">
<div class="de1"><span class="kw4">long</span> sun.<span class="me1">misc</span>.<span class="me1">Unsafe</span>.<span class="me1">getLong</span><span class="br0">&#40;</span><span class="kw3">Object</span> o,<span class="kw4">int</span> offset<span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>Java提供了一个本地方法，可以最快最好转换byte与long。该函数是直接访问一个对象的内存，内存地址是对象指针加偏移量，返回该地址指向的值。有人说Java很安全，不可以操作指针，所以有的时候性能也不高。其实不对，有了这个Unsafe类，Java一样也不安全。所以Unsafe类中的方法都不是public的，不过没关系，我们有反射。言归正传，下面是使用这种技术手段的实现代码。</p>
<div class="geshi no java">
<ol>
<li class="li1">
<div class="de1"><span class="kw2">public</span> <span class="kw4">int</span> compareTo<span class="br0">&#40;</span><span class="kw4">byte</span><span class="br0">&#91;</span><span class="br0">&#93;</span> buffer1, <span class="kw4">int</span> offset1, <span class="kw4">int</span> length1,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">byte</span><span class="br0">&#91;</span><span class="br0">&#93;</span> buffer2, <span class="kw4">int</span> offset2, <span class="kw4">int</span> length2<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="co1">// Short circuit equal case</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw1">if</span> <span class="br0">&#40;</span>buffer1 == buffer2 <span class="sy0">&amp;&amp;</span> offset1 == offset2</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;<span class="sy0">&amp;&amp;</span> length1 == length2<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw2">return</span> <span class="nu0">0</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw4">int</span> minLength = <span class="kw3">Math</span>.<span class="me1">min</span><span class="br0">&#40;</span>length1, length2<span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw4">int</span> minWords = minLength / Longs.<span class="me1">BYTES</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw4">int</span> offset1Adj = offset1 + BYTE_ARRAY_BASE_OFFSET<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw4">int</span> offset2Adj = offset2 + BYTE_ARRAY_BASE_OFFSET<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="coMULTI">/*</span></div>
</li>
<li class="li1">
<div class="de1"><span class="coMULTI">&nbsp; * Compare 8 bytes at a time. Benchmarking shows comparing 8</span></div>
</li>
<li class="li1">
<div class="de1"><span class="coMULTI">&nbsp; * bytes at a time is no slower than comparing 4 bytes at a time</span></div>
</li>
<li class="li1">
<div class="de1"><span class="coMULTI">&nbsp; * even on 32-bit. On the other hand, it is substantially faster</span></div>
</li>
<li class="li1">
<div class="de1"><span class="coMULTI">&nbsp; * on 64-bit.</span></div>
</li>
<li class="li1">
<div class="de1"><span class="coMULTI">&nbsp; */</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw1">for</span> <span class="br0">&#40;</span><span class="kw4">int</span> i = <span class="nu0">0</span><span class="sy0">;</span> i <span class="sy0">&lt;</span> minWords <span class="sy0">*</span> Longs.<span class="me1">BYTES</span><span class="sy0">;</span> i += Longs.<span class="me1">BYTES</span><span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">long</span> lw = theUnsafe.<span class="me1">getLong</span><span class="br0">&#40;</span>buffer1, offset1Adj + <span class="br0">&#40;</span><span class="kw4">long</span><span class="br0">&#41;</span> i<span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">long</span> rw = theUnsafe.<span class="me1">getLong</span><span class="br0">&#40;</span>buffer2, offset2Adj + <span class="br0">&#40;</span><span class="kw4">long</span><span class="br0">&#41;</span> i<span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">long</span> diff = lw ^ rw<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw1">if</span> <span class="br0">&#40;</span>diff <span class="sy0">!</span>= <span class="nu0">0</span><span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;<span class="kw1">if</span> <span class="br0">&#40;</span><span class="sy0">!</span>littleEndian<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw2">return</span> <span class="br0">&#40;</span>lw + <span class="kw3">Long</span>.<span class="me1">MIN_VALUE</span><span class="br0">&#41;</span> <span class="sy0">&lt;</span> <span class="br0">&#40;</span>rw + <span class="kw3">Long</span>.<span class="me1">MIN_VALUE</span><span class="br0">&#41;</span> <span class="sy0">?</span> <span class="nu0">-1</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; : <span class="nu0">1</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;<span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;<span class="co1">// Use binary search,一下省略若干代码</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;.....</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;<span class="kw2">return</span> <span class="br0">&#40;</span><span class="kw4">int</span><span class="br0">&#41;</span> <span class="br0">&#40;</span><span class="br0">&#40;</span><span class="br0">&#40;</span>lw <span class="sy0">&gt;&gt;&gt;</span> n<span class="br0">&#41;</span> <span class="sy0">&amp;</span> 0xFFL<span class="br0">&#41;</span> - <span class="br0">&#40;</span><span class="br0">&#40;</span>rw <span class="sy0">&gt;&gt;&gt;</span> n<span class="br0">&#41;</span> <span class="sy0">&amp;</span> 0xFFL<span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="co1">// The epilogue to cover the last (minLength % <img src='http://www.yankay.com/wp-includes/images/smilies/icon_cool.gif' alt='8)' class='wp-smiley' /> elements.</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw1">for</span> <span class="br0">&#40;</span><span class="kw4">int</span> i = minWords <span class="sy0">*</span> Longs.<span class="me1">BYTES</span><span class="sy0">;</span> i <span class="sy0">&lt;</span> minLength<span class="sy0">;</span> i++<span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw4">int</span> result = UnsignedBytes.<span class="me1">compare</span><span class="br0">&#40;</span>buffer1<span class="br0">&#91;</span>offset1 + i<span class="br0">&#93;</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; buffer2<span class="br0">&#91;</span>offset2 + i<span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw1">if</span> <span class="br0">&#40;</span>result <span class="sy0">!</span>= <span class="nu0">0</span><span class="br0">&#41;</span> <span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp;<span class="kw2">return</span> result<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;<span class="kw2">return</span> length1 - length2<span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#125;</span></div>
</li>
</ol>
</div>
<p>实现比原来复杂了一些。但这次一次可以比较8个字节了。这种getLong函数和系统的字节序是紧紧相关的，如果是小端序操作起来有点麻烦，代码先省略掉。这样操作实际效果如何？我们需要对比测试下。对比两个1M的字节数组，如果使用第一个版本，每次比较平均需要2.5499ms,如果使用第二个版本，需要0.8359ms,提升了3倍。对应这种CPU密集型的操作，这样的提升可是很可观的。</p>
<p>如果要提升性能，使用Unsafe直接访问内存也是不错的选择。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/java-fast-byte-comparison/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>HBase介绍PPT</title>
		<link>http://www.yankay.com/hbase-slider/</link>
		<comments>http://www.yankay.com/hbase-slider/#comments</comments>
		<pubDate>Wed, 14 Mar 2012 06:56:51 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[软件技术]]></category>
		<category><![CDATA[Hbase]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26426</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<div style="width:425px" id="__ss_11999745"><strong style="display:block;margin:12px 0 4px"><a href="http://www.slideshare.net/kaiyannju/hbase-11999745" title="Hbase介绍">Hbase介绍</a></strong><object id="__sse11999745" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=hbase-120314014437-phpapp01&#038;stripped_title=hbase-11999745&#038;userName=kaiyannju" /><param name="allowFullScreen" value="true"/><param name="allowScriptAccess" value="always"/><param name="wmode" value="transparent"/><embed name="__sse11999745" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=hbase-120314014437-phpapp01&#038;stripped_title=hbase-11999745&#038;userName=kaiyannju" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" wmode="transparent" width="425" height="355"></embed></object></div>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/hbase-slider/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Amazon EBS架构猜想</title>
		<link>http://www.yankay.com/amazon-ebs%e6%9e%b6%e6%9e%84%e7%8c%9c%e6%83%b3/</link>
		<comments>http://www.yankay.com/amazon-ebs%e6%9e%b6%e6%9e%84%e7%8c%9c%e6%83%b3/#comments</comments>
		<pubDate>Thu, 08 Mar 2012 10:19:19 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[软件技术]]></category>
		<category><![CDATA[Amazon EBS]]></category>
		<category><![CDATA[EBS]]></category>
		<category><![CDATA[EBS架构]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26394</guid>
		<description><![CDATA[Amazon EBS是专门为Amazon EC2 虚拟机设计的弹性块存储服务。Amazon EBS可以为Amazon EC2的虚拟机创建卷volumes，Amazon EBS卷类似没有格式化的外部卷设备。卷有设备名称，同时也提供了块设备接口。你可以在Amazon EBS 卷上驻留自己的文件系统，或者直接作为卷设备使用。也就是说EBS就是一个基于集群的完美的“大磁盘”，可以随机读写，较高性能，完美的一致性和高可靠。本来以为这只是幻象，十分不好做。杨公一席话让我茅塞顿开。所以猜测EBS的架构如下，内部人士不要笑话我。 架构 EBS虚拟磁盘驱动器：EBS客户端构件，和EC2部署在一起，同生共死。作为操作系统的磁盘驱动器存在。作用是管理该磁盘的块,接受磁盘请求。 缓冲存储：我猜测，如果要想保证高性能，同时保证数据不丢失，需要使用一个本地的持久化存储作为缓冲和缓存。直接对集群进行大量的细碎的操作，延迟是不可接受的。如果使用长连接，多网卡，可以让延迟变得可以接受，那么这个组件就不是必须的。 类S3:类似S3的Key-Value存储。有高可靠，高延时，高吞吐的特点。肯定不是S3，也许也不是Key-value，但是大致是类似的。不可修改和按块存储的特性是相似的。 类Mysql:类似Mysql的DB,用来存储块的信息，必须高可靠，容量不比太大，压力也不大。 Slave:可选组件，Slave会记录磁盘驱动器的每个操作，同步其日志。如果EC2那台机器的缓冲存储损坏，可以使用Slave上面的来恢复最近这段时间没有同步到S3的数据。 存储方式 存储的基本单位是块。每个块由Key和Value组成。块的Key分三部分:diskNo-blockNo-version,Value就是Block的内容。块存储在"S3"中，每个块都是不可以修改的，逻辑上的修改通过增加版本来实现。同时不是每一次修改都必须增加版本的。具体方式下面说。块的大小估计在4M左右，要综合"S3"的性能来决定。 一个“盘”由若干个“块”构成，需要记录每一个"块版本号"，blockNo和块的个数在盘创建的时候就已经决定了。“块版本号”信息持久化存储在Mysql中。 这个架构分两层，S3是底层，负责不断存储“盘”的快照。本地缓冲提供低延迟读写。 实现 下面分几种情况，分别来讨论如何实现。 创建，挂载盘 当创建一个盘的时候，只要在Mysql生成一个新的DiskNo。根据盘的大小和Block的大小，计算出Block数量，在Mysql中初始化元信息，将每个Block的版本标记为0(Block在物理上还不存在)。 然后“磁盘驱动器”挂载他，将Mysql中的源信息，加载到内存中。如果上次非正常关闭，可以通过缓冲存储中的数据，执行恢复操作。因为这个盘不是共享的只有该EC2可以使用。所以挂载后不需要再读Mysql.所以对元数据的操作都发生在内存中，每隔一段时间(比如10分钟)，将元数据增量添加到Mysql。 有一点需要注意的是，如果一个块在元数据中有，这个块的数据可能在本地缓冲也可能在S3上。但是如果在Mysql的元数据中有，S3上必须存在有该块。 读块 根据blockId，可以得之其最近版本，并且是在本地缓冲存储还是在"S3"，直接访问即可。读过的块可以放入缓冲。 写块 写块比较复杂。当发起一个写操作的时候，如果本地不存在或者正在同步，本地会先写入一个临时块，写入成功就返回成功。然后会找到"S3"上的块，下载合并修改。如果本地存在，并且该块不在同步中，就直接修改。 每隔一段时间，系统会建立一个check point，将修改的块增加一个版本号，同步到“S3”中去。这里的同步是异步的。全部完成算完成，不存在中间状态。如果操作系统对一个块修改10次，但这些修改操作在两次同步之间，只增加一个版本，避免重复。 缓冲存储损坏 如果缓冲存储损坏，如果没有Slave。由于S3和Mysql中有上一次的快照信息，所以可以恢复到最近的快照状态。不会出现一致性问题。但Check Point之间的时间间隔可能比较长，如果无法忍受的话，可以建立一个Slave用来记录所有的写操作，但缓冲存储损坏的时候，可以通过Slave上的数据恢复到最近的点。 总结 分布式的虚拟磁盘，可以通过两层存储架构，同时满足严格的一致性和分区要求，也有好的随机读写性能。之所以可以采取两层存储，是因为一块“盘”只能一台机器独享，不要求共享，相当于在可分区的特性上打了个折扣，所以这样应该是没问题的。 亚马逊的测试报告也是写的性能远远大于读性能，和这个架构的特点也是相似的。暂时没有发现什么冲突的地方。当然这个架构只是我的猜想，做不得数的。 好像除了亚马逊，没有其他公司对这种磁盘系统感兴趣，也许是没有必要吧。这样虚拟出来的磁盘性能有限，而且系统层次越多越不稳定。但“云计算”招摇撞骗，大行其道，探索一下也好。]]></description>
			<content:encoded><![CDATA[<p>Amazon EBS是专门为Amazon EC2 虚拟机设计的弹性块存储服务。Amazon EBS可以为Amazon EC2的虚拟机创建卷volumes，Amazon EBS卷类似没有格式化的外部卷设备。卷有设备名称，同时也提供了块设备接口。你可以在Amazon EBS 卷上驻留自己的文件系统，或者直接作为卷设备使用。也就是说EBS就是一个基于集群的完美的“大磁盘”，可以随机读写，较高性能，完美的一致性和高可靠。本来以为这只是幻象，十分不好做。杨公一席话让我茅塞顿开。所以猜测EBS的架构如下，内部人士不要笑话我。</p>
<h3>架构</h3>
<p style="text-align: center;"><a href="http://www.yankay.com/wp-content/uploads/2012/03/EBS-arch.png" rel="shadowbox[sbpost-26394];player=img;"><img class="size-full wp-image-26398 aligncenter" title="EBS-arch" src="http://www.yankay.com/wp-content/uploads/2012/03/EBS-arch.png" alt="" width="336" height="273" /></a></p>
<ul>
<li><strong>EBS虚拟磁盘驱动器</strong>：EBS客户端构件，和EC2部署在一起，同生共死。作为操作系统的磁盘驱动器存在。作用是管理该磁盘的块,接受磁盘请求。</li>
<li><strong>缓冲存储</strong>：我猜测，如果要想保证高性能，同时保证数据不丢失，需要使用一个本地的持久化存储作为缓冲和缓存。直接对集群进行大量的细碎的操作，延迟是不可接受的。如果使用长连接，多网卡，可以让延迟变得可以接受，那么这个组件就不是必须的。</li>
<li><strong>类S3</strong>:类似S3的Key-Value存储。有高可靠，高延时，高吞吐的特点。肯定不是S3，也许也不是Key-value，但是大致是类似的。不可修改和按块存储的特性是相似的。</li>
<li><strong>类Mysql</strong>:类似Mysql的DB,用来存储块的信息，必须高可靠，容量不比太大，压力也不大。</li>
<li><strong>Slave</strong>:可选组件，Slave会记录磁盘驱动器的每个操作，同步其日志。如果EC2那台机器的缓冲存储损坏，可以使用Slave上面的来恢复最近这段时间没有同步到S3的数据。</li>
</ul>
<h3><span style="line-height: 18px;">存储方式</span></h3>
<p>存储的基本单位是块。每个块由Key和Value组成。块的Key分三部分:diskNo-blockNo-version,Value就是Block的内容。块存储在"S3"中，每个块都是不可以修改的，逻辑上的修改通过增加版本来实现。同时不是每一次修改都必须增加版本的。具体方式下面说。块的大小估计在4M左右，要综合"S3"的性能来决定。</p>
<p>一个“盘”由若干个“块”构成，需要记录每一个"块版本号"，blockNo和块的个数在盘创建的时候就已经决定了。“块版本号”信息持久化存储在Mysql中。</p>
<p>这个架构分两层，S3是底层，负责不断存储“盘”的快照。本地缓冲提供低延迟读写。</p>
<h3>实现</h3>
<p>下面分几种情况，分别来讨论如何实现。</p>
<h4><strong>创建，挂载盘</strong></h4>
<p>当创建一个盘的时候，只要在Mysql生成一个新的DiskNo。根据盘的大小和Block的大小，计算出Block数量，在Mysql中初始化元信息，将每个Block的版本标记为0(Block在物理上还不存在)。</p>
<p>然后“磁盘驱动器”挂载他，将Mysql中的源信息，加载到内存中。如果上次非正常关闭，可以通过缓冲存储中的数据，执行恢复操作。因为这个盘不是共享的只有该EC2可以使用。所以挂载后不需要再读Mysql.所以对元数据的操作都发生在内存中，每隔一段时间(比如10分钟)，将元数据增量添加到Mysql。</p>
<p>有一点需要注意的是，如果一个块在元数据中有，这个块的数据可能在本地缓冲也可能在S3上。但是如果在Mysql的元数据中有，S3上必须存在有该块。</p>
<h4><strong>读块</strong></h4>
<p>根据blockId，可以得之其最近版本，并且是在本地缓冲存储还是在"S3"，直接访问即可。读过的块可以放入缓冲。</p>
<h4><strong>写块</strong></h4>
<p>写块比较复杂。当发起一个写操作的时候，如果本地不存在或者正在同步，本地会先写入一个临时块，写入成功就返回成功。然后会找到"S3"上的块，下载合并修改。如果本地存在，并且该块不在同步中，就直接修改。</p>
<p>每隔一段时间，系统会建立一个check point，将修改的块增加一个版本号，同步到“S3”中去。这里的同步是异步的。全部完成算完成，不存在中间状态。如果操作系统对一个块修改10次，但这些修改操作在两次同步之间，只增加一个版本，避免重复。</p>
<p><strong>缓冲存储损坏</strong></p>
<p>如果缓冲存储损坏，如果没有Slave。由于S3和Mysql中有上一次的快照信息，所以可以恢复到最近的快照状态。不会出现一致性问题。但Check Point之间的时间间隔可能比较长，如果无法忍受的话，可以建立一个Slave用来记录所有的写操作，但缓冲存储损坏的时候，可以通过Slave上的数据恢复到最近的点。</p>
<h3>总结</h3>
<p>分布式的虚拟磁盘，可以通过两层存储架构，同时满足严格的一致性和分区要求，也有好的随机读写性能。之所以可以采取两层存储，是因为一块“盘”只能一台机器独享，不要求共享，相当于在可分区的特性上打了个折扣，所以这样应该是没问题的。</p>
<p>亚马逊的测试报告也是写的性能远远大于读性能，和这个架构的特点也是相似的。暂时没有发现什么冲突的地方。当然这个架构只是我的猜想，做不得数的。</p>
<p>好像除了亚马逊，没有其他公司对这种磁盘系统感兴趣，也许是没有必要吧。这样虚拟出来的磁盘性能有限，而且系统层次越多越不稳定。但“云计算”招摇撞骗，大行其道，探索一下也好。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/amazon-ebs%e6%9e%b6%e6%9e%84%e7%8c%9c%e6%83%b3/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Linux大文件传输</title>
		<link>http://www.yankay.com/linux%e5%a4%a7%e6%96%87%e4%bb%b6%e4%bc%a0%e8%be%93/</link>
		<comments>http://www.yankay.com/linux%e5%a4%a7%e6%96%87%e4%bb%b6%e4%bc%a0%e8%be%93/#comments</comments>
		<pubDate>Tue, 07 Feb 2012 06:35:49 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[每日心得]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[传输]]></category>
		<category><![CDATA[大文件]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26367</guid>
		<description><![CDATA[我们经常需要在机器之间传输文件。比如备份，复制数据等等。这个是很常见，也是很简单的。用scp或者rsync就能很好的完成任务。但是如果文件很大，需要占用一些传输时间的时候，怎样又快又好地完成任务就很重要了。在我的测试用例中，一个最佳的方案比最差的方案，性能提高了10倍。 复制文件 如果我们是复制一个未压缩的文件。这里走如下步骤： 压缩数据 发送到另外一台机器上 数据解压缩 校验正确性 这样做会很有效率，数据压缩后可以更有效的利用带宽 使用ZIP+SCP 我们可以通过ZIP+SCP的组合实现这个功能。 gzip -c /home/yankay/data &#124; ssh yankay01 "gunzip -c - &#62; /home/yankay/data" 这条命令是将/home/yankay/data经过GZIP压缩，通过ssh传输到yankay01的机器上。 data文件的大小是1.1GB,经过Zip压缩后是183MB，执行上面的命令需要45.6s。平均吞吐量为24.7MB/s 我们会发现Scp也有压缩功能，所以上面的语句可以写成 scp -C -c blowfish /home/yankay/data yankay01:/home/yankay/data 这样运行效果是相同的，不通之处在于我使用了blowfish算法作为Scp的密匙算法，使用这个算法可以比默认的情况快很多。单单对与scp,使用了blowfish 吞吐量是62MB/s,不使用只有46MB/s。 可是我执行上面一条命令的时候，发现还是需要45s。平均吞吐量还为24MB/s。没有丝毫的提升，可见瓶颈不在网络上。 那瓶颈在哪里呢？ 性能分析 我们先定义几个变量 压缩工具的压缩比是 CompressRadio 压缩工具的压缩吞吐是CompressSpeed MB/s &#8230;<p class="read-more"><a href="http://www.yankay.com/linux%e5%a4%a7%e6%96%87%e4%bb%b6%e4%bc%a0%e8%be%93/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<div>我们经常需要在机器之间传输文件。比如备份，复制数据等等。这个是很常见，也是很简单的。用scp或者rsync就能很好的完成任务。但是如果文件很大，需要占用一些传输时间的时候，怎样又快又好地完成任务就很重要了。在我的测试用例中，一个最佳的方案比最差的方案，性能提高了10倍。</div>
<h3><strong>复制文件</strong></h3>
<div>如果我们是复制一个<strong>未压缩</strong>的文件。这里走如下步骤：</div>
<div>
<ol>
<li><span style="line-height: 19px;">压缩数据</span></li>
<li><span style="line-height: 19px;">发送到另外一台机器上</span></li>
<li><span style="line-height: 19px;">数据解压缩</span></li>
<li><span style="line-height: 19px;">校验正确性</span></li>
</ol>
<div>这样做会很有效率，数据压缩后可以更有效的利用带宽</div>
</div>
<h3><strong>使用ZIP+SCP</strong></h3>
<div>我们可以通过ZIP+SCP的组合实现这个功能。</div>
<div>
<pre>gzip -c /home/yankay/data | ssh yankay01 "gunzip -c - &gt; /home/yankay/data"</pre>
<p>这条命令是将/home/yankay/data经过GZIP压缩，通过ssh传输到yankay01的机器上。</p>
</div>
<div>data文件的大小是1.1GB,经过Zip压缩后是183MB，执行上面的命令需要45.6s。平均吞吐量为24.7MB/s</div>
<div>我们会发现Scp也有压缩功能，所以上面的语句可以写成</div>
<div>
<pre>scp -C -c blowfish /home/yankay/data yankay01:/home/yankay/data</pre>
<p>这样运行效果是相同的，不通之处在于我使用了blowfish算法作为Scp的密匙算法，使用这个算法可以比默认的情况快很多。单单对与scp,使用了blowfish 吞吐量是62MB/s,不使用只有46MB/s。</p>
</div>
<div>可是我执行上面一条命令的时候，发现还是需要45s。平均吞吐量还为24MB/s。没有丝毫的提升，可见瓶颈不在网络上。</div>
<div>那瓶颈在哪里呢？</div>
<h3><strong>性能分析</strong></h3>
<p>我们先定义几个变量</p>
<ul>
<li>压缩工具的压缩比是 CompressRadio</li>
<li>压缩工具的压缩吞吐是CompressSpeed MB/s</li>
<li>网络传输的吞吐是 NetSpeed MB/s</li>
</ul>
<p>由于使用了管道，管道的性能取决于管道中最慢的部分的性能，所以整体的性能是：</p>
<p><span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_9c615bedbd3b3d46dcc03b5d71b40ae9.gif' style='vertical-align: middle; border: none; ' class='tex' alt="speed=min(NetSpeed/CompressRadio,CompressSpeed)" /></span><script type='math/tex'>speed=min(NetSpeed/CompressRadio,CompressSpeed)</script></p>
<p>当压缩吞吐较网络传输慢的时候，压缩是瓶颈；但网络较慢的时候，网络传输/吞吐 是瓶颈。</p>
<p>根据现有的测试数据(纯文本)，可以得到表格：</p>
<table width="615" border="0" cellspacing="0" cellpadding="0">
<colgroup>
<col span="2" width="72" />
<col width="64" />
<col width="146" />
<col width="129" />
<col width="132" /> </colgroup>
<tbody>
<tr>
<td width="72" height="18"></td>
<td width="72">压缩比</td>
<td width="64">吞吐量</td>
<td width="146">千兆网卡(100MB/s)吞吐量</td>
<td width="129">千兆网卡吞吐量,基于ssh(62MB/s)</td>
<td width="132">百兆网卡(10MB/s)吞吐量</td>
</tr>
<tr>
<td height="18">ZLIB</td>
<td align="right">35.80%</td>
<td align="right">9.6</td>
<td align="right">9.6</td>
<td align="right">9.6</td>
<td align="right">9.6</td>
</tr>
<tr>
<td height="18">LZO</td>
<td align="right">54.40%</td>
<td align="right">101.7</td>
<td align="right">101.7</td>
<td align="right">101.7</td>
<td align="right">18.38235294</td>
</tr>
<tr>
<td height="18">LIBLZF</td>
<td align="right">54.60%</td>
<td align="right">134.3</td>
<td align="right">134.3</td>
<td align="right">113.5531136</td>
<td align="right">18.31501832</td>
</tr>
<tr>
<td height="18">QUICKLZ</td>
<td align="right">54.90%</td>
<td align="right">183.4</td>
<td align="right">182.1493625</td>
<td align="right">112.9326047</td>
<td align="right">18.21493625</td>
</tr>
<tr>
<td height="18">FASTLZ</td>
<td align="right">56.20%</td>
<td align="right">134.4</td>
<td align="right">134.4</td>
<td align="right">110.3202847</td>
<td align="right">17.79359431</td>
</tr>
<tr>
<td height="18">SNAPPY</td>
<td align="right">59.80%</td>
<td align="right">189</td>
<td align="right">167.2240803</td>
<td align="right">103.6789298</td>
<td align="right">16.72240803</td>
</tr>
<tr>
<td height="18">NONE</td>
<td align="right">100%</td>
<td align="right">300</td>
<td align="right">100</td>
<td align="right">62</td>
<td align="right">10</td>
</tr>
</tbody>
</table>
<p>可以看出来。在千兆网卡下，使用QuickLZ作为压缩算法，可以达到最高的性能。如果使用SSH作为数据传输通道，则远远没有达到网卡可以达到的最佳性能。在百兆网卡的情况下，各个算法相近。对比下来QuickLZ是有优势的。</p>
<p>对于不同的数据和不同的机器，可以得出不同的最佳压缩算法。但有一点是肯定的，尽量把瓶颈压在网络上。对于较慢的网络环境，高压缩比的算法会比较有优势；相反对于较快的网络环境，低压缩比的算法会更好。</p>
<h3><strong>结论</strong></h3>
<p>根据上面的分析结果，我们不能是用SSH作为网络传输通道，可以使用NC这个基本网络工具，提高性能。同时使用qpress作为压缩算法。</p>
<pre>scp /usr/bin/qpress yankay01:/usr/bin/qpress
ssh yankay01 "nc -l 12345 |  qpress -dio &gt; /home/yankay/data" &amp;
qpress -o /home/yankay/data |nc yankay01 12345</pre>
<p>第一行是将gpress安装到远程机器上，第二行在远程机器上使用nc监听一个端口，第三行压缩并传送数据。</p>
<p>执行上面的命令需要2.8s。平均吞吐量为402MB/s,比使用Gzip+Scp快了16倍！！</p>
<p>根据上文的公式，和自己的数据，可以绘出上面的表格，就可以选择出最适合的压缩算法和传输方式。达到满意的效果。如果是一个长期运行的脚本的话，这么做是值得的。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/linux%e5%a4%a7%e6%96%87%e4%bb%b6%e4%bc%a0%e8%be%93/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>内存究竟有多快？</title>
		<link>http://www.yankay.com/%e5%86%85%e5%ad%98%e7%a9%b6%e7%ab%9f%e6%9c%89%e5%a4%9a%e5%bf%ab%ef%bc%9f/</link>
		<comments>http://www.yankay.com/%e5%86%85%e5%ad%98%e7%a9%b6%e7%ab%9f%e6%9c%89%e5%a4%9a%e5%bf%ab%ef%bc%9f/#comments</comments>
		<pubDate>Mon, 06 Feb 2012 14:58:10 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[每日心得]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[性能]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26346</guid>
		<description><![CDATA[一般来说。CPU需要0个周期来访问其寄存器，1-30个周期来访问高速缓存，50-200个周期来访问主存。 对于Intel Core i7来说。这个值可以很具体。Intel Core i7的主频约在2-3GHz。可以计算出。 L1—指令缓存 L1-数据缓存 L2-缓存 L3-缓存 内存 访问周期 4 4 11 30-40 50-200 缓存大小 32KB 32KB 256KB 8MB 若干GB 访问时间 2ns 2ns 5ns 14-18ns 24-93ns 也就是说，访问内存的时间是ns级别的。 再来看看磁盘。 磁盘的访问时间=寻道时间+旋转延迟+数据传输时间。对于普通的7200转STAT磁盘。这个值是:9ms+4ms+0.02ms=13.02ms。 也就是说，如果从磁盘随机访问一个字节，需要13.02ms，比从内存获取的时间24-93ns，至少要多14万倍。相差5个数据级，何其巨大的差距。 顺序读写磁盘会快一些。 假设一个盘片有1000个扇区，每个扇区512字节，7200转。顺序读可以忽略掉寻道的时间。所以吞吐量是 扇区数×扇区大小×转速=1000*512/(60/7200)=58MB/s。这个数据似乎不咋样。如果使用多盘系统。STAT II的接口，吞吐量可以达到300MB/s。追求极限性能可以mount裸盘直接操作多盘。 存储器山 《深入理解计算机系统》一书中提到了一个存储器山的概念。教授先生别出心裁的将存储器的吞吐量，画成了一座山。 &#160; 存储器山的测试程序是这样的： &#8230;<p class="read-more"><a href="http://www.yankay.com/%e5%86%85%e5%ad%98%e7%a9%b6%e7%ab%9f%e6%9c%89%e5%a4%9a%e5%bf%ab%ef%bc%9f/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<p>一般来说。CPU需要0个周期来访问其寄存器，1-30个周期来访问高速缓存，50-200个周期来访问主存。</p>
<p>对于Intel Core i7来说。这个值可以很具体。Intel Core i7的主频约在2-3GHz。可以计算出。</p>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="94"></td>
<td valign="top" width="96">L1—指令缓存</td>
<td valign="top" width="95">L1-数据缓存</td>
<td valign="top" width="95">L2-缓存</td>
<td valign="top" width="98">L3-缓存</td>
<td valign="top" width="91">内存</td>
</tr>
<tr>
<td valign="top" width="94">访问周期</td>
<td valign="top" width="96">4</td>
<td valign="top" width="95">4</td>
<td valign="top" width="95">11</td>
<td valign="top" width="98">30-40</td>
<td valign="top" width="91">50-200</td>
</tr>
<tr>
<td valign="top" width="94">缓存大小</td>
<td valign="top" width="96">32KB</td>
<td valign="top" width="95">32KB</td>
<td valign="top" width="95">256KB</td>
<td valign="top" width="98">8MB</td>
<td valign="top" width="91">若干GB</td>
</tr>
<tr>
<td valign="top" width="94">访问时间</td>
<td valign="top" width="96">2ns</td>
<td valign="top" width="95">2ns</td>
<td valign="top" width="95">5ns</td>
<td valign="top" width="98">14-18ns</td>
<td valign="top" width="91">24-93ns</td>
</tr>
</tbody>
</table>
<p>也就是说，访问内存的时间是ns级别的。</p>
<p><strong>再来看看磁盘。</strong></p>
<p>磁盘的访问时间=寻道时间+旋转延迟+数据传输时间。对于普通的7200转STAT磁盘。这个值是:9ms+4ms+0.02ms=13.02ms。</p>
<p>也就是说，如果从磁盘随机访问一个字节，需要13.02ms，比从内存获取的时间24-93ns，至少要多14万倍。相差5个数据级，何其巨大的差距。</p>
<p>顺序读写磁盘会快一些。 假设一个盘片有1000个扇区，每个扇区512字节，7200转。顺序读可以忽略掉寻道的时间。所以吞吐量是 扇区数×扇区大小×转速=1000*512/(60/7200)=58MB/s。这个数据似乎不咋样。如果使用多盘系统。STAT II的接口，吞吐量可以达到300MB/s。追求极限性能可以mount裸盘直接操作多盘。</p>
<p><strong>存储器山</strong></p>
<p>《深入理解计算机系统》一书中提到了一个<a href="http://zh.wikipedia.org/wiki/%E5%AD%98%E5%82%A8%E5%99%A8%E5%B1%B1">存储器山</a>的概念。教授先生别出心裁的将存储器的吞吐量，画成了一座山。</p>
<p><a href="http://www.yankay.com/wp-content/uploads/2012/02/MemoryMountain17.png" rel="shadowbox[sbpost-26346];player=img;"><img class="alignnone size-full wp-image-26348" title="MemoryMountain" src="http://www.yankay.com/wp-content/uploads/2012/02/MemoryMountain17.png" alt="" width="560" height="420" /></a></p>
<p>&nbsp;</p>
<p>存储器山的测试程序是这样的：</p>
<pre><strong>Kernel_loop(elems, stride):</strong>
for (i = 0; i &lt; elems; i += stride)
    result = data[i];</pre>
<p>X轴表示的是读取步长，Y轴是吞吐量，Z轴是数据总量的大小。</p>
<p>可以看出来步长越小，数据数据总量越小。性能越好。</p>
<p>很明显，山是不是平滑的，是成阶梯状。红色部分为L1缓存，绿色为L2缓存，浅蓝是L3缓存，深蓝是内存。我们可以得一些数据。</p>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="77"></td>
<td valign="top" width="91">L1-数据缓存</td>
<td valign="top" width="74">L2-缓存</td>
<td valign="top" width="89">L3-缓存</td>
<td valign="top" width="83">内存</td>
<td valign="top" width="71">磁盘</td>
<td valign="top" width="71">SSD</td>
</tr>
<tr>
<td valign="top" width="77">缓存大小</td>
<td valign="top" width="91">32KB</td>
<td valign="top" width="74">256KB</td>
<td valign="top" width="89">8MB</td>
<td valign="top" width="83">十几GB</td>
<td valign="top" width="71">几TB</td>
<td valign="top" width="71">几百GB</td>
</tr>
<tr>
<td valign="top" width="77">访问时间</td>
<td valign="top" width="91">2ns</td>
<td valign="top" width="74">5ns</td>
<td valign="top" width="89">14-18ns</td>
<td valign="top" width="83">24-93ns</td>
<td valign="top" width="71">13.0ms</td>
<td valign="top" width="71">30-300us</td>
</tr>
<tr>
<td valign="top" width="77">吞吐量</td>
<td valign="top" width="91">6500MB/s</td>
<td valign="top" width="74">3300MB/s</td>
<td valign="top" width="89">2200MB/s</td>
<td valign="top" width="83">800MB/s</td>
<td valign="top" width="71">60MB/s</td>
<td valign="top" width="71">250MB/s</td>
</tr>
</tbody>
</table>
<p>也就是说，去除高速缓存的内存，吞吐量性能只有800MB/s而已。比起磁盘的300MB/s,网络的100MB/s。也只是快了几倍。平时说内存比磁盘快许多，其实没有那么多，如果不好好操作内存，内存的频繁读写，也可以成为系统瓶颈。</p>
<p><strong>总结</strong></p>
<p>现在处理器的主频已经停止了增长。但是高速缓存仍然以摩尔定律的速度增长的。长久的看，高速缓存频率逐渐会追上处理器的性能，容量也会越来越大。但是内存则不容乐观，虽然容量增加了许多，但是性能确没有大的提升,磁盘的状况也是类似;SSD刚刚开始普及,趋势不明显。</p>
<p>但可以看到，SSD的吞吐量和内存的吞吐量相去并不大。也就是说在未来，当SSD完全替代了磁盘。我们要像现在操作磁盘一样小心翼翼地操作内存，才有可能写出符合那个时代计算机性能的程序。相比之下，SSD的使用比磁盘要轻松一些，毕竟随机读写的速度在一两个数量级上。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/%e5%86%85%e5%ad%98%e7%a9%b6%e7%ab%9f%e6%9c%89%e5%a4%9a%e5%bf%ab%ef%bc%9f/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>并发编程之巧用锁</title>
		<link>http://www.yankay.com/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e4%b9%8b%e5%b7%a7%e7%94%a8%e9%94%81/</link>
		<comments>http://www.yankay.com/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e4%b9%8b%e5%b7%a7%e7%94%a8%e9%94%81/#comments</comments>
		<pubDate>Sun, 08 Jan 2012 15:22:02 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[每日心得]]></category>
		<category><![CDATA[多核编程]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26320</guid>
		<description><![CDATA[背景 程序都是跑在机器上的，并行CPU包含几个部分。 处理器核心。执行线程的设备。一个核心可以执行一个或多个线程(超线程) 互连线。处理器和处理器，处理器和储存器之间的通信通道。一般CPU分两种架构，SMP对称多处理器和NUMA架构。SMP的通信是总线式的，核心增加后性能会下降。NUMA的结果相当于以太网，性能相对较好。理论上NUMA是没有cache的,不过商业产品都有。互连线的资源是有限的。 存储器。这里存储器值得是一级缓存，二级缓存和内存。一级缓存一般和处理器在一起，访问它需要1-2个时钟周期，访问二级缓存则需要数十个时钟周期。而访问内存，需要数百个时钟周期。 可以看出来，CPU访问内存的代价是很高的。如果一个处理器改变的一个volatile变量，为了保持一致性，需要将这个消息广播到其他的处理器，其他处理器废弃其一级缓存，重新加载。整个操作需要至少数十个时钟周期。而如果CPU仅仅访问其一级缓存的数据，性能就很高。所以多核编程和网络编程一样，性能的瓶颈就在于互连线的使用。 自转锁 自转锁是一种锁的实现方式。就是在while语句中不断监视一个变量，通过观察到变化，保证只有一个线程持有锁。 public void lock(){ &#160; &#160; &#160; &#160; while&#40;state.getAndSet&#40;true&#41;&#41;&#123;&#125; &#160; &#160; &#125; 这里的GetAndSet方法是一个CPU的CAS硬件指令。我们可以看到，每次调用这个质量，CPU要保证缓存一致，丢弃该CPU持有该变量的缓存，重新加载。会消耗至少数十个时钟周期的时间。所以可以进行如下改进。    public void lock(){ &#160; &#160; &#160; &#160; while&#40;true&#41;&#123;         &#160; &#160; while&#40;state.get&#40;&#41;&#41;&#123;&#125;; &#160; &#160; &#160; &#160; &#8230;<p class="read-more"><a href="http://www.yankay.com/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e4%b9%8b%e5%b7%a7%e7%94%a8%e9%94%81/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<p><strong>背景</strong></p>
<p>程序都是跑在机器上的，并行CPU包含几个部分。</p>
<ul>
<li><strong>处理器核心</strong>。执行线程的设备。一个核心可以执行一个或多个线程(超线程)</li>
<li><strong>互连线</strong>。处理器和处理器，处理器和储存器之间的通信通道。一般CPU分两种架构，SMP对称多处理器和NUMA架构。SMP的通信是总线式的，核心增加后性能会下降。NUMA的结果相当于以太网，性能相对较好。理论上NUMA是没有cache的,不过商业产品都有。互连线的资源是<strong>有限的</strong>。</li>
<li><strong>存储器</strong>。这里存储器值得是一级缓存，二级缓存和内存。一级缓存一般和处理器在一起，访问它需要1-2个时钟周期，访问二级缓存则需要数十个时钟周期。而访问内存，需要数百个时钟周期。</li>
</ul>
<div><span style="line-height: 18px;"><a href="http://www.yankay.com/wp-content/uploads/2012/01/image002.gif" rel="shadowbox[sbpost-26320];player=img;"><img class="alignnone size-medium wp-image-26322" title="smp-numa" src="http://www.yankay.com/wp-content/uploads/2012/01/image002.gif" alt="" width="555" height="279" /></a></span></div>
<div>可以看出来，CPU访问内存的代价是很高的。如果一个处理器改变的一个volatile变量，为了保持一致性，需要将这个消息广播到其他的处理器，其他处理器废弃其一级缓存，重新加载。整个操作需要至少数十个时钟周期。而如果CPU仅仅访问其一级缓存的数据，性能就很高。所以多核编程和网络编程一样，性能的瓶颈就在于互连线的使用。</div>
<div><strong>自转锁</strong></div>
<div>自转锁是一种锁的实现方式。就是在while语句中不断监视一个变量，通过观察到变化，保证只有一个线程持有锁。</div>
<div>
<div class="geshi no java">
<div class="head">public void lock(){</div>
<ol>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">while</span><span class="br0">&#40;</span>state.<span class="me1">getAndSet</span><span class="br0">&#40;</span><span class="kw2">true</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#123;</span><span class="br0">&#125;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="br0">&#125;</span></div>
</li>
</ol>
</div>
</div>
<div>
<div>
<p>这里的GetAndSet方法是一个CPU的CAS硬件指令。我们可以看到，每次调用这个质量，CPU要保证缓存一致，丢弃该CPU持有该变量的缓存，重新加载。会消耗至少数十个时钟周期的时间。所以可以进行如下改进。</p>
<div class="geshi no java">
<div class="head">   public void lock(){</div>
<ol>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">while</span><span class="br0">&#40;</span><span class="kw2">true</span><span class="br0">&#41;</span><span class="br0">&#123;</span></div>
</li>
<li class="li1">
<div class="de1">        &nbsp; &nbsp; <span class="kw1">while</span><span class="br0">&#40;</span>state.<span class="me1">get</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#123;</span><span class="br0">&#125;</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span><span class="br0">&#40;</span><span class="sy0">!</span>state.<span class="me1">getAndSet</span><span class="br0">&#40;</span><span class="kw2">true</span><span class="br0">&#41;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">return</span><span class="sy0">;</span></div>
</li>
<li class="li1">
<div class="de1">    <span class="br0">&#125;</span></div>
</li>
</ol>
</div>
</div>
<p>这个锁和上面的锁逻辑上是一样的。区别是处理器在自旋的时候，不再执行CAS，而是执行一个get操作，直接从一级缓存中取数据，不主动同步。让发现状态有所变化后，再通过CAS操作确认并加锁。这样可以大大减少CAS指令的数量，节约了处理器之间的信息流量。</p>
<p>当然这个锁还有很多可以改进的地方，不详述了。不过其中“乐观”的思想是值得借鉴的。下面总结了一个用锁时候的方法，可以提高性能。</p>
<p><strong>细粒度同步</strong></p>
<p>细粒度同步避免对整个数据结构上锁。比如Java语言中的ConcurrentHashMap相对普通的HashMap性能要高很多。下图是ConcurrentHashMap的结构。</p>
<p><img class="alignnone" src="http://pic.yupoo.com/goldendoc/Ba4GCFe1/nuEZ0.png" alt="" width="995" height="530" /></p>
<p><a title="Java并发编程之ConcurrentHashMap" href="http://www.goldendoc.org/2011/06/">《Java并发编程之ConcurrentHashMap》</a>很好的解释了ConcurrentHashMap的实现。</p>
<p><strong>读写锁</strong></p>
<p>许多共享对象都是这样的，读可以并发进行，不会去修改对象。而写操作需要线性进行。如果读操作远远大于写操作，区分读写行为，可以提高程序的并发性。</p>
<p><strong>乐观同步</strong></p>
<p>上面的自旋锁，就是一个乐观的例子。乐观就是先取出变量，乐观的认为没有冲突，在最后再确认下没有冲突，如果有冲突，重新再执行一边。这样就把麻烦的事情放到了最后，可以减少整段程序中加锁的部分，提高并行性。</p>
<p><strong>惰性同步</strong></p>
<p><strong></strong>惰性同步和乐观思路是一样的。在修改变量的时候，分两个阶段，先修改好，再在最后确认没有冲突，完成修改。</p>
<p><strong>非阻塞操作</strong></p>
<p>非阻塞操作就是CAS操作。使用这些操作可以避免使用加锁的传统方法，但实现一个非阻塞的共享数据结构非常的复杂，很容易出问题。性能的提升不是没有成本的。关于CAS操作，有一个缺陷，即ABA问题。CAS（compare and swap）操作的语义是(先比较原来的值和新的值是否一致，如果一致则更新并返回True,不一致则返回False)。这个操作<strong>不是原子</strong>的，中途如果有其他的线程先将这个地址修改为另一个值在改回来，一样会返回True。所以要注意这个特性，否则就可能造成一些Bug.</p>
<p>性能的提升很不容易，多点复杂性，就多点Bug的可能。并发编程的Bug还不怎么好找出来。所以我觉得，善用现有的久经考验的数据结构，少自己操作底层的原语，等到发现性能问题的时候，再在性能瓶颈的部分履薄冰地编程，尽可能少的引入复杂度。话说Java的LinkedTransferQueue使用了更精致的并发编程可以极大的提高队列的性能，可到Java7才成为JDK的一部分。可见实现一个并发的队列有多难了。</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e4%b9%8b%e5%b7%a7%e7%94%a8%e9%94%81/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>查询利器-bloom-filter详解</title>
		<link>http://www.yankay.com/%e6%9f%a5%e8%af%a2%e5%88%a9%e5%99%a8-bloom-filter%e8%af%a6%e8%a7%a3/</link>
		<comments>http://www.yankay.com/%e6%9f%a5%e8%af%a2%e5%88%a9%e5%99%a8-bloom-filter%e8%af%a6%e8%a7%a3/#comments</comments>
		<pubDate>Sat, 12 Nov 2011 16:06:35 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[每日心得]]></category>
		<category><![CDATA[Bloom Filter]]></category>
		<category><![CDATA[Hadoop]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26267</guid>
		<description><![CDATA[布隆过滤器（Bloom Filter）是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。本文着重于在实现Bloom Filter的时候会使用到的一些技巧。 布隆过滤器的原理不难理解。相对于一个精简的HashMap的数据结构，存入数据的时候，不存入数据本身，只保存其Hash的值。可以用于判断该数据是否存在。其本质是用Hash对数据进行"有损压缩"的位图索引。详细参见。 &#160; 错误率 如果用来存放Hash值的槽位足够多，那么碰撞的概率就会比较小。但是所占用的空间就会比较大。所以当分配空间的时候，需要通过你能容忍的错误率和需要存放的Key的数量来指定。如果所需存储的Key数量是n,错误率是p，所需要的槽位是m。有计算槽位的公式m=-\frac{n\ln p}{(\ln 2)^2}.，也有计算概率的公式p = \left( 1-e^{-(m/n\ln 2) n/m} \right)^{(m/n\ln 2)}。这些公式当然不是我推导出来的，想来也不太难，就不赘述推导过程了。下面这张图可以很好的表示n和m取不同的值的时候，p的值。 根据这张图。我们可以计算出所需要的内存使用量。如果把错误率控制在1%以下的话。 保存key数 占用空间 1万 64KB 10万 1MB 100万 16MB 1000万 256MB 1亿 &#60;4GB &#160; 可见占用的空间在key的数量在百万级别还是很划算的，但到了上亿的级别就不那么划算了。 Bloom Filter的插入和查询都是常数级别的，所以最大的问题就是占用内存过大。而初次分配内存的时候，如果没有能够确认槽位的个数。如果分配过多会导致内存浪费，太少就会倒是错误率过高。下面提到的两个改进方案可以分别解决这两个问题。 折叠 折叠是指当你初始化一个Bloom Filter的时候，可以分配足够大的槽位，等到Key导入完毕后，可以对使用的槽位进行合并操作。具体方法是将槽位切成两半，一边完全叠加到另一边上。减少内存的使用量。检查key的代码要做稍许改变。例： &#160; 通过这个操作，可以使实际使用的内存量减半。多执行几次，能减少更多。 动态扩展 通过折叠操作，可以解决分配过大的问题，但是如果一开始分配过小，就需要扩展槽位才行。如何扩展呢？只要按原尺寸再建立一个Bloom &#8230;<p class="read-more"><a href="http://www.yankay.com/%e6%9f%a5%e8%af%a2%e5%88%a9%e5%99%a8-bloom-filter%e8%af%a6%e8%a7%a3/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<p><a href="http://zh.wikipedia.org/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8 " target="_blank">布隆过滤器</a>（Bloom Filter）是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。本文着重于在实现Bloom Filter的时候会使用到的一些技巧。</p>
<p>布隆过滤器的原理不难理解。相对于一个精简的HashMap的数据结构，存入数据的时候，不存入数据本身，只保存其Hash的值。可以用于判断该数据是否存在。其本质是用Hash对数据进行"有损压缩"的位图索引。详细<a title="Hash和Bloom Filter" href="http://www.sigma.me/2011/09/13/hash-and-bloom-filter.html" target="_blank">参见</a>。</p>
<p><a href="http://farm7.static.flickr.com/6053/6336783480_5f7e19e905.jpg" rel="shadowbox"><img src="http://farm7.static.flickr.com/6053/6336783480_5f7e19e905.jpg" alt="Bloom_filter" width="500" height="180" /></a></p>
<p>&nbsp;</p>
<h3>错误率</h3>
<p>如果用来存放Hash值的槽位足够多，那么碰撞的概率就会比较小。但是所占用的空间就会比较大。所以当分配空间的时候，需要通过你能容忍的错误率和需要存放的Key的数量来指定。如果所需存储的Key数量是n,错误率是p，所需要的槽位是m。有计算槽位的公式<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_e81898e5f58942aaf3e1acd0fc67c3de.gif' style='vertical-align: middle; border: none; ' class='tex' alt="m=-\frac{n\ln p}{(\ln 2)^2}." /></span><script type='math/tex'>m=-\frac{n\ln p}{(\ln 2)^2}.</script>，也有计算概率的公式<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_a526e2891eca62974b9f6e8b8ff80111.gif' style='vertical-align: middle; border: none; ' class='tex' alt="p = \left( 1-e^{-(m/n\ln 2) n/m} \right)^{(m/n\ln 2)}" /></span><script type='math/tex'>p = \left( 1-e^{-(m/n\ln 2) n/m} \right)^{(m/n\ln 2)}</script>。这些公式当然不是我推导出来的，想来也不太难，就不赘述推导过程了。下面这张图可以很好的表示n和m取不同的值的时候，p的值。</p>
<p><a href="http://upload.wikimedia.org/wikipedia/commons/e/ef/Bloom_filter_fp_probability.svg"><img src="http://upload.wikimedia.org/wikipedia/commons/e/ef/Bloom_filter_fp_probability.svg" alt="Bloom_filter" width="500" /></a></p>
<p>根据这张图。我们可以计算出所需要的内存使用量。如果把错误率控制在1%以下的话。</p>
<table width="80%">
<tbody>
<tr>
<th>保存key数</th>
<th>占用空间</th>
</tr>
<tr>
<td>1万</td>
<td>64KB</td>
</tr>
<tr>
<td>10万</td>
<td>1MB</td>
</tr>
<tr>
<td>100万</td>
<td>16MB</td>
</tr>
<tr>
<td>1000万</td>
<td>256MB</td>
</tr>
<tr>
<td>1亿</td>
<td>&lt;4GB</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>可见占用的空间在key的数量在百万级别还是很划算的，但到了上亿的级别就不那么划算了。</p>
<p>Bloom Filter的插入和查询都是常数级别的，所以最大的问题就是占用内存过大。而初次分配内存的时候，如果没有能够确认槽位的个数。如果分配过多会导致内存浪费，太少就会倒是错误率过高。下面提到的两个改进方案可以分别解决这两个问题。</p>
<h3>折叠</h3>
<p>折叠是指当你初始化一个Bloom Filter的时候，可以分配足够大的槽位，等到Key导入完毕后，可以对使用的槽位进行合并操作。具体方法是将槽位切成两半，一边完全叠加到另一边上。减少内存的使用量。检查key的代码要做稍许改变。例：</p>
<p><a href="http://www.yankay.com/wp-content/uploads/2011/11/bloom-filter.png" rel="shadowbox[sbpost-26267];player=img;"><img class="alignnone size-full wp-image-26307" title="bloom filter" src="http://www.yankay.com/wp-content/uploads/2011/11/bloom-filter.png" alt="" width="247" height="99" /></a></p>
<p>&nbsp;</p>
<p>通过这个操作，可以使实际使用的内存量减半。多执行几次，能减少更多。</p>
<h3>动态扩展</h3>
<p>通过折叠操作，可以解决分配过大的问题，但是如果一开始分配过小，就需要扩展槽位才行。如何扩展呢？只要按原尺寸再建立一个Bloom Filter数组。原来的那个保存起来，不再写入。有新的写请求的时候，就将数据写入到新的那个Bloom Filter数组里面去。等到新的也写满了，就再建立一个，以此类推。查询的时候，就需要遍历每一个Bloom Filter数组才行。但因为查询一个Bloom Filter数组的速度很快，查询一组Bloom Filter数组也不会太影响性能。使用这种手段可以是Bloom Filter的大小可以轻易的扩展。但这样做有个的缺陷，就是错误率会随着数组的增加而上升，因为实际的数组长度并没有增加。</p>
<p><a href="http://farm7.static.flickr.com/6228/6336898071_4347601709_b.jpg" rel="shadowbox"><img src="http://farm7.static.flickr.com/6228/6336898071_4347601709.jpg" alt="d-bloom-filter" width="500" height="297" /></a></p>
<p>通过上面的两个方法，就可以解决BloomFilter的分配内存的问题。但无论哪种方法都有自己局限性，折叠每次只能减半，不是很精确。动态增加的方法会造成错误率增加。最好还是能预先估计到这个BloomFilter的容量。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/%e6%9f%a5%e8%af%a2%e5%88%a9%e5%99%a8-bloom-filter%e8%af%a6%e8%a7%a3/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>多核平台下的JAVA优化</title>
		<link>http://www.yankay.com/%e5%a4%9a%e6%a0%b8%e5%b9%b3%e5%8f%b0%e4%b8%8b%e7%9a%84java%e4%bc%98%e5%8c%96/</link>
		<comments>http://www.yankay.com/%e5%a4%9a%e6%a0%b8%e5%b9%b3%e5%8f%b0%e4%b8%8b%e7%9a%84java%e4%bc%98%e5%8c%96/#comments</comments>
		<pubDate>Sat, 05 Nov 2011 12:41:47 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[每日心得]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[多核]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26220</guid>
		<description><![CDATA[现在多核CPU是主流。利用多核技术，可以有效发挥硬件的能力，提升吞吐量，对于Java程序，可以实现并发垃圾收集。但是Java利用多核技术也带来了一些问题，主要是多线程共享内存引起了。目前内存和CPU之间的带宽是一个主要瓶颈，每个核可以独享一部分高速缓存，可以提高性能。JVM是利用操作系统的"轻量级进程"实现线程，所以线程每操作一次共享内存，都无法在高速缓存中命中，是一次开销较大的系统调用。所以区别于普通的优化，针对多核平台，需要进行一些特殊的优化。 代码优化 线程数要大于等于核数 如果使用多线程，只有运行的线程数比核数大，才有可能榨干CPU资源，否则会有若干核闲置。要注意的是，如果线程数目太多，就会占用过多内存，导致性能不升反降。JVM的垃圾回收也是需要线程的，所以这里的线程数包含JVM自己的线程 尽量减少共享数据写操作 每个线程有自己的工作内存，在这个区域内，系统可以毫无顾忌的优化，如果去读共享内存区域，性能也不会下降。但是一旦线程想写共享内存(使用volatile关键字)，就会插入很多内存屏障操作(Memory Barrier或者Memory Fence)指令，保证处理器不乱序执行。相比写本地线程自有的变量，性能下降很多。处理方法是尽量减少共享数据，这样也符合"数据耦合"的设计原则。 使用synchronize关键字 在Java1.5中，synchronize是性能低效的。因为这是一个重量级操作，需要调用操作接口，导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象，性能更高一些。但是到了Java1.6，发生了变化。synchronize在语义上很清晰，可以进行很多优化，有适应自旋，锁消除，锁粗化，轻量级锁，偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示，他们也更支持synchronize，在未来的版本中还有优化余地。 使用乐观策略 传统的同步并发策略是悲观的。表现语义为：多线程操作一个对象的时候，总觉得会有两个线程在同时操作，所以需要锁起来。乐观策略是，假设平时就一个线程访问，当出现了冲突的时候，再重试。这样更高效一些。Java的AtomicInteger就是使用了这个策略。 使用线程本地变量(ThreadLocal) 使用ThreadLocal可以生成线程本地对象的副本，不会和其他线程共享。当该线程终止的时候，其本地变量可以全部回收。 类中Field的排序 可以将一个类会频繁访问到的几个field放在一起，这样他们就有更多的可能性被一起加入高速缓存。同时最好把他们放在头部。基本变量和引用变量不要交错排放。 批量处理数组 现在处理器可以用一条指令来处理一个数组中的多条记录，例如可以同时向一个byte数组中读或者写store记录。所以要尽量使用System.arraycopy()这样的批量接口，而不是自己操作数组。 JVM优化 启用大内存页 现在一个操作系统默认页是4K。如果你的heap是4GB，就意味着要执行1024*1024次分配操作。所以最好能把页调大。这个配额设计操作系统，单改Jvm是不行的。Linux上的配置有点复杂，不详述。 在Java1.6中UseLargePages是默认开启的，LasrgePageSzieInBytes被设置成了4M。笔者看到一些情况下配置成了128MB，在官方的性能测试中更是配置到256MB。 启用压缩指针 Java的64的性能比32慢，原因是因为其指针由32位扩展到64位，虽然寻址空间从4GB扩大到256 TB，但导致性能的下降，并占用了更多的内存。所以对指针进行压缩。压缩后的指针最多支持32GB内存，并且可以获得32位JVM的性能。 在JDK6 update 23默认开启了，之前的版本可以使用-XX:+UseCompressedOops来启动配置。 性能可以看这个评测，性能的提升是很可观。 启用NUMA numa是一个CPU的特性。SMP架构下，CPU的核是对称，但是他们共享一条系统总线。所以CPU多了，总线就会成为瓶颈。在NUMA架构下，若干CPU组成一个组，组之间有点对点的通讯，相互独立。启动它可以提高性能。 NUMA需要硬件，操作系统，JVM同时启用，才能启用。Linux可以用numactl来配置numa,JVM通过-XX:+UseNUMA来启用。 激进优化特性 在Java1.6中，激进优化(AggressiveOpts)是默认开启的。激进优化是一般有一些下一个版本才会发布的优化选项。但是有可能造成不稳定。前段时间以讹传讹的JDK7的Bug，就是开启这个选项后测到的。 逃逸分析 让一个对象在一个方法内创建后，如果他传递出去，就可以称为方法逃逸；如果传递到别的线程，成为线程逃逸。如果能知道一个对象没有逃逸，就可以把它分配在栈而不是堆上，节约GC的时间。同时可以将这个对象拆散，直接使用其成员变量，有利于利用高速缓存。如果一个对象没有线程逃逸，就可以取消其中一切同步操作，很大的提高性能。 但是逃逸分析是很有难度的，因为花了cpu去对一个对象去分析，要是他不逃逸，就无法优化，之前的分析血本无归。所以不能使用复杂的算法，同时现在的JVM也没有实现栈上分配。所以开启之后，性能也可能下降。 可以使用-XX:+DoEscapeAnalysis来开启逃逸分析。 高吞吐量GC配置 对于高吞吐量，在年轻态可以使用Parallel &#8230;<p class="read-more"><a href="http://www.yankay.com/%e5%a4%9a%e6%a0%b8%e5%b9%b3%e5%8f%b0%e4%b8%8b%e7%9a%84java%e4%bc%98%e5%8c%96/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<p>现在多核CPU是主流。利用多核技术，可以有效发挥硬件的能力，提升吞吐量，对于Java程序，可以实现并发垃圾收集。但是Java利用多核技术也带来了一些问题，主要是多线程共享内存引起了。目前内存和CPU之间的带宽是一个主要瓶颈，每个核可以独享一部分高速缓存，可以提高性能。JVM是利用操作系统的"轻量级进程"实现线程，所以线程每操作一次共享内存，都无法在高速缓存中命中，是一次开销较大的系统调用。所以区别于普通的优化，针对多核平台，需要进行一些特殊的优化。</p>
<h3>代码优化</h3>
<p><strong>线程数要大于等于核数</strong></p>
<p>如果使用多线程，只有运行的线程数比核数大，才有可能榨干CPU资源，否则会有若干核闲置。要注意的是，如果线程数目太多，就会占用过多内存，导致性能不升反降。JVM的垃圾回收也是需要线程的，所以这里的线程数包含JVM自己的线程</p>
<p><strong>尽量减少共享数据写操作</strong></p>
<p>每个线程有自己的工作内存，在这个区域内，系统可以毫无顾忌的优化，如果去读共享内存区域，性能也不会下降。但是一旦线程想写共享内存(使用volatile关键字)，就会插入很多内存屏障操作(Memory Barrier或者Memory Fence)指令，保证处理器不乱序执行。相比写本地线程自有的变量，性能下降很多。处理方法是尽量减少共享数据，这样也符合"数据耦合"的设计原则。</p>
<p><strong>使用synchronize关键字</strong></p>
<p>在Java1.5中，synchronize是性能低效的。因为这是一个重量级操作，需要调用操作接口，导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象，性能更高一些。但是到了Java1.6，发生了变化。synchronize在语义上很清晰，可以进行很多优化，有适应自旋，锁消除，锁粗化，轻量级锁，偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示，他们也更支持synchronize，在未来的版本中还有优化余地。</p>
<p><strong>使用乐观策略</strong></p>
<p>传统的同步并发策略是悲观的。表现语义为：多线程操作一个对象的时候，总觉得会有两个线程在同时操作，所以需要锁起来。乐观策略是，假设平时就一个线程访问，当出现了冲突的时候，再重试。这样更高效一些。Java的AtomicInteger就是使用了这个策略。</p>
<p><strong>使用线程本地变量(ThreadLocal)</strong></p>
<p>使用ThreadLocal可以生成线程本地对象的副本，不会和其他线程共享。当该线程终止的时候，其本地变量可以全部回收。</p>
<p><strong>类中Field的排序</strong></p>
<p>可以将一个类会频繁访问到的几个field放在一起，这样他们就有更多的可能性被一起加入高速缓存。同时最好把他们放在头部。基本变量和引用变量不要交错排放。</p>
<p><strong>批量处理数组</strong></p>
<p>现在处理器可以用一条指令来处理一个数组中的多条记录，例如可以同时向一个byte数组中读或者写store记录。所以要尽量使用System.arraycopy()这样的批量接口，而不是自己操作数组。</p>
<h3>JVM优化</h3>
<p><strong>启用大内存页</strong></p>
<p>现在一个操作系统默认页是4K。如果你的heap是4GB，就意味着要执行1024*1024次分配操作。所以最好能把页调大。这个配额设计操作系统，单改Jvm是不行的。Linux上的配置有点复杂，不详述。</p>
<p>在Java1.6中UseLargePages是默认开启的，LasrgePageSzieInBytes被设置成了4M。笔者看到一些情况下配置成了128MB，在官方的性能测试中更是配置到256MB。</p>
<p><strong>启用压缩指针</strong></p>
<p>Java的64的性能比32慢，原因是因为其指针由32位扩展到64位，虽然寻址空间从4GB扩大到256 TB，但导致性能的下降，并占用了更多的内存。所以对指针进行压缩。压缩后的指针最多支持32GB内存，并且可以获得32位JVM的性能。</p>
<p>在JDK6 update 23默认开启了，之前的版本可以使用-XX:+UseCompressedOops来启动配置。</p>
<p>性能可以看这个<a href="http://blog.juma.me.uk/tag/compressed-oops/">评测</a>，性能的提升是很可观。<br />
<a rel="shadowbox" href="http://farm7.static.flickr.com/6039/6314042797_3e4992989f_b.jpg"><br />
<img src="http://farm7.static.flickr.com/6039/6314042797_3e4992989f.jpg" alt="benchmarka" width="500" height="205" /><br />
</a><br />
<strong>启用NUMA</strong></p>
<p>numa是一个CPU的特性。SMP架构下，CPU的核是对称，但是他们共享一条系统总线。所以CPU多了，总线就会成为瓶颈。在NUMA架构下，若干CPU组成一个组，组之间有点对点的通讯，相互独立。启动它可以提高性能。</p>
<p>NUMA需要硬件，操作系统，JVM同时启用，才能启用。Linux可以用<a href="http://linux.die.net/man/8/numactl">numactl</a>来配置numa,JVM通过-XX:+UseNUMA来启用。</p>
<p><strong>激进优化特性</strong></p>
<p>在Java1.6中，激进优化(AggressiveOpts)是默认开启的。激进优化是一般有一些下一个版本才会发布的优化选项。但是有可能造成不稳定。前段时间以讹传讹的<a title="Java 7 Hotspot循环Bug详解" href="http://www.infoq.com/cn/news/2011/08/java7-hotspot">JDK7的Bug</a>，就是开启这个选项后测到的。</p>
<p><strong>逃逸分析</strong></p>
<p>让一个对象在一个方法内创建后，如果他传递出去，就可以称为方法逃逸；如果传递到别的线程，成为线程逃逸。如果能知道一个对象没有逃逸，就可以把它分配在栈而不是堆上，节约GC的时间。同时可以将这个对象拆散，直接使用其成员变量，有利于利用高速缓存。如果一个对象没有线程逃逸，就可以取消其中一切同步操作，很大的提高性能。</p>
<p>但是逃逸分析是很有难度的，因为花了cpu去对一个对象去分析，要是他不逃逸，就无法优化，之前的分析血本无归。所以不能使用复杂的算法，同时现在的JVM也没有实现栈上分配。所以开启之后，性能也可能下降。</p>
<p>可以使用-XX:+DoEscapeAnalysis来开启逃逸分析。</p>
<p><strong>高吞吐量GC配置</strong></p>
<p><strong></strong>对于高吞吐量，在年轻态可以使用Parallel Scavenge,年老态可以使用Parallel Old垃圾收集器。<br />
使用-XX:+UseParallelOldGC开启</p>
<p>可以将-XX:ParallelGCThreads根据CPU的个数进行调整。可以是CPU数的1/2或者5/8</p>
<p><strong>低延迟GC配置</strong></p>
<p><strong></strong>对于低延迟的应用，在年轻态可以使用ParNew,年老态可以使用CMS垃圾收集器。</p>
<p>可以使用-XX:+UseConcMarkSweepGC和-XX:+UseParNewGC打开。</p>
<p>可以将-XX:ParallelGCThreads根据CPU的个数进行调整。可以是CPU数的1/2或者5/8</p>
<p>可以调整-XX:MaxTenuringThreshold(晋升年老代年龄)调高，默认是15.这样可以减少年老代GC的压力</p>
<p>可以-XX:TargetSurvivorRatio，调整Survivor的占用比率。默认50%.调高可以提供Survivor区的利用率</p>
<p>可以调整-XX:SurvivorRatio,调整Eden和Survivor的比重。默认是8。这个比重越小，Survivor越大，对象可以在年轻态呆更多时间。</p>
<p>等等</p>
<p>&nbsp;</p>
<p>参见：《<a href="http://java.sun.com/performance/reference/whitepapers/tuning.html">Java优化白皮书</a>》，《<a href="http://book.douban.com/subject/6522893/">深入理解Java虚拟机</a>》</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/%e5%a4%9a%e6%a0%b8%e5%b9%b3%e5%8f%b0%e4%b8%8b%e7%9a%84java%e4%bc%98%e5%8c%96/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>HBase中文官方文档</title>
		<link>http://www.yankay.com/hbase%e4%b8%ad%e6%96%87%e5%ae%98%e6%96%b9%e6%96%87%e6%a1%a3/</link>
		<comments>http://www.yankay.com/hbase%e4%b8%ad%e6%96%87%e5%ae%98%e6%96%b9%e6%96%87%e6%a1%a3/#comments</comments>
		<pubDate>Tue, 01 Nov 2011 03:02:55 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[算法]]></category>
		<category><![CDATA[Hbase]]></category>
		<category><![CDATA[中文]]></category>
		<category><![CDATA[官方文档]]></category>
		<category><![CDATA[文档]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26193</guid>
		<description><![CDATA[&#160; HBase – Hadoop Database，是一个构建在Apache Hadoop上的列数据。Hbase有很好的扩展性，被认为是BigTable的一个克隆，可以存储数以亿计的行。 在Hbase的官网，我们看到一篇很好的官方文档。我花了很长的时间，把他汉化了。请点击这里:《HBase中文官方文档》 这篇文档非常的详尽，从Hbase的安装，配置，使用，原理，优化，维护都做了非常全面的介绍。还提供了很多扩展阅读，方便我们知道更多Hbase的工作机制。通过翻译这篇文档，我细细读了这篇文档，受益良多。 翻译是一个吃进去再吐出来的过程，我的Hbase的理解也可能会发生一些偏差，所以如果有地方发生错误，请告诉我，大家一起进步。 &#160;]]></description>
			<content:encoded><![CDATA[<p>&nbsp;</p>
<p><a href="http://hbase.apache.org/"><img class="alignright" style="border-style: initial; border-color: initial;" title="hbase" src="http://hbase.apache.org/images/hbase_logo.png" alt="hbase" width="200" height="50" /></a></p>
<p><a href="http://hbase.apache.org/">HBase </a>– Hadoop Database，是一个构建在Apache Hadoop上的列数据。Hbase有很好的扩展性，被认为是BigTable的一个克隆，可以存储数以亿计的行。</p>
<p>在Hbase的官网，我们看到一篇很好的<a href="http://hbase.apache.org/book.html">官方文档</a>。我花了很长的时间，把他汉化了。请点击这里:《<a href="http://www.yankay.com/wp-content/hbase/book.html">HBase中文官方文档</a>》</p>
<p>这篇文档非常的详尽，从Hbase的安装，配置，使用，原理，优化，维护都做了非常全面的介绍。还提供了很多扩展阅读，方便我们知道更多Hbase的工作机制。通过翻译这篇文档，我细细读了这篇文档，受益良多。</p>
<p>翻译是一个吃进去再吐出来的过程，我的Hbase的理解也可能会发生一些偏差，所以如果有地方发生错误，请告诉我，大家一起进步。</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/hbase%e4%b8%ad%e6%96%87%e5%ae%98%e6%96%b9%e6%96%87%e6%a1%a3/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>最小完美哈希函数简介</title>
		<link>http://www.yankay.com/introduction-to-opmphf/</link>
		<comments>http://www.yankay.com/introduction-to-opmphf/#comments</comments>
		<pubDate>Sun, 16 Oct 2011 09:32:17 +0000</pubDate>
		<dc:creator>颜开</dc:creator>
				<category><![CDATA[算法]]></category>
		<category><![CDATA[opmphf]]></category>
		<category><![CDATA[哈希]]></category>
		<category><![CDATA[最小完美哈希函数]]></category>

		<guid isPermaLink="false">http://www.yankay.com/?p=26158</guid>
		<description><![CDATA[什么是保序最小完美哈希函数 我曾经花了很多脑筋来找一个很好很完美的哈希算法，但都没有想到，最近看到了，掩不住一阵激动分享下。最小完美哈希函数是什么，要从定义说起，这个名字很长，一步步解释。 哈希函数 任意函数h(x)都可以说哈希函数，一般来说，一个良好的哈希函数可以尽量避免重复。x的集合是参数域，h(x)的集合是值域。 完美哈希函数  完美哈希函数，就是完全不会冲突的哈希函数，这要求函数的值域至少比参数域要大 最小完美哈希函数 最小完美哈希函数，就是指函数的值域和参数域的大小完全相等，一个也不多 保序最小完美哈希函数 保序的意思就是指这个哈希之后顺序是不变的，同时还能满足其他两个条件。 这个函数的优点就是形式上很完美，就像给一个排好序的文档编上的序号一般紧凑可靠。但是这个函数有两个缺点，一是必须事前必须知道原数据集，二是需要花一定的CPU来生成这个函数。我认为，对于数据仓库类的线下搜索应用，这个算法是有用武之地的。但对于强调实时的数据业务，这个算法是不适合的。 算法实现 该算法的实现方法是这样的。先构造两个普通的哈希函数h1(x)和h2(x),还有一个用数组实现的函数g(x)。使得h(x)=g(h1(x))+g(h2(x))\ mod\ n,其中n是参数的总个数，H(x)就是最终的有序最小完美哈希函数了。 以上是定义，说不清楚，举个例子就明白了。取一个n为12的数据集。 首先构造这三个函数： 函数h1和h2: x h1函数 h2函数 jezebel 5 9 jezer 5 7 ... ... ... 函数g: x g函数 5 0 7 1 9 0 ... &#8230;<p class="read-more"><a href="http://www.yankay.com/introduction-to-opmphf/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<h4>什么是保序最小完美哈希函数</h4>
<p>我曾经花了很多脑筋来找一个很好很完美的哈希算法，但都没有想到，最近看到了，掩不住一阵激动分享下。最小完美哈希函数是什么，要从定义说起，这个名字很长，一步步解释。</p>
<ol>
<li><strong>哈希函数 </strong>任意函数h(x)都可以说哈希函数，一般来说，一个良好的哈希函数可以尽量避免重复。x的集合是参数域，h(x)的集合是值域。</li>
<li><strong>完美哈希函数  </strong>完美哈希函数，就是完全不会冲突的哈希函数，这要求函数的值域至少比参数域要大</li>
<li><strong>最小完美哈希函数 </strong>最小完美哈希函数，就是指函数的值域和参数域的大小完全相等，一个也不多</li>
<li><strong>保序最小完美哈希函数 </strong>保序的意思就是指这个哈希之后顺序是不变的，同时还能满足其他两个条件。</li>
</ol>
<div>这个函数的优点就是形式上很完美，就像给一个排好序的文档编上的序号一般紧凑可靠。但是这个函数有两个缺点，一是必须事前必须知道原数据集，二是需要花一定的CPU来生成这个函数。我认为，对于数据仓库类的线下搜索应用，这个算法是有用武之地的。但对于强调实时的数据业务，这个算法是不适合的。</div>
<div>
<h4>算法实现</h4>
<p>该算法的实现方法是这样的。先构造两个普通的哈希函数h1(x)和h2(x),还有一个用数组实现的函数g(x)。使得<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_9b42c96a9cadc5316431dcba4be449c4.gif' style='vertical-align: middle; border: none; ' class='tex' alt="h(x)=g(h1(x))+g(h2(x))\ mod\ n" /></span><script type='math/tex'>h(x)=g(h1(x))+g(h2(x))\ mod\ n</script>,其中n是参数的总个数，H(x)就是最终的有序最小完美哈希函数了。</p>
<p>以上是定义，说不清楚，举个例子就明白了。取一个n为12的数据集。</p>
<p>首先构造这三个函数：<br />
函数h1和h2:</p>
<table width="100%">
<tbody>
<tr>
<td>x</td>
<td>h1函数</td>
<td>h2函数</td>
</tr>
<tr>
<td>jezebel</td>
<td>5</td>
<td>9</td>
</tr>
<tr>
<td>jezer</td>
<td>5</td>
<td>7</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</tbody>
</table>
<p>函数g:</p>
<table width="100%">
<tbody>
<tr>
<td>x</td>
<td>g函数</td>
</tr>
<tr>
<td>5</td>
<td>0</td>
</tr>
<tr>
<td>7</td>
<td>1</td>
</tr>
<tr>
<td>9</td>
<td>0</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
</tbody>
</table>
</div>
<div>根据上文的公式<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_9b42c96a9cadc5316431dcba4be449c4.gif' style='vertical-align: middle; border: none; ' class='tex' alt="h(x)=g(h1(x))+g(h2(x))\ mod\ n" /></span><script type='math/tex'>h(x)=g(h1(x))+g(h2(x))\ mod\ n</script>，可以得出：</div>
<div>
<table width="100%">
<tbody>
<tr>
<td>x</td>
<td>h计算步骤</td>
<td>h值</td>
</tr>
<tr>
<td>jezebel</td>
<td>g(5)+g(9)</td>
<td>0</td>
</tr>
<tr>
<td>jezer</td>
<td>g(5)+g(7)</td>
<td>1</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</tbody>
</table>
</div>
<p>这里的h就可以最小完美哈希函数算出的值了，很神奇，不是吗？</p>
<p>大致的流程走了一遍，现在最最关键的是h1(x),h2(x)和g(x)是怎么得来的。</p>
<p>h1(x)和h2(x)比较简单，可以使用一个很简便的方法获得：先定义一个权重数组w[i],这个数据是一系列随机的数。<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_55ac0acfb9571acf9b84e334e3261080.gif' style='vertical-align: middle; border: none; ' class='tex' alt="h1=(t[1]*w[1]+t[2]*w[2]+...+t[i]*w[i])\ mod\ m" /></span><script type='math/tex'>h1=(t[1]*w[1]+t[2]*w[2]+...+t[i]*w[i])\ mod\ m</script>。其中t[i]指得的字符串x的第i个字符,m值得的函数的值域。只要更换一个权重数组，就可以重新构造一个新函数。有很多方法可以构造这两个函数。</p>
<p>g(x)的获得就比较复杂。可以是凑出来的。就如同上面的例子，因为<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_8969be6edbc9aea3a822c26c3f8ff94e.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(5)+g(9)\ mod\ 12=0" /></span><script type='math/tex'>g(5)+g(9)\ mod\ 12=0</script>,<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_051bbfd5dd44dc8441a6d2eabb2c74eb.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(5)+g(7)\ mod\ 12=1" /></span><script type='math/tex'>g(5)+g(7)\ mod\ 12=1</script>。所以我们可以凑出<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_f5381713627d5c238d1fa6b9267a829b.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(5)=0,g(7)=1,g(9)=0" /></span><script type='math/tex'>g(5)=0,g(7)=1,g(9)=0</script>这样就可以满足上面的两个条件了。需要一个数组来存储函数g的结果，当然凑也不能瞎凑，是有方法的，下面专门讲凑的步骤。</p>
<div>
<h4>算法函数生成</h4>
<p>首先随意设定一个数，比如是7，设为<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_9d5a6cdd04a416615c443a61c255499f.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(7)=1" /></span><script type='math/tex'>g(7)=1</script>,因为我们已知<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_051bbfd5dd44dc8441a6d2eabb2c74eb.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(5)+g(7)\ mod\ 12=1" /></span><script type='math/tex'>g(5)+g(7)\ mod\ 12=1</script>，所以可以推论出<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_c0a99568946a0c4a3bcc868606c429ab.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(5)=0" /></span><script type='math/tex'>g(5)=0</script>。因为<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_8969be6edbc9aea3a822c26c3f8ff94e.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(5)+g(9)\ mod\ 12=0" /></span><script type='math/tex'>g(5)+g(9)\ mod\ 12=0</script>，所以可以推出<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_25a91f90de6573ba9a835a86970af9b5.gif' style='vertical-align: middle; border: none; ' class='tex' alt="g(9)=0" /></span><script type='math/tex'>g(9)=0</script>，以此类推就可以了。但要注意的是千万不能重复设定一个数两次，这样就会形成一个环，永远也推不完。所以遇到已经推算过的数的时候，要检测环的存在。这样下去，就可以猜出全部的值了。</p>
<p>现在需要的就是分析这个凑的过程的运行效率问题。这个就要涉及到h1,h2这两个函数的值域大小，如果越大，越容易凑出一个g函数，但是g函数的参数域就会比较大，存储这个g函数的数据就需要占用更多的空间。相反如果值域越小，在凑的时候就非常容易出现环，需要更长的时间才能凑出这个g函数。</p>
<p>怎么办呢？</p>
<p>我们可以使用3个的h函数来降低形成环的可能，就是这样<span class='MathJax_Preview'><img src='http://www.yankay.com/wp-content/plugins/latex/cache/tex_a7b24ac19b142e3ef86d30dab3f5492a.gif' style='vertical-align: middle; border: none; ' class='tex' alt="h(x)=g(h1(x))+g(h2(x))+g(h3(x))\ mod\ n" /></span><script type='math/tex'>h(x)=g(h1(x))+g(h2(x))+g(h3(x))\ mod\ n</script>，这样虽然推理g函数的过程会复杂一些，但是很有效，有实验分析表明，当h函数的值域大约参数域的1.23倍的时候，这个g函数的创建尝试次数是常数。</p>
<p>至此，这个算法介绍完了。这个方法是从《Managing Gigabytes》这本书看到的，这里的讲述更浅显一些。</p>
</div>
<div>
<h4>结语</h4>
<p>这个哈希函数是一个静态Hash函数，可以非常有效的缩减索引所需要的空间。《Managing Gigabytes》一书中有一个对比，如果直接使用字符串数组，100万个术语需要28MB的空间，而是要这样的哈希函数，可以缩减到12MB。要知道索引小一点，磁盘就能读得快一点，查询就能快一点。所以这个哈希函数对于提高性能是非常给力的。</p>
<p>但是他是静态的，就意味着事前必须知道需要哈希哪些数据。同时生成的算法比较复杂，需要很长的时间来建立索引。没有办法实时添加更新。给他的应用范围提了个极大的限制。窃以为输入法的词库，数据仓库的查询索引，还有一些不需要更新且对性能有要求的场景，这个算法是适用的。</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.yankay.com/introduction-to-opmphf/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>

