<?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>中文 &#187; softarts11</title>
	<atom:link href="http://software.intel.com/zh-cn/blogs/author/softarts11/feed/" rel="self" type="application/rss+xml" />
	<link>http://software.intel.com/zh-cn/blogs</link>
	<description></description>
	<lastBuildDate>Sat, 26 May 2012 06:34:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.3</generator>
		<item>
		<title>多核程序探秘 (续): false sharing 及使用 vtune 验证</title>
		<link>http://software.intel.com/zh-cn/blogs/2009/08/13/false-sharing-vtune/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2009/08/13/false-sharing-vtune/#comments</comments>
		<pubDate>Thu, 13 Aug 2009 09:14:25 +0000</pubDate>
		<dc:creator>softarts11</dc:creator>
				<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[false sharing]]></category>
		<category><![CDATA[多核]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2009/08/13/false-sharing-vtune/</guid>
		<description><![CDATA[感谢网友对前一篇文章 －“多核程序探秘- false sharing及使用vtune验证”的支持。本篇文章在原先文章的基础上添加了图片，欢迎网友继续评论并提出宝贵意见。 多核开发中常见的一个问题是false sharing(失效共享)，这个问题让我们用一个全新的角度来看待多核程序的编写，这个角度就是硬件的角度。 Intel Core 2 Duo处理器平台上, L2 cache是由两个core共享的，而L1 data cache是分开的，由两个core分别存取。cache line的大小是64 Bytes。当不同的线程同时读写不同的，看起来更不相关的2个变量时，由于这2个变量实际保存在同一条cache line上，从而会暗地里造成cache line的访问冲突而导致潜在的性能损失。例如这段代码： unsigned char VectorA[10]; unsigned char VectorB[10]; UINT MyThreadProcA( LPVOID pParam ) { unsigned long long myCounter = 100000000; while(--myCounter) { for (int i=0; i&#60;10; ++i) { ++VectorA[i]; } } return 0; // thread completed successfully } UINT [...]]]></description>
			<content:encoded><![CDATA[<p>感谢网友对前一篇文章 －“多核程序探秘- false sharing及使用vtune验证”的支持。本篇文章在原先文章的基础上添加了图片，欢迎网友继续评论并提出宝贵意见。</p>
<p>多核开发中常见的一个问题是false sharing(失效共享)，这个问题让我们用一个全新的角度来看待多核程序的编写，这个角度就是硬件的角度。</p>
<p>Intel Core 2 Duo处理器平台上, L2 cache是由两个core共享的，而L1 data cache是分开的，由两个core分别存取。cache line的大小是64 Bytes。当不同的线程同时读写不同的，看起来更不相关的2个变量时，由于这2个变量实际保存在同一条cache line上，从而会暗地里造成cache line的访问冲突而导致潜在的性能损失。例如这段代码：</p>
<p>unsigned char VectorA[10];<br />
unsigned char VectorB[10];</p>
<p>UINT MyThreadProcA( LPVOID pParam )<br />
{<br />
unsigned long long myCounter = 100000000;<br />
while(--myCounter)<br />
{<br />
for (int i=0; i&lt;10; ++i)<br />
{<br />
++VectorA[i];<br />
}<br />
}<br />
return 0; // thread completed successfully<br />
}</p>
<p>UINT MyThreadProcB( LPVOID pParam )<br />
{<br />
unsigned long long myCounter = 100000000;<br />
while(--myCounter)<br />
{<br />
for (int i=0; i&lt;10; ++i)<br />
{<br />
++VectorB[i];<br />
}<br />
}<br />
return 0; // thread completed successfully<br />
}</p>
<p>尽管MyThreadProc[A/B] 是两个不同的线程，访问的也是两个不同的变量，但是,false sharing却真真实实的发生了。当MyThreadProcA更新VectorA[i]的时候，对应的Core A上的cache line同时被更新，变为modified状态，而这个cache line中又保存了VectorB[i]的一份copy，因此，另一个Core B中的cacheline就会变为失效状态(invalid)，CPU会不得不通过cache protocol(cache的同步协议)去通知Core B上的cache line同时更新VectorB的数据，这样，尽管MyThreadProcA没有修改VectorB，却会导致MyThreadProcB线程访问VectorB时cache miss增加！而我们知道，cache的访问速度是普通内存的10倍，cache miss增大自然会造成明显的性能下降！</p>
<p>在Core2平台上，可以使用EXT_SNOOP.ALL_AGENTS.HITM 事件来评测false sharing的影响，它监测的是总线(内存总线)传输的情况，如果HITM事件发生，则表明总线上响应端的cache正处于修改状态，这恰恰反映了false sharing问题的根源。</p>
<p>vtune的手册对于EXT_SNOOP.ALL_AGENTS.HITM 的解释的:</p>
<p>This event counts the snoop responses to bus transactions. Responses can be counted separately by type and by bus agent. With the 'THIS_AGENT' mask the event counts snoop responses from this processor to bus transactions sent by this processor. With the 'ALL_AGENTS' mask the event counts all snoop responses seen on the bus.</p>
<p>先看看看看上面这段代码的测量结果吧！</p>
<p><img src="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2009/08/false_sharing_11.jpg" alt="" /></p>
<p>采用sampling测量，EXT_SNOOP.ALL_AGENTS.HITM 发生次数1175次,CPU_CLK 为6373,INST_RETIRED为3796</p>
<p>false sharing的解决也很简单，只要把共享的数据放到不同的cache line中即可，例如，将代码改为：</p>
<p>unsigned char VectorA[100];<br />
unsigned char VectorB[100];</p>
<p>这样，使用的仍然是VectorA[0~9]和VectorB[0~9]，VectorA[10~99]则充当了一个pad占位符，把同一条cache line(64bytes)占满。</p>
<p>解决false sharing问题后的测量数据为：</p>
<p><img src="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2009/08/2false_sharing22.jpg" alt="" /></p>
<p>EXT_SNOOP.ALL_AGENTS.HITM 显著降到179次，CPU_CLK 降为1847，由于指令个数没有太大变化，INST_RETIRED为3370。通过程序中内嵌计时函数的方法也能得到接近的结果。</p>
<p>总结，解决false sharing问题的方法：</p>
<p>1. 增大数组元素的间隔使得由不同线程存取的元素位于不同的cache line上<br />
2. 在每个线程中创建全局数组各个元素的本地拷贝，然后结束后再写回全局数组</p>
<p>false sharing是多核程序开发的常见问题，需要引起程序员们的重视。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2009/08/13/false-sharing-vtune/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TBB: concurrent_queue 高性能的奥秘</title>
		<link>http://software.intel.com/zh-cn/blogs/2009/08/13/tbb-concurrent_queue/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2009/08/13/tbb-concurrent_queue/#comments</comments>
		<pubDate>Thu, 13 Aug 2009 03:49:00 +0000</pubDate>
		<dc:creator>softarts11</dc:creator>
				<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[TBB]]></category>
		<category><![CDATA[高性能]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2009/08/13/tbb-concurrent_queue/</guid>
		<description><![CDATA[在如今的多线程开发的滚滚浪潮中，线程安全会是一个充满正面色彩的广告语，还是一个隐含性能低下令人不安的信息？众所周知，STL库所提供的容器均不能保证线程安全，所有的工作都要需要开发者来承担。最简单的实现线程安全的手段便是使用锁来同步对容器的访问，只需要lock和unlock两行语句，容器就变成线程安全了，很简单，不是吗？不过，这时候"线程安全"就成了性能低下的同名词，期望的并发操作成了对容器的串行访问，我们不仅仅需要安全，还需要高性能。 TBB::concurrent_queue令人惊异地同时做到了这两点，在让开发者放心地得到线程安全的同时，还可以心安理得的享受高性能的并发访问。这其中会有什么奥秘？作为普通的开发者，我们可以从中学到什么东西？ Herb Sutter在DDJ 一文中抛出并行编程的三个简单论点，一是分离任务，使用更细粒度的锁或者无锁编程；二是尽量通过并行任务使用CPU资源，以提高系统吞吐量及扩展性；三是保证对共享资源访问的一致性。 传统的STL:queue 加锁的方案，Intel Thread Profilec测量，图的上半部分 fully utilized 只有9%（绿色部分），下半部分有大段时间处于串行执行状态（黄色），并行运算度很低 这三个论点各有侧重，我们看看concurrent_queue是怎么做的。 所谓分解任务，以尽可能细的粒度执行，这样就可以让每个任务运行而不互相干扰。并行编程中有一个对应的重要概念，就是尽量使用thread的private/local数据，例如ptmalloc中采用了好几个private heap，以降低多个线程同时请求分配内存，造成访问global heap时的contend。 针对一个concurrent_queue，内部预先分配了8个micro_queue，并保存在名为array数组中，然后通过concurrent_queue_rep::choose函数来选择其中一个队列，这样做的好处是能把Multi Thread对1个queue的访问平均分布到多个内部的micro_queue上，从而尽可能避免冲突。 micro_queue&#38; choose( ticket k ) { // The formula here approximates LRU in a cache-oblivious way. return array[index(k)]; } static size_t index( ticket k ) { return k*phi%n_queue;//phi=3,n_queue=8 } 具体选择哪个micro_queue，取决于一个为ticket类型的变量k,这个变量实质上是push操作次数的计数。 例如 k=0, 那么选择 array[0*3%8]=array[0] k=1,array[1*3%8]=array[3] ... k=7,array[5] [...]]]></description>
			<content:encoded><![CDATA[<p>在如今的多线程开发的滚滚浪潮中，线程安全会是一个充满正面色彩的广告语，还是一个隐含性能低下令人不安的信息？众所周知，STL库所提供的容器均不能保证线程安全，所有的工作都要需要开发者来承担。最简单的实现线程安全的手段便是使用锁来同步对容器的访问，只需要lock和unlock两行语句，容器就变成线程安全了，很简单，不是吗？不过，这时候"线程安全"就成了性能低下的同名词，期望的并发操作成了对容器的串行访问，我们不仅仅需要安全，还需要高性能。</p>
<p>TBB::concurrent_queue令人惊异地同时做到了这两点，在让开发者放心地得到线程安全的同时，还可以心安理得的享受高性能的并发访问。这其中会有什么奥秘？作为普通的开发者，我们可以从中学到什么东西？</p>
<p>Herb Sutter在DDJ 一文中抛出并行编程的三个简单论点，一是分离任务，使用更细粒度的锁或者无锁编程；二是尽量通过并行任务使用CPU资源，以提高系统吞吐量及扩展性；三是保证对共享资源访问的一致性。</p>
<p>传统的STL:queue 加锁的方案，Intel Thread Profilec测量，图的上半部分 fully utilized 只有9%（绿色部分），下半部分有大段时间处于串行执行状态（黄色），并行运算度很低</p>
<p>这三个论点各有侧重，我们看看concurrent_queue是怎么做的。</p>
<p>所谓分解任务，以尽可能细的粒度执行，这样就可以让每个任务运行而不互相干扰。并行编程中有一个对应的重要概念，就是尽量使用thread的private/local数据，例如ptmalloc中采用了好几个private heap，以降低多个线程同时请求分配内存，造成访问global heap时的contend。</p>
<p>针对一个concurrent_queue，内部预先分配了8个micro_queue，并保存在名为array数组中，然后通过concurrent_queue_rep::choose函数来选择其中一个队列，这样做的好处是能把Multi Thread对1个queue的访问平均分布到多个内部的micro_queue上，从而尽可能避免冲突。</p>
<p>micro_queue&amp; choose( ticket k ) {</p>
<p>// The formula here approximates LRU in a cache-oblivious way.</p>
<p>return array[index(k)];</p>
<p>}</p>
<p>static size_t index( ticket k ) {</p>
<p>return k*phi%n_queue;//phi=3,n_queue=8</p>
<p>}</p>
<p>具体选择哪个micro_queue，取决于一个为ticket类型的变量k,这个变量实质上是push操作次数的计数。</p>
<p>例如</p>
<p>k=0, 那么选择 array[0*3%8]=array[0]</p>
<p>k=1,array[1*3%8]=array[3]</p>
<p>...</p>
<p>k=7,array[5]</p>
<p>k=8,array[0] //又回到了与k=0时一样的结果。</p>
<p>无锁的push操作</p>
<p>即使能把queue分布到内部多个micro_queue上，那还是不可避免的会有冲突存在。例如，有可能2个不同的thread都是在访问同一个micro_queue。由上面结果可以看出，假设thread A在进行push操作时，k=0，那么使用的是array[0]；而thread B同时在进行第8个push操作，k=8，使用的也是array[0]，这种情况下怎么办？</p>
<p>concurrent_queue用这段代码来解决共享资源的访问冲突：</p>
<p>void micro_queue::push( const void* item, ticket k, concurrent_queue_base&amp; base ) {</p>
<p>...</p>
<p>push_finalizer finalizer( *this, k+concurrent_queue_rep::n_queue );</p>
<p>if( tail_counter!=k ) {</p>
<p>ExponentialBackoff backoff;</p>
<p>do {</p>
<p>backoff.pause();</p>
<p>// no memory. throws an exception</p>
<p>if( tail_counter&amp;0x1 ) throw bad_last_alloc();</p>
<p>} while( tail_counter!=k ) ;</p>
<p>}</p>
<p>...</p>
<p>}</p>
<p>对于每一次push操作，micro_queue使用了一个tail_counter来作为阻塞标记。例如，array[0]被thread A占用时，该标记会阻止thread B更新array[0] (此时tail_counter!=k)，程序会在do{}while循环里反复测试等式是否成立，不然去执行backoff.pause()以进入睡眠状态。</p>
<p>MorganStanley的Petru marginean 在DDJ上的文章上曾经提出过几种阻塞的同步方法，分别是NO_WAIT(循环查询),SLEEP(睡眠),TIMED_WAIT(超时等待)以及LOCK_NO_WAIT(锁)，TBB使用的这种类似于TIMED_WAIT的方法，通过pause指令让线程暂停一会后，再重试。从网上得到的资料，pause指令在这种spin_lock模式的阻塞中，可以提高25倍效率。</p>
<p>保证共享资源访问的一致性，及使用new placement 改善内存分配的性能</p>
<p>通过以上两点，TBB::concurrent_queue已经基本解决并行处理数据的问题，然而还有些细节可以提高性能。有关内存的操作永远都是性能测试中的热点(hotspot)，改善性能也可以从内存管理上入手</p>
<p>例如，每次push操作都需要为插入的元素分配内存，malloc是一个昂贵的操作，如果能够预先分配好一大段连续内存，或者是从内存池取得一段内存呢？</p>
<p>TBB中，首先分配一个可以容纳多个对象的page，然后在具体发生push操作时，使用C++的new placement语法，再从已分配的page上取得一个对象大小的内存。</p>
<p>/*overide*/ virtual page *allocate_page() {</p>
<p>size_t n = sizeof(page) + items_per_page*item_size;</p>
<p>page *p = reinterpret_cast(my_allocator.allocate( n ));</p>
<p>if( !p ) internal_throw_exception();</p>
<p>return p;</p>
<p>}</p>
<p>此外，concurrent_queue利用了TBB库的成果:atomic来声明一些原子变量，如tail_counter.</p>
<p>结论：</p>
<p>测试表明，并行度达到50%（图上半部分的蓝色柱），时间上也比STL:queue加锁的版本快了2-3倍。</p>
<p>concurrent_queue是一个典型的高性能并行容器实现，相信concurrent_haspmap和其它的容器，内存池及算法，都可以采用类似的方法去提高性能。只不过，仍然有些对concurrent_queue不解的地方：</p>
<p>为什么不采用Lock-Free的算法直接实现一个micro_queue?而是仍然使用一种类似spin_lock的做法去同步线程？</p>
<p>不过，目前的方案很有可能是Intel开发人员的折中选择，队列负载均衡，再加上预分配内存后，push入队操作应该很快，发生碰撞的机率并不大。毕竟Lock-Free还不够足够成熟。</p>
<p>关于作者:曾任职于Alcatel-Lucent,Nokia,从事高速数据处理，移动网络系统软件开发等。对Linux Kernel，多核编程饶有兴趣。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2009/08/13/tbb-concurrent_queue/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>多核程序探秘- false sharing及使用vtune验证</title>
		<link>http://software.intel.com/zh-cn/blogs/2009/06/10/false-sharingvtune/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2009/06/10/false-sharingvtune/#comments</comments>
		<pubDate>Wed, 10 Jun 2009 03:58:40 +0000</pubDate>
		<dc:creator>softarts11</dc:creator>
				<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[false sharing]]></category>
		<category><![CDATA[vtune]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2009/06/10/false-sharingvtune/</guid>
		<description><![CDATA[多核开发中常见的一个问题是false sharing(失效共享)，这个问题让我们用一个全新的角度来看待多核程序的编写，这个角度就是硬件的角度。]]></description>
			<content:encoded><![CDATA[<p>多核程序探秘(1) false sharing及使用vtune验证</p>
<p>多核开发中常见的一个问题是false sharing(失效共享)，这个问题让我们用一个全新的角度来看待多核程序的编写，这个角度就是硬件的角度。</p>
<p>Intel Core 2 Duo处理器平台上, L2 cache是由两个core共享的，而L1 data cache是分开的，由两个core分别存取。cache line的大小是64 Bytes。当不同的线程同时读写不同的，看起来更不相关的2个变量时，由于这2个变量实际保存在同一条cache line上，从而会暗地里造成cache line的访问冲突而导致潜在的性能损失。例如这段代码：</p>
<p>unsigned char VectorA[10];<br />
unsigned char VectorB[10];</p>
<p>UINT MyThreadProcA( LPVOID pParam )<br />
{<br />
unsigned long long myCounter = 100000000;<br />
while(--myCounter)<br />
{<br />
for (int i=0; i&lt;10; ++i)<br />
{<br />
++VectorA[i];<br />
}<br />
}<br />
return 0; // thread completed successfully<br />
}</p>
<p>UINT MyThreadProcB( LPVOID pParam )<br />
{<br />
unsigned long long myCounter = 100000000;<br />
while(--myCounter)<br />
{<br />
for (int i=0; i&lt;10; ++i)<br />
{<br />
++VectorB[i];<br />
}<br />
}<br />
return 0; // thread completed successfully<br />
}</p>
<p>尽管MyThreadProc[A/B] 是两个不同的线程，访问的也是两个不同的变量，但是,false sharing却真真实实的发生了。当MyThreadProcA更新VectorA[i]的时候，对应的Core A上的cache line同时被更新，变为modified状态，而这个cache line中又保存了VectorB[i]的一份copy，因此，另一个Core B中的cacheline就会变为失效状态(invalid)，CPU会不得不通过cache protocol(cache的同步协议)去通知Core B上的cache line同时更新VectorB的数据，这样，尽管MyThreadProcA没有修改VectorB，却会导致MyThreadProcB线程访问VectorB时cache miss增加！而我们知道，cache的访问速度是普通内存的10倍，cache miss增大自然会造成明显的性能下降！</p>
<p>在Core2平台上，可以使用EXT_SNOOP.ALL_AGENTS.HITM 事件来评测false sharing的影响，它监测的是总线(内存总线)传输的情况，如果HITM事件发生，则表明总线上响应端的cache正处于修改状态，这恰恰反映了false sharing问题的根源。</p>
<p>vtune的手册对于EXT_SNOOP.ALL_AGENTS.HITM 的解释的:</p>
<p>This event counts the snoop responses to bus transactions. Responses can be counted separately by type and by bus agent. With the 'THIS_AGENT' mask the event counts snoop responses from this processor to bus transactions sent by this processor. With the 'ALL_AGENTS' mask the event counts all snoop responses seen on the bus.</p>
<p>先看看看看上面这段代码的测量结果吧！</p>
<p>采用sampling测量，EXT_SNOOP.ALL_AGENTS.HITM 发生次数1175次,CPU_CLK 为6373,INST_RETIRED为3796</p>
<p>false sharing的解决也很简单，只要把共享的数据放到不同的cache line中即可，例如，将代码改为：</p>
<p>unsigned char VectorA[100];<br />
unsigned char VectorB[100];</p>
<p>这样，使用的仍然是VectorA[0~9]和VectorB[0~9]，VectorA[10~99]则充当了一个pad占位符，把同一条cache line(64bytes)占满。</p>
<p>解决false sharing问题后的测量数据为：</p>
<p>EXT_SNOOP.ALL_AGENTS.HITM 显著降到179次，CPU_CLK 降为1847，由于指令个数没有太大变化，INST_RETIRED为3370。通过程序中内嵌计时函数的方法也能得到接近的结果。</p>
<p>总结，解决false sharing问题的方法：</p>
<p>1. 增大数组元素的间隔使得由不同线程存取的元素位于不同的cache line上<br />
2. 在每个线程中创建全局数组各个元素的本地拷贝，然后结束后再写回全局数组</p>
<p>false sharing是多核程序开发的常见问题，需要引起程序员们的重视。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2009/06/10/false-sharingvtune/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

