https://joemario.github.io/blog/2016/09/01/c2c-blog/

C2C - Linux Perf里的“伪共享”检测

你是否在NUMA环境中运行你的应用呢,或者你的应用是多线程,又或者是有共享内存的多进程的?满足上面任意一条,那么你的应用就可能因为伪共享而性能下降。

但是要怎样才能知道是不是受伪共享所害呢?我们在提交一些patch能够实现这个需求。这些patch在Linux的著名的perf工具上,添加了一些新特性,我们把它叫做c2c,意思是“高速缓存到高速缓存”(cache-2-cache)。

我们已经在红帽的大量Linux的应用上,测试过这套c2c的开发原型,成功地发现了很多热点的伪共享缓存行。

我自己玩了这个工具好一阵子了,真的很棒。我来分享下它是什么,怎么用。

总览一下,“perf c2c”会为你展示以下内容:

发生伪共享的缓存行
    谁在读写上述的缓存行,以及发生处的缓存行内偏移
    读者和写者分别的pid, tid, 指令地址,函数名,二进制文件
        读者和写者的源代码文件,代码行
	    这些热点缓存行上的,load操作的平均延迟
	        这些缓存行的样本来自哪些numa节点,哪些cpu参与了读写 

		perf c2c和perf里现有的工具比较类似

		先用perf c2c record收集数据,再用perf c2c report生成报告

		介绍输出数据前,先给一个简单的perf c2c的使用帮助

		c2c usage flags

		然后是一个perf c2c的输出的样例

		c2c output file

		然后如果你想自己玩玩perf c2c的话,这里一个有一个小程序,可以产生大量的伪共享

		Fasle sharing .c src file
		[编辑] 我先来过一遍输出文件,强调下其中有趣的域

		输出里的第一个表,概括了load和store的样本。挺有趣的,能够看到load操作都是在哪里取到了数据。

		注意这个术语“HITM”,意思是一次load操作命中了一条修改过的缓存行”(Hit In The Modified)(后面的术语HITM,一般指HITM的计数,译者注)。伪共享发生的关键就在于此。

		而Remote HITM,意思是跨numa节点的HITM,这个是所有load操作里代价最高的情况,尤其在读者和写者非常多的情况下,这个代价会变得非常的高。

		(Local HITM,相对的,则是numa节点内的HITM;LLC Misses to Remote cache (HITM),其实是第26行比上第22行, 在本地LLC miss掉这部分样本里有多少是最后跨numa节点的cache里取到数据的,译者注)

		 1  =================================================
		 2              Trace Event Information
		 3  =================================================
		 4    Total records                     :     329219  << Total loads and stores sampled.
		 5    Locked Load/Store Operations      :      14654
		 6    Load Operations                   :      69679  << Total loads
		 7    Loads - uncacheable               :          0
		 8    Loads - IO                        :          0
		 9    Loads - Miss                      :       3972
		 10    Loads - no mapping                :          0
		 11    Load Fill Buffer Hit              :      11958
		 12    Load L1D hit                      :      17235  << loads that hit in the L1 cache.
		 13    Load L2D hit                      :         21
		 14    Load LLC hit                      :      14219  << loads that hit in the last level cache (LLC).
		 15    Load Local HITM                   :       3402  << loads that hit in a modified cache on the same numa node (local HITM).
		 16    Load Remote HITM                  :      12757  << loads that hit in a modified cache on a remote numa node (remote HITM).
		 17    Load Remote HIT                   :       5295
		 18    Load Local DRAM                   :        976  << loads that hit in the local node's main memory.
		 19    Load Remote DRAM                  :       3246  << loads that hit in a remote node's main memory.
		 20    Load MESI State Exclusive         :       4222 
		 21    Load MESI State Shared            :          0
		 22    Load LLC Misses                   :      22274  << loads not found in any local node caches.
		 23    LLC Misses to Local DRAM          :        4.4% << % hitting in local node's main memory.
		 24    LLC Misses to Remote DRAM         :       14.6% << % hitting in a remote node's main memory.
		 25    LLC Misses to Remote cache (HIT)  :       23.8% << % hitting in a clean cache in a remote node.
								 26    LLC Misses to Remote cache (HITM) :       57.3% << % hitting in remote modified cache. (most expensive - false sharing)
														 27    Store Operations                  :     259539  << store instruction sample count
																			       28    Store - uncacheable               :          0
																								  29    Store - no mapping                :         11
																												     30    Store L1D Hit                     :     256696  << stores that got L1 cache when requested.
																																 31    Store L1D Miss                    :       2832  << stores that couldn't get the L1 cache when requested (L1 miss).
																																			     32    No Page Map Rejects               :       2376
																																						  33    Unable to parse data source       :          1

																																								       第二个表里,每一行是一条缓存行的数据,显示了发生伪共享最热点的一些缓存行。默认按照发生Remote HITM的次数比例排序,改下参数也可以按照发生Local HITM的次数比例排序。

																																								       这个表可以给予一个,缓存行维度的,load、store活动的一个比较好的总体感受。

																																								       我喜欢在这个表里找,Rmt LLC Load HITM(即跨numa节点缓存里取到数据的)比较高的,如果有,就得深挖一下。

																																								       54  =================================================
																																								       55             Shared Data Cache Line Table
																																								       56  =================================================
																																								       57  #
																																								       58  #                              Total      Rmt  ----- LLC Load Hitm -----  ---- Store Reference ----  --- Load Dram ----      LLC    Total  ----- Core Load Hit -----  -- LLC Load Hit --
																																								       59  # Index           Cacheline  records     Hitm    Total      Lcl      Rmt    Total    L1Hit   L1Miss       Lcl       Rmt  Ld Miss    Loads       FB       L1       L2       Llc       Rmt
																																								       60  # .....  ..................  .......  .......  .......  .......  .......  .......  .......  .......  ........  ........  .......  .......  .......  .......  .......  ........  ........
																																								       61  #
																																								       62        0            0x602180   149904   77.09%    12103     2269     9834   109504   109036      468       727      2657    13747    40400     5355    16154        0      2875       529
																																								       63        1            0x602100    12128   22.20%     3951     1119     2832        0        0        0        65       200     3749    12128     5096      108        0      2056       652
																																								       64        2  0xffff883ffb6a7e80      260    0.09%       15        3       12      161      161        0         1         1       15       99       25       50        0         6         1
																																								       65        3  0xffffffff81aec000      157    0.07%        9        0        9        1        0        1         0         7       20      156       50       59        0        27         4
																																								       66        4  0xffffffff81e3f540      179    0.06%        9        1        8      117       97       20         0        10       25       62       11        1        0        24         7

																																								       下一个是Pareto表,显示了每条内部产生竞争的缓存行的一些信息。这是最重要的一个表。为了精简,我只展示了三条缓存行。表格里包含了这些信息:

																																								           71,72行是列名,每列都解释了缓存行的一些活动。
																																									       76行显示了每条缓存行的HITM和store活动情况 - 依次是load和store活动的计数,以及缓存行的虚拟地址。
																																									           然后是数据地址列。上面提到了76行显示了缓存行的虚拟地址,而下面几行的这一列则是行内偏移。
																																										       下一列显示了pid,或线程id(如果设置了要输出tid)。
																																										           接下来是指令地址。
																																											       接下来三列,展示了平均load操作的延迟。我常看着里有没有很高的平均延迟。这个平均延迟,可以反映该行的竞争紧张程度。
																																											           cpu cnt列展示了该行访问的样本采集自多少个cpu。
																																												       然后是函数名,二进制文件名,源代码名,和代码行数。
																																												           最后一列展示了对于每个节点,样本分别来自于哪些cpu 

																																													   67  =================================================
																																													   68        Shared Cache Line Distribution Pareto      
																																													   69  =================================================
																																													   70  #
																																													   71  #        ----- HITM -----  -- Store Refs --        Data address                               ---------- cycles ----------       cpu                                     Shared                                   
																																													   72  #   Num      Rmt      Lcl   L1 Hit  L1 Miss              Offset      Pid        Code address  rmt hitm  lcl hitm      load       cnt               Symbol                Object                  Source:Line  Node{cpu list}
																																													   73  # .....  .......  .......  .......  .......  ..................  .......  ..................  ........  ........  ........  ........  ...................  ....................  ...........................  ....
																																													   74  #
																																													   75    -------------------------------------------------------------
																																													   76        0     9834     2269   109036      468            0x602180
																																													   77    -------------------------------------------------------------
																																													   78            65.51%   55.88%   75.20%    0.00%                 0x0    14604            0x400b4f     27161     26039     26017         9  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:144   0{0-1,4}  1{24-25,120}  2{48,54}  3{169}
																																													   79             0.41%    0.35%    0.00%    0.00%                 0x0    14604            0x400b56     18088     12601     26671         9  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:145   0{0-1,4}  1{24-25,120}  2{48,54}  3{169}
																																													   80             0.00%    0.00%   24.80%  100.00%                 0x0    14604            0x400b61         0         0         0         9  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:145   0{0-1,4}  1{24-25,120}  2{48,54}  3{169}
																																													   81             7.50%    9.92%    0.00%    0.00%                0x20    14604            0x400ba7      2470      1729      1897         2  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:154   1{122}  2{144}
																																													   82            17.61%   20.89%    0.00%    0.00%                0x28    14604            0x400bc1      2294      1575      1649         2  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:158   2{53}  3{170}
																																													   83             8.97%   12.96%    0.00%    0.00%                0x30    14604            0x400bdb      2325      1897      1828         2  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:162   0{96}  3{171}

																																													   84    -------------------------------------------------------------
																																													   85        1     2832     1119        0        0            0x602100
																																													   86    -------------------------------------------------------------
																																													   87            29.13%   36.19%    0.00%    0.00%                0x20    14604            0x400bb3      1964      1230      1788         2  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:155   1{122}  2{144}
																																													   88            43.68%   34.41%    0.00%    0.00%                0x28    14604            0x400bcd      2274      1566      1793         2  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:159   2{53}  3{170}
																																													   89            27.19%   29.40%    0.00%    0.00%                0x30    14604            0x400be7      2045      1247      2011         2  [.] read_write_func  no_false_sharing.exe  false_sharing_example.c:163   0{96}  3{171}

																																													   90    -------------------------------------------------------------
																																													   91        2       12        3      161        0  0xffff883ffb6a7e80
																																													   92    -------------------------------------------------------------
																																													   93            58.33%  100.00%    0.00%    0.00%                 0x0    14604  0xffffffff810cf16d      1380       941      1229         9  [k] task_tick_fair              [kernel.kallsyms]  atomic64_64.h:21   0{0,4,96}  1{25,120,122}  2{53}  3{170-171}
																																													   94            16.67%    0.00%   98.76%    0.00%                 0x0    14604  0xffffffff810c9379      1794         0       625        13  [k] update_cfs_rq_blocked_load  [kernel.kallsyms]  atomic64_64.h:45   0{1,4,96}  1{25,120,122}  2{48,53-54,144}  3{169-171}
																																													   95            16.67%    0.00%    0.00%    0.00%                 0x0    14604  0xffffffff810ce098      1382         0       867        12  [k] update_cfs_shares           [kernel.kallsyms]  atomic64_64.h:21   0{1,4,96}  1{25,120,122}  2{53-54,144}  3{169-171}
																																													   96             8.33%    0.00%    0.00%    0.00%                 0x8    14604  0xffffffff810cf18c      2560         0       679         8  [k] task_tick_fair              [kernel.kallsyms]  atomic.h:26        0{4,96}  1{24-25,120,122}  2{54}  3{170}
																																													   97             0.00%    0.00%    1.24%    0.00%                 0x8    14604  0xffffffff810cf14f         0         0         0         2  [k] task_tick_fair              [kernel.kallsyms]  atomic.h:50        2{48,53}

																																													   [编辑] 我一般怎么用”perf c2c”

																																													   这是我一般习惯使用的命令行:

																																													     perf c2c record -F 60000 -a --all-user sleep 5
																																													      perf c2c record -F 60000 -a --all-user sleep 3     // 采样较短时间
																																													       perf c2c record -F 60000 -a --all-kernel sleep 3   // 只采样内核态样本
																																													        perf c2c record -F 60000 -a -u --ldlat 50 sleep 3  // 或者只采集load延迟大于60个(默认是30个)机器周期的样本

																																														你可以使用带图形界面的tui来看输出,或者只输出到标准输出

																																														perf report -NN -c pid,iaddr                 // 使用tui交互式界面
																																														perf report -NN -c pid,iaddr --stdio         // 或者输出到标准输出
																																														perf report -NN -d lcl -c pid,iaddr --stdio  // 或者按local time排序

																																														默认情况,为了好看,符号名被截断为定长。你可以用 “-full-symbols” 参数来显示完整符号名。

																																														例如:

																																														perf c2c report -NN -c pid,iaddr --full-symbols --stdio

																																														[编辑] 找到这些缓存行的调用者

																																														有的时候,很需要找到缓存行的调用者是谁。下面是获得调用图信息的方法。但我一般不会一上来就用这个,输出太多难以定位伪共享。我一般先找到问题,再回过头来使用调用图。

																																														perf c2c record --call-graph dwarf,8192 -F 60000 -a --all-user sleep 5
																																														perf c2c report -NN -g --call-graph -c pid,iaddr --stdio

																																														[编辑] 增加perf的采用频率真的有用吗?

																																														我有时候会把perf采样频率提升到”-F 60000” 或者 “-F 80000”

																																														并没有强行要求这样做,这样做可以短时间获得较丰富的采样集合。如果你想提升采样频率,可以用下面的方法。(根据dmesg里有没有“perf interrupt took too long …”信息来调整频率)

																																														echo    500 > /proc/sys/kernel/perf_cpu_time_max_percent
																																														echo 100000 > /proc/sys/kernel/perf_event_max_sample_rate
																																														<then do your "perf c2c record" here>
																																														echo     50 > /proc/sys/kernel/perf_cpu_time_max_percent

																																														[编辑] 当perf有过量的样本时怎么办?

																																														在大型系统上(比如有4,8,16个cpu插槽的系统)运行,可能会样本太多,消耗大量的CPU时间,perf.data文件也可能明显变大。 对于这个问题,我有以下建议(包含但不仅限于):

																																														    将ldlat从30增加大50。这使得perf跳过我们没啥兴趣的很快的load
																																														        降低采样频率
																																															    缩短perf record的睡眠时间窗口。比如,从sleep 5改成sleep 3 

																																															    [编辑] 在许多应用上使用c2c让我学到了什么

																																															    一般搭建看见性能工具的输出,都会问这些数据意味着什么。这些是我学到的一些东西,希望对大家能有所帮助。

																																															        我倾向于运行”perf c2c” 3秒、5秒或10秒。运行更久,观测到的可能就不是并发的伪共享,而是时间错开的缓存行访问。
																																																    如果你对内核样本没有兴趣,只想看用户态的样本,可以指定—all-user。反之使用—all-kernel
																																																        cpu很多的系统上(如>148个),设置-ldlat为一个较大的值(50甚至70),perf可能能产生更丰富的C2C样本。
																																																	    读最上面那个具有概括性的Trace Event表,尤其是“LLC Misses to Remote cache HITM”的数字。只要不是接近0,就可能有值得追究的伪共享。
																																																	        看Pareto表时,我们需要关注的,多半只是最热点的两三个缓存行。
																																																		    有的时候,一段代码,它不在某一行cacheline上竞争严重,但是它却在很多cacheline上竞争,这样的代码段也是很值得优化的。同理还有多进程程序访问共享内存时的情况。
																																																		        在Pareto表里,当你看到很长的load操作平均延迟,常常就表明存在严重的伪共享,影响了性能。
																																																			    接下来去看样本采样自哪些节点和cpu,据此进行优化,将哪些内存或task进行numa节点锁存。 

																																																			    最后,Pareto表还能对怎么解决对齐得很不好的缓存行,提供灵感。 例如:

																																																			        定位到:写地很频繁的数据,这些数据会所在缓存行。可以据此进行对齐调整,让他们不那么竞争,运行更快,也能让其它的共享了该缓存行的变量不被拖慢。
																																																				    定位到:没有对齐,被拆成了多个缓存行的锁或者互斥量。
																																																				        定位到:读多写少的变量,可以将他们组合到相同或相邻的缓存行。 

																																																					[编辑] 原始的样本也能有帮助

																																																					有的时候,看一看perf.data文件中原始的指令样本也是有用的(用perf c2c record命令生成)。 你可以用”perf script"命令得到原始样本,man perf-script可以查看这个命令的manpage。输出可能是编码过的,但你可以按load权重排序(第5列),看看那个load样本受伪共享影响最严重,运行了最长时间。
																																																					[编辑] perf的c2c功能在上游的Linux 4.2内核已经可用了
																																																					[编辑] 最后,这是一群人集体的努力的结果

																																																					Don Zickus, Dick Fowles and Joe Mario 一起工作实现了这个功能,除此之外我们还从Arnaldo Carvalho de Melo, Stephane Eranian, Jiri Olsa and Andi Kleen.那里得到了相当多的帮助。 另外,最近Jiri也积极参与了c2c功能集成进perf的工作。 非常感谢你们各位一起推进了这份工作!


																																																					@丁缓