<?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; zhanglei8893</title>
	<atom:link href="http://software.intel.com/zh-cn/blogs/author/zhanglei8893/feed/" rel="self" type="application/rss+xml" />
	<link>http://software.intel.com/zh-cn/blogs</link>
	<description></description>
	<lastBuildDate>Mon, 28 May 2012 14:23:20 +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问题</title>
		<link>http://software.intel.com/zh-cn/blogs/2011/12/16/false-sharing-2/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2011/12/16/false-sharing-2/#comments</comments>
		<pubDate>Fri, 16 Dec 2011 03:59:05 +0000</pubDate>
		<dc:creator>zhanglei8893</dc:creator>
				<category><![CDATA[其他]]></category>
		<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[移动技术]]></category>
		<category><![CDATA[英特尔® 软件网络 2.0]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2011/12/16/false-sharing-2/</guid>
		<description><![CDATA[在多核快速发展的现在，利用多线程技术提高CPU设备的利用率已经成为一种趋势。然而多核计算机体系架构和单核有了很大的变化，在多线程编程中会碰到一些意想不到的问题，比如多核中非常典型的false sharing问题。下文会非常详细的揭示false sharing产生的根源，以及何如避免来提高程序的性能。 先来了解一下典型的多核架构，每个CPU都有自己的Cache，如果一个内存中的变量在多个Cache中都有副本的话，则需要保证变量的Cache一致性：变量的值为最后一次写入的值。Intel多核架构实现Cache一致性是采用的MESI (Modified/Exclusive/Shared/Invalid) 协议。 以上图为例，初始P1和P2都从Memory中加载变量x=1，这时每个CPU的Cache的x变量均处于Shared状态；当P1写入x=3时，P2中的变量成为无效状态，P1的为Modified状态。以后在P2读取变量x的值，由于P2的Cache的变量x是无效的，致使Cache命中失败，同时系统会用P1的值来更新P2的Cache和Memory，。 需要注意的是，CPU Cache的结构是按CacheLine为最小单位进行读写的。在Linux可以用命令行sudo cat /proc/cpuinfo看到Cache信息。 本人的机器信息如下：Cache Size： 3072KB， Cache_alignment:64。 下面的例子就能说明false sharing产生的原因： view plaincopy to clipboardprint? 01.double sum=0.0, sum_local[NUM_THREADS]; 02.#pragma omp parallel num_threads(NUM_THREADS) 03.{ 04.int me = omp_get_thread_num(); 05.sum_local[me] = 0.0; 06.#pragma omp for 07.for (i = 0; i &#60; N; i++) 08.sum_local[me] += x[i] * y[i]; //产生false sharing的代码行 09.#pragma [...]]]></description>
			<content:encoded><![CDATA[<p>在多核快速发展的现在，利用多线程技术提高CPU设备的利用率已经成为一种趋势。然而多核计算机体系架构和单核有了很大的变化，在多线程编程中会碰到一些意想不到的问题，比如多核中非常典型的false sharing问题。下文会非常详细的揭示false sharing产生的根源，以及何如避免来提高程序的性能。<br />
        先来了解一下典型的多核架构，每个CPU都有自己的Cache，如果一个内存中的变量在多个Cache中都有副本的话，则需要保证变量的Cache一致性：变量的值为最后一次写入的值。Intel多核架构实现Cache一致性是采用的MESI (Modified/Exclusive/Shared/Invalid) 协议。<br />
<img src="http://hi.csdn.net/attachment/201111/12/0_1321097443P93x.gif" alt="null" /></p>
<p> 以上图为例，初始P1和P2都从Memory中加载变量x=1，这时每个CPU的Cache的x变量均处于Shared状态；当P1写入x=3时，P2中的变量成为无效状态，P1的为Modified状态。以后在P2读取变量x的值，由于P2的Cache的变量x是无效的，致使Cache命中失败，同时系统会用P1的值来更新P2的Cache和Memory，。<br />
<img src="http://hi.csdn.net/attachment/201111/12/0_1321097371L66t.gif" alt="null" /></p>
<p> 需要注意的是，CPU Cache的结构是按CacheLine为最小单位进行读写的。在Linux可以用命令行sudo cat /proc/cpuinfo看到Cache信息。</p>
<p>本人的机器信息如下：Cache Size： 3072KB， Cache_alignment:64。</p>
<p>    下面的例子就能说明false sharing产生的原因：</p>
<p>view plaincopy to clipboardprint?<br />
01.double sum=0.0, sum_local[NUM_THREADS];<br />
02.#pragma omp parallel num_threads(NUM_THREADS)<br />
03.{<br />
04.int me = omp_get_thread_num();<br />
05.sum_local[me] = 0.0;<br />
06.#pragma omp for<br />
07.for (i = 0; i &lt; N; i++)<br />
08.sum_local[me] += x[i] * y[i];  //产生false sharing的代码行<br />
09.#pragma omp atomic<br />
10.sum += sum_local[me];<br />
11.}<br />
<img src="http://hi.csdn.net/attachment/201111/12/0_1321098328Khl4.gif" alt="null" /><br />
在上图中，CPU0和CPU1的sum_local数组位于同一个Cache Line中。比如某个CPU中的线程更新sum_local[]时，会使其他CPU的Cache的sum_local变成Invalid，这样其他CPU中的线程访问该变量的时候就会进行更新，导致Cache失败，这样会造成额外的内存和Cache之间的同步代价。</p>
<p>       在实际的多线程程序中为了避免这种情况，可以采用如下几种方法：</p>
<p>1， 每个线程使用局部线程数据</p>
<p>2， 每个线程访问的全局数据尽可能分隔开至少超过一个Cache Line。</p>
<p>        那么，false sharing对程序性能的影响到底有多大呢？下面通过一个程序来进行的粗略性能实验。</p>
<p>        下面是一个通过蒙特卡洛法来求取pi的值。</p>
<p>view plaincopy to clipboardprint?<br />
01.#include<br />
02.#include<br />
03.#include<br />
04.#include<br />
05.#define MaxThreadNum 32<br />
06.#define kSamplePoints 100000000<br />
07.#define kSpace 1<br />
08.void *compute_pi(void *);<br />
09.inline double WallTime();<br />
10.<br />
11.int total_hits, hits[MaxThreadNum][kSpace];<br />
12.int sample_points_per_thread, num_threads;<br />
13.<br />
14.<br />
15.int main(void)<br />
16.{<br />
17.   int i;<br />
18.   double time_start, time_end;<br />
19.<br />
20.   pthread_t  p_threads[MaxThreadNum];<br />
21.   pthread_attr_t attr;<br />
22.<br />
23.   pthread_attr_init(&amp;attr);<br />
24.   pthread_attr_setscope(&amp;attr, PTHREAD_SCOPE_SYSTEM);<br />
25.   printf("Enter num_threads\n");<br />
26.   scanf("%d", &amp;num_threads);<br />
27.<br />
28.   time_start = WallTime();<br />
29.<br />
30.   total_hits = 0;<br />
31.   sample_points_per_thread = kSamplePoints / num_threads;<br />
32.<br />
33.   for(i=0; i&lt;num_threads; i++)<br />
34.   {<br />
35.     hits[i][0] = i;<br />
36.     pthread_create(&amp;p_threads[i], &amp;attr, compute_pi, (void *)&amp;hits[i]);<br />
37.   }<br />
38.<br />
39.   for(i=0; i&lt;num_threads; i++)<br />
40.   {<br />
41.     pthread_join(p_threads[i], NULL);<br />
42.     total_hits += hits[i][0];<br />
43.   }<br />
44.<br />
45.   double pi = 4.0 * (double)total_hits / kSamplePoints;<br />
46.   time_end = WallTime();<br />
47.   printf(&quot;Elasped time: %lf, Pi: %lf\n&quot;, time_end - time_start, pi);<br />
48.<br />
49.   return 0;<br />
50.<br />
51.}<br />
52.<br />
53.void *compute_pi(void * s)<br />
54.{<br />
55.    unsigned int seed;<br />
56.    int i;<br />
57.    int *hit_pointer;<br />
58.    double rand_no_x, rand_no_y;<br />
59.    int local_hits;<br />
60.<br />
61.    hit_pointer = (int *)s;<br />
62.    seed = *hit_pointer;<br />
63.    local_hits = 0;<br />
64.<br />
65.    for(i=0; i&lt;sample_points_per_thread; i++)<br />
66.    {<br />
67.       rand_no_x = (double)(rand_r(&amp;seed))/(double)(RAND_MAX);<br />
68.       rand_no_y = (double)(rand_r(&amp;seed))/(double)(RAND_MAX);<br />
69.       if((rand_no_x - 0.5)*(rand_no_x - 0.5) + (rand_no_y - 0.5) * (rand_no_y - 0.5) &lt; 0.25)<br />
70.          (*hit_pointer)++;<br />
71.       // ++local_hits;<br />
72.       seed *= i;<br />
73.     }<br />
74.     //*hit_pointer = local_hits;<br />
75.     pthread_exit(0);<br />
76.}<br />
77.<br />
78.inline double WallTime()<br />
79.{<br />
80.    struct timeval tv;<br />
81.    struct timezone tz;<br />
82.<br />
83.    gettimeofday(&amp;tv, &amp;tz);<br />
84.<br />
85.    double currTime =  (double)tv.tv_sec + (double)tv.tv_usec/1000000.0;<br />
86.<br />
87.    return currTime;<br />
88.}<br />
#include<br />
#include<br />
#include<br />
#include<br />
#define MaxThreadNum 32<br />
#define kSamplePoints 100000000<br />
#define kSpace 1<br />
void *compute_pi(void *);<br />
inline double WallTime();</p>
<p>int total_hits, hits[MaxThreadNum][kSpace];<br />
int sample_points_per_thread, num_threads;</p>
<p>int main(void)<br />
{<br />
   int i;<br />
   double time_start, time_end;</p>
<p>   pthread_t  p_threads[MaxThreadNum];<br />
   pthread_attr_t attr;</p>
<p>   pthread_attr_init(&amp;attr);<br />
   pthread_attr_setscope(&amp;attr, PTHREAD_SCOPE_SYSTEM);<br />
   printf("Enter num_threads\n");<br />
   scanf("%d", &amp;num_threads);</p>
<p>   time_start = WallTime();</p>
<p>   total_hits = 0;<br />
   sample_points_per_thread = kSamplePoints / num_threads;</p>
<p>   for(i=0; i&lt;num_threads; i++)<br />
   {<br />
     hits[i][0] = i;<br />
     pthread_create(&amp;p_threads[i], &amp;attr, compute_pi, (void *)&amp;hits[i]);<br />
   }</p>
<p>   for(i=0; i&lt;num_threads; i++)<br />
   {<br />
     pthread_join(p_threads[i], NULL);<br />
     total_hits += hits[i][0];<br />
   }</p>
<p>   double pi = 4.0 * (double)total_hits / kSamplePoints;<br />
   time_end = WallTime();<br />
   printf(&quot;Elasped time: %lf, Pi: %lf\n&quot;, time_end - time_start, pi);</p>
<p>   return 0;   </p>
<p>}</p>
<p>void *compute_pi(void * s)<br />
{<br />
    unsigned int seed;<br />
    int i;<br />
    int *hit_pointer;<br />
    double rand_no_x, rand_no_y;<br />
    int local_hits;</p>
<p>    hit_pointer = (int *)s;<br />
    seed = *hit_pointer;<br />
    local_hits = 0;</p>
<p>    for(i=0; i&lt;sample_points_per_thread; i++)<br />
    {<br />
       rand_no_x = (double)(rand_r(&amp;seed))/(double)(RAND_MAX);<br />
       rand_no_y = (double)(rand_r(&amp;seed))/(double)(RAND_MAX);<br />
       if((rand_no_x - 0.5)*(rand_no_x - 0.5) + (rand_no_y - 0.5) * (rand_no_y - 0.5) &lt; 0.25)<br />
          (*hit_pointer)++;<br />
       // ++local_hits;<br />
       seed *= i;<br />
     }<br />
     //*hit_pointer = local_hits;<br />
     pthread_exit(0);<br />
}</p>
<p>inline double WallTime()<br />
{<br />
    struct timeval tv;<br />
    struct timezone tz;</p>
<p>    gettimeofday(&amp;tv, &amp;tz);</p>
<p>    double currTime =  (double)tv.tv_sec + (double)tv.tv_usec/1000000.0;</p>
<p>    return currTime;<br />
}</p>
<p>        其中注释的代码是一种采用线程局部数据的方法，记为pi_local，改变kSpace可以改变不同的线程访问的全局数据之间的间隔，其中改变kSpace的值使不同线程访问的数据之间的间隔为4bytes， 32bytes，64bytes，并分别记为pi_4, p1_32, pi_64，测试得到的结果如下：</p>
<p><img src="http://hi.csdn.net/attachment/201111/12/0_13210971951i48.gif" alt="null" /><br />
 因为CPU的数量为2，最大的并行效率为2，以pi_local为标准(因为这种情况完全不存在false sharing问题)，可以看到pi_4的false sharing最为严重，因为16个线程访问的hits数组均位于一个Cache Line，因此会导致严重的Cache Invalid现象。而pi_64就采用另外的空间间隔策略完全避免了这一问题，效果也还不错。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2011/12/16/false-sharing-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

