<?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.litrin.net/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.litrin.net</link>
	<description>It&#039;s Cool to OpenSource</description>
	<lastBuildDate>Fri, 28 Aug 2020 00:18:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.1</generator>
	<item>
		<title>记一次CPU的“升级”评估</title>
		<link>http://www.litrin.net/2020/08/28/cpu/</link>
					<comments>http://www.litrin.net/2020/08/28/cpu/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Fri, 28 Aug 2020 00:16:40 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3282</guid>

					<description><![CDATA[收到一家主力卖东西的公司质询，大致意思是：他们正在大量部署的某种型号的服务器平台，由于CPU、主板、内存三大件 &#8230; <p class="link-more"><a href="http://www.litrin.net/2020/08/28/cpu/" class="more-link">继续阅读<span class="screen-reader-text">“记一次CPU的“升级”评估”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>收到一家主力卖东西的公司质询，大致意思是：他们正在大量部署的某种型号的服务器平台，由于CPU、主板、内存三大件已经停产，所有决定换代升级为最新的平台。后经推荐选择了同等架构，同等核心数的另一款CPU。但在进行iperf 网络压力测试的时候，发觉新的平台极限网络压力无法突破16GB/s（猜测这个“大批量的”平台是用来做转发主机或者存储可能性较大），对比旧版本的平台为22GB/s，1/3 的性能下降让他们开始怀疑人生了……</p>



<span id="more-3282"></span>



<p>拿到了两块CPU的配置，直接笑喷了。这根本就不是“升级”，简直就是“成本优化”，卖个关子，最后再对两个CPU的纸面数据做对比。出于工作的必要，于是让对方工程师在进行iperf压测的时候通过几个监控工具采样用以对比两个平台的不同以及性能差异的由来。</p>



<p>在众多监控工具的数据上，我采用了pqos的数据——简单明了。这个工具在我之前的帖子中也多次被提及，其实本身它只是一个Intel RDT的配置工具，在目前的几个主流Linux发行版中已经被收入，可以通过系统自带的软件管理工具简单的安装：</p>



<pre class="wp-block-code"><code>~# dnf install intel-cmt-cat  # for centos/fedora
~# apt install intel-cmt-cat  # for debian/ubuntu</code></pre>



<p>使用的方法，也是简单，可以直接输出为csv文件，方便通过类似excel之类的工具直接分析。</p>



<pre class="wp-block-code"><code>~# pqos -r -o &lt;filename.csv> -u csv</code></pre>



<figure class="wp-block-image"><img src="https://pic4.zhimg.com/80/v2-ce0e94fb8585bd2f16c1cfff8a7504d1_720w.jpg" alt=""/></figure>



<p>首先展示的是IPC，图中记录了A B两套主机上每个HT的平均IPC，A为旧机器。这个指标是指系统平均每个时钟周期的指令数——字面上自然是越大越好。另外有个容易混淆的CPI两者互为倒数关系，自然CPI越小越好。（感谢@Sirius的提醒，自己都搞晕了 ）<br>毕竟是同一个架构下的产物，从数据上看两颗CPU并没有表现出较大的差距，一个0.76，一个0.77。那是不是就说明两台主机的性能应该表现一致了呢？这就是一个典型的误区！重复，IPC中C的含义是cycle，即时钟周期数，iperf这类的测试结果最终的结果最终会计算为GB每秒，那频率越高，每秒的时钟周期数就会越多，同等的ipc意味着跑分将直接取决于CPU的频率。</p>



<figure class="wp-block-image"><img src="https://pic1.zhimg.com/80/v2-eec6451cc625c0a83797fbff00930746_720w.jpg" alt=""/></figure>



<p>接着是L3 缓存的misses/s，即每秒L3缓存的miss数。有限的三级缓存会不断地通过lru算法淘汰旧的/冷的数据，填充新的/热的数据。但如果访问的数据并没有被L3cache保存，系统在请求L3失败之后就会记录一次L3 cache misses。当然，在正常使用中这个值越大，性能会越差。<br>图中，B，新平台的L3 cache misses/s和A的差距已经“无法放在同一个坐标系下展示”足足526倍的差距。</p>



<figure class="wp-block-image"><img src="https://pic4.zhimg.com/80/v2-2bfd472637a286d5bd7dc09bc90636e4_720w.jpg" alt=""/></figure>



<p>一并的，放上内存带宽的数据，L3 cache misses之后，系统将不得不直接从内存获取数据，过大的cache misses之后直接导致了B的内存带宽远高于A的。这是另一个理解误区，同一个应用并于一定是不是内存带宽消耗越大，系统性能越好的。<br>好在这张图上展示的系统内存带宽最差情况也仅有不到250MB/s，以DDR4-2666内存来计算，这带宽也就相当于一个内存DIMM的10%略高。即便8个核心并发达到最高，也就是刚刚触及到了20GB/s</p>



<figure class="wp-block-image"><img src="https://pic4.zhimg.com/80/v2-f76a90d506310032aa7213b282587a6c_720w.jpg" alt=""/></figure>



<p>回到L3 cache上来，那究竟是什么原因导致了两台主机的L3 cache misses 有了500倍以上的差距呢？这就要看L3 cache的occupancy（使用空间）了。cache 空间越大，被挤出的概率就越小。<br>数据展示出A的L3 occ接近10MB，而B仅有6MB。结果自然……</p>



<p>回到两块CPU的具体参数上，上图了（A← B→）：</p>



<figure class="wp-block-image"><img src="https://pic4.zhimg.com/80/v2-266859d6eb4cc96c24742832e71c3b66_720w.jpg" alt=""/></figure>



<ul><li>尽管A B都是4核8线程的CPU，但A是一款3.5GHz CPU，B仅有1.5GHz，结合IPC部分的“基本一致”评价，得到事实上A的理论性能可以达到B的2.3倍。</li><li>A 和 B的实际L3大小和L3 occpuancy的测量大小基本一致，说明测试程序iperf对于L3的需求相对较多。B较少的L3容量导致了更高的L3 cache misses，以及后续一系列的内存带宽提升等问题。</li><li>当然，B的优势也非常明显，22W的功耗对比140W可算得上是绿色环保的产品了。而且，就CPU单价来说A的价格正巧是B的两倍。</li></ul>



<p>尽管说一分价钱一分货，但感觉还是有点搞技术的被搞采购的人坑了的意思。</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2020/08/28/cpu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Open media vault 开启Wi-Fi热点</title>
		<link>http://www.litrin.net/2020/05/23/open-media-vault-wi-fi/</link>
					<comments>http://www.litrin.net/2020/05/23/open-media-vault-wi-fi/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Sat, 23 May 2020 09:40:35 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[网络和安全]]></category>
		<category><![CDATA[samba]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3273</guid>

					<description><![CDATA[之前家里弱电箱一直放着着一套WD的网络硬盘做个人NAS，除了日常一家人的数据共享之外，还可以配合Macbook &#8230; <p class="link-more"><a href="http://www.litrin.net/2020/05/23/open-media-vault-wi-fi/" class="more-link">继续阅读<span class="screen-reader-text">“Open media vault 开启Wi-Fi热点”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>之前家里弱电箱一直放着着一套WD的网络硬盘做个人NAS，除了日常一家人的数据共享之外，还可以配合Macbook的时间机器功能，非常便利！眼瞧着用了接近8年的东西，也差不多到了必须要退休的时间了。另外，前几日家里的wifi坏掉了，用着卡到爆的电信的“接入设备”光猫+Wi-Fi撑了几天。这不到了618了，正准备一次性换掉两样东西。但简单的盘算了一下，觉得这次就自己一下DIY搞定。</p>



<span id="more-3273"></span>



<p>翻出了自己退役的一台4代NUC，自带无线网卡。准备安装open media vault。老实说，除了下载的时候网速慢需要科学上网之外真没什么值得细说的，中英界面一贯的方便。几大步也是照旧：下载，用balenaetcher制作启动盘，U盘启动安装程序，<strong>插上网线安装操作系统</strong>……。需要说明的是，安装的时候open media vault需要一块硬盘做系统盘，但实际使用时还需要另一块盘作为数据盘。从系统盘不到10G的使用量来看，完全可以做到安装到U盘上。</p>



<p>庆幸当时买的是厚机，系统除了一个现在都快买不到的mstat的硬盘之外还有一个2.5寸笔记本硬盘的空位；又找到了之前笔记本升级时换下的320GB机械硬盘组装了上去——硬件平台就搞定了。</p>



<p>安装完成后，应该至少有线网卡是配好的吧？先别急着用，通过SSH有线网卡的IP地址登录上去。由于Open media vault就是是一个基于Debian的派生版本，我就沿用debian的风格吧（还是习惯于Ubuntu的操作多一点）。</p>



<p>首先，安装所需的服务：</p>



<pre class="wp-block-code"><code>root@SmartyNUC:/etc# apt-get update 
...
root@SmartyNUC:/etc# apt-get install -y hostapd dnsmasq-base
...</code></pre>



<p>就只需要2个软件包，hostapd是真正的wifi 热点服务器。开始配置hostapd：</p>



<pre class="wp-block-code"><code>root@SmartyNUC:/dev/net# ip link
1: lo: &lt;LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s25: &lt;BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether b8:ae:ed:7e:3d:29 brd ff:ff:ff:ff:ff:ff
3: wlp2s0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 00:21:5c:c4:09:be brd ff:ff:ff:ff:ff:ff</code></pre>



<p>系统认出了两块网卡，有线网卡叫<strong>enp0s25</strong>，无线网卡叫<strong>wlp2s0。</strong>这里加粗，如果你参考这个文档的话，请留意把这两个关键字替换成你自己主机的网络接口名称。</p>



<p>编辑/etc/hostapd/hostapd.conf。</p>



<pre class="wp-block-code"><code>interface=wlp2s0  # interface
driver=nl80211      # ap driver

ssid=Litrin-SmartyNUC # WIFI SSID 

hw_mode=g        # 802.11g 
channel=11          # 1-13

# 802.11ac enabling, failed when I tried :P
#ieee80211ac=1 
#hw_mode=a
#channel=acs_survey
#

wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=8aa9c5cc36fc1 # password, may be changed 
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
</code></pre>



<p>然后是dnsmasq-base是用来做DHCP server的，它本身也可以用来做DNS cache server，由于我家里的网络通过都是通过电信的设备出口，不需要自己做DNS解析。编辑/etc/dnsmasq.conf&nbsp;</p>



<pre class="wp-block-code"><code>interface=wlp2s0                   #AP interface
listen-address=192.168.0.1      # AP ip
no-dhcp-interface=enp0s25   # internal ethernet need not DHCP 
bind-interfaces

expand-hosts
dhcp-range=192.168.0.2,192.168.0.250,255.255.255.0,2h           #; client IP 2~250, rotaion per 2 hrs. 
dhcp-option=option:dns-server,114.114.114.114,192.168.1.1,8.8.4.4 #; DNS server ip list 

cache-size=150</code></pre>



<p>上面的配置要求为无线网卡配置192.168.0.1的IP。编辑/etc/network/interface，发觉open media vault会接管网络管理，这里只能硬写。</p>



<pre class="wp-block-code"><code>source-directory /etc/network/interfaces.d
#---- original contents

#---- new added
interface wlp2s0                               # &lt;- interface
    static ip_address=192.168.0.1/24    # &lt;- IP / netmask
    nohook wpa_supplicant</code></pre>



<p>apply 无线网卡，我这里没有用网络重启的方式，选择了手工配置，效果是一样的，只是不需要冒断网的风险。</p>



<pre class="wp-block-code"><code>～# ip address add 192.168.0.1/24 dev wlp2s0</code></pre>



<p>尝试启动两个服务，居然无效！好吧，Debian……</p>



<pre class="wp-block-code"><code>~# echo 'DAEMON_CONF="/etc/hostapd/hostapd.conf"' >> /etc/default/hostapd 

~# echo ENABLED=1 >> /etc/default/dnsmasq 
~# echo CONFIG_DIR=/etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new >> /etc/default/dnsmasq </code></pre>



<p>再次尝试：</p>



<pre class="wp-block-code"><code>systemctl enable hostdapd
systemctl unmask hostdapd
systemctl enable hostapd</code></pre>



<p>理论上跟到这里你就可以看到你的wifi已经出现在列表中，可喜可贺！但千万别像我一样头脑发热直接断了现有wifi去尝试连接，因为现在DHCP还没开，拿不到IP地址。还是照旧一通：</p>



<pre class="wp-block-code"><code>systemctl enable dnsmasq
systemctl unmask dnsmasq
systemctl enable dnsmasq</code></pre>



<p>这次学乖了，用手机去连，可以拿到IP地址。下一步就是吧NAT转发配置好。感觉回到了当年做运维工程师的年代，看家的手艺没跑了。</p>



<p>第一步要开启内核的ip_forward</p>



<pre class="wp-block-code"><code>root@SmartyNUC:/etc/hostapd# sysctl -w net.ipv4.ip_forward=1
root@SmartyNUC:/etc/hostapd# sysctl -a | grep ipv4.ip_forward  >> /etc/sysctl.conf </code></pre>



<p>第二部是设置iptables 转发策略</p>



<pre class="wp-block-code"><code> iptables -t nat -A  POSTROUTING -o enp0s25 -j MASQUERADE
 iptables -A FORWARD -i wlp2s0 -o enp0s25 -j ACCEPT
 iptables -A FORWARD -o wlp2s0 -i enp0s25 -j ACCEPT  </code></pre>



<p>巧的是当时我的手机在这个时候忽然叮的一声收到了推送 。其实大多数的家用场景只要第一句就够了，设定让NAT转发，在每次wifi启动之后生效。搬回/etc/network/interface 文件。</p>



<pre class="wp-block-code"><code>source-directory /etc/network/interfaces.d
#---- original contents

#---- new added
interface wlp2s0                               # &lt;- interface
    static ip_address=192.168.0.1/24    # &lt;- IP / netmask
    nohook wpa_supplicant
    up  iptables -t nat -A  POSTROUTING -o enp0s25 -j MASQUERADE</code></pre>



<p>到这个时候其实已经结束了，我开始搞起了open media vault。然后就是装好了插件准备玩docker，可之后就忽然断了网。docker的安装和使用会重置网络环境，导致了之前的配置全部被覆盖了。应该是不难解决的问题，但docker并非是必须的，考虑到稳定性吧，就把docker卸掉拉倒了。更多好玩的还在研究中！</p>



<p>使用下来觉得我自己的8G内存和i5CPU完全是浪费——最大不到20%的CPU和内存使用率。于是拆了一根内存并通过脚本定频到了powersave mode 800MHz的CPU，也算省电+静音的选择。如果你是新购NUC做open media vault的话，我其实看下来不建议用i5/i7版本的NUC，i3甚至赛扬是足够的。感觉目前较新的NUC有一种自带读卡器的版本更为适合。恰饭一下，主要看看大约需要多少钱。<a rel="noreferrer noopener" href="https://union-click.jd.com/jdc?e=jdext-1247572437430513664-0&amp;p=AyIGZRhbFAsTA1cTWRYyEQdTHl0UARAPUBtrUV1KWQorAlBHU0VeBUVNR0ZbSkAOClBMW0sYWxMHFAZWGVMQAg1eEEcGJXIWeDQcM2dccWYJQANoZkReLWYiF3IeC2UbUhUAGgZUG1wWMhIGVBtfEAERAVQraxUHIkY7HF4cBBYGZRprFQYaBlYYXhAHGwZQGWsVChY3g7XPwIqt3uW%2Bjb%2Bsx4rlztK%2B1qyBZStrFjJJUgFSHgkCEQJTH10WBxABUBlaEQIQAFIeRxQyEgZUGFoXABADVnVaewIaBVESWhEEEg9lG2sWMkxpVEsIEQMXU1F1AUVZRl0QGxh7CxUGXBNSEDIQBlQZWQ%3D%3D" target="_blank">​</a></p>



<p>嗯，差不多控制在1500以内和2.5个小时的安装吧。省了一个WI-FI和一个家用NAS以及一个插座和一根排线。土炮版的Apple 时间胶囊 也不是很亏是吧，要算上重在拧巴开源精神呢？这就无价了！<a rel="noreferrer noopener" href="https://union-click.jd.com/jdc?e=jdext-1247573592067629056-0&amp;p=AyIGZRprFQMTBlQaWRABEg9WKx9KWkxYZUIeUENQDEsFA1BWThgOTkRHXE4ZVRpaFAMTBVAYWx0BDV4QRwYlcFNnEF8vUAt3DxEYIhNJYnQdbi59Yh4LZRtSFQAaBlQbXBYyEgZUG18QAREBVCtrFQciUTsbWhQDEwZUHV8QMhM3VR9TFAERAlMYWRULGjdVE18l1LyTgJPkzLK30f%2B1jpiyx47%2Bz%2BWTMiI3VisAQFZbQkkbWBAEFgFXH1MWAxUBUhhTFAYVG1QrWxQDEQZXGVkRAXwGOxtTFwYbBlEdWx0yEjdWKwV7A0JUBhxTEFZ8XQVAD1VDQFA7ElwQCxYEVCtZFAMQBQ%3D%3D" target="_blank"></a></p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2020/05/23/open-media-vault-wi-fi/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Faiss的多线程效率问题</title>
		<link>http://www.litrin.net/2020/03/26/faiss%e7%9a%84%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%95%88%e7%8e%87%e9%97%ae%e9%a2%98/</link>
					<comments>http://www.litrin.net/2020/03/26/faiss%e7%9a%84%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%95%88%e7%8e%87%e9%97%ae%e9%a2%98/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Thu, 26 Mar 2020 02:22:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[性能调优]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3250</guid>

					<description><![CDATA[过去的一周，被AI组的同事拉去分析faiss的多核心效率低下的问题。Faiss是由Facebook主导开源的一 &#8230; <p class="link-more"><a href="http://www.litrin.net/2020/03/26/faiss%e7%9a%84%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%95%88%e7%8e%87%e9%97%ae%e9%a2%98/" class="more-link">继续阅读<span class="screen-reader-text">“Faiss的多线程效率问题”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>过去的一周，被AI组的同事拉去分析faiss的多核心效率低下的问题。<a href="https://github.com/facebookresearch/faiss" target="_blank" rel="noreferrer noopener">Faiss</a>是由Facebook主导开源的一个“向量搜索（召回）算法”库。简单的说：Faiss能在一大堆库存的向量中找出与指定向量最相似的几向量。</p>



<span id="more-3250"></span>



<p>从理论上看，作为一个明显的CPU计算型的应用，更多的core意味着更大的计算吞吐能力。性能只会越来越好才是。可现实是当通过taskset命令分配更多的core给faiss（在本文中亦特指一个以faiss为基础的ivf proxy benchmark工具）只会带来更长响应时间以及更大的响应时间偏差（variation）。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" width="480" height="289" src="http://www.litrin.net/wp-content/uploads/2020/03/image-2.png" alt="" class="wp-image-3253" srcset="http://www.litrin.net/wp-content/uploads/2020/03/image-2.png 480w, http://www.litrin.net/wp-content/uploads/2020/03/image-2-300x181.png 300w" sizes="(max-width: 480px) 100vw, 480px" /><figcaption>更多core带来更长响应时间以及更大的响应时间偏差</figcaption></figure></div>



<p>一开始，我首先检查了CPU利用率，发现即便是性能最差的情况，CPU都是满载，这就非常不合情理了。本以为可能性最大（也是最容易解决）的一条路被堵死了，只能认栽，从调黑盒性能变成了调白盒性能。</p>



<p>代码是根据faiss自带的tutorial/cpp/3-IVFPQ.cpp改进而来。测试的流程大致上可以划分为3个阶段：建库（train)、校验（sanity check）、搜索。把每一个环节的耗时统计出来，发现校验过程并不需要太多时间，可以直接排除掉。而剩余的两个阶段都存在“核心越多，性能越差”的奇怪表现。而考虑到具体线上的正常使用模式：建库的过程是一次性的。意味着对建库的修复过程没有这么紧要，那就直接去到搜索阶段的优化。</p>



<p>基于break down。搜索阶段又分两个部分，一个是quantizer，一个是search_preassigned。这么说把，search_preassigned又是耗时大头。对这个函数经过1000次测试之后发现了一个问题：导致单个core和多个core之间性能差异的并非是大多数情况，是少数outlier（离群，囧）导致的平均数增加。outlier的出现频率和偏离成都与核心数呈正比关系。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" width="483" height="573" src="http://www.litrin.net/wp-content/uploads/2020/03/image-3.png" alt="" class="wp-image-3254" srcset="http://www.litrin.net/wp-content/uploads/2020/03/image-3.png 483w, http://www.litrin.net/wp-content/uploads/2020/03/image-3-253x300.png 253w" sizes="(max-width: 483px) 100vw, 483px" /><figcaption>outlier的出现频率和偏离成都与核心数呈正比关系</figcaption></figure></div>



<p>这种状况就算是诡异了，从分部特征来看，这应该跟线程的计算有关。在AI组的同事指导下将nprobe值和batch设置强置为1，从算法上保证search_preassigned只能用单核单线程。本来希望满满的，结果还是照旧。并且奇怪的是即便已经确信只有单线程在执行，系统中照样还是在多个核心上满负载。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" width="481" height="289" src="http://www.litrin.net/wp-content/uploads/2020/03/image-4.png" alt="" class="wp-image-3255" srcset="http://www.litrin.net/wp-content/uploads/2020/03/image-4.png 481w, http://www.litrin.net/wp-content/uploads/2020/03/image-4-300x180.png 300w" sizes="(max-width: 481px) 100vw, 481px" /><figcaption>search_preassigned函数单线程模式</figcaption></figure></div>



<p>看来白盒不是我的风格了，切换回黑盒方法，直接用perf top命令查看系统调用栈。发现在多核、单线程的模式下占比最高的居然是libgomp，而真正的benchmark(6-IVFPQ)只占了很少的CPU资源。</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="751" height="325" src="http://www.litrin.net/wp-content/uploads/2020/03/orig.png" alt="" class="wp-image-3256" srcset="http://www.litrin.net/wp-content/uploads/2020/03/orig.png 751w, http://www.litrin.net/wp-content/uploads/2020/03/orig-300x130.png 300w" sizes="(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px" /></figure>



<p>这就是核心的问题了：</p>



<ol><li>Faiss的多核心是通过openMP实现的。</li><li>默认OMP_NUM_THREADS等于所有可用的CPU数，即OpenMP默认将会在启动与核心数相同的线程数作为线程池。</li><li>默认情况下，openmp假定所有的调用都是计算密集型的。为了减少线程启动/唤醒过程需要上下文开销，系统必须时刻保证每一个线程都是alive状态。换句话说，要让线程活着，OpenMP会让线程池的每个线程做大量的无意义计算占据时间片而不是wait挂起。</li><li>quantizer 的过程中系统启动了omp线程池，理论上在修改后的search_preassigned开始后，线程池已经没有任何意义。但在放任不管的情况下，系统的每个核心的CPU使用率都会被空白计算占据，理论上100%。主线程结束之前线程池不会自己销毁。</li><li>这个时候如果位于主线程上的search_preassigned函数需要执行，那就不得不与OMP线程池抢占CPU time。这就是核心越多性能越差的原因。而放大这个影响的原因是我们的测试程序经过了变态级别的优化之后导致OMP的线程维护开销远远大于任务的CPU开销（微秒级响应，少于0.1个最小上下文）。这个测试事实上成为了某种程度“系统调度时延”测量。这个结果恰恰反应了预期。</li></ol>



<p>剩下的就是解这个问题了，那只要在合适的时候让线程池销毁所有线程就迎刃而解了。OpenMP的实现是基于编译器的，并没有详细的代码可以参考。找了文档，发现没有办法可以直接实现我的目的，只有两个对应的环境变量可以缓解：</p>



<ul><li><strong>GOMP_SPINCOUNT=&lt;int n&gt; </strong>omp线程经过了n个spin lock之后便被挂起。自然，n值越小就越早的挂起线程。</li><li><strong>OMP_WAIT_POLICY=PASSIVE</strong> 通过使用wait方法挂起 omp线程。对应的<strong>ACTIVE</strong>&nbsp;意味着线程池中的线程始终处于活动状态——消耗大量的CPU。</li></ul>



<p>我这里就用了后者，展示下优化结果，同样的坐标系之下，效果还是很感人的。再次perf top，omp线程已经不再出现在头部了。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" width="480" height="289" src="http://www.litrin.net/wp-content/uploads/2020/03/image-7.png" alt="" class="wp-image-3268" srcset="http://www.litrin.net/wp-content/uploads/2020/03/image-7.png 480w, http://www.litrin.net/wp-content/uploads/2020/03/image-7-300x181.png 300w" sizes="(max-width: 480px) 100vw, 480px" /></figure></div>



<figure class="wp-block-image size-large"><img loading="lazy" width="757" height="269" src="http://www.litrin.net/wp-content/uploads/2020/03/OMP_WAIT_POLICYPASSIVE.png" alt="" class="wp-image-3261" srcset="http://www.litrin.net/wp-content/uploads/2020/03/OMP_WAIT_POLICYPASSIVE.png 757w, http://www.litrin.net/wp-content/uploads/2020/03/OMP_WAIT_POLICYPASSIVE-300x107.png 300w" sizes="(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2020/03/26/faiss%e7%9a%84%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%95%88%e7%8e%87%e9%97%ae%e9%a2%98/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CPU各级缓存</title>
		<link>http://www.litrin.net/2020/02/24/cpu%e5%90%84%e7%ba%a7%e7%bc%93%e5%ad%98/</link>
					<comments>http://www.litrin.net/2020/02/24/cpu%e5%90%84%e7%ba%a7%e7%bc%93%e5%ad%98/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Mon, 24 Feb 2020 08:08:43 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3241</guid>

					<description><![CDATA[被问起CPU的各级缓存，才想起我之前一直没有对应的帖子介绍这块的内容。索性就借着这个机会讲讲吧。 对目前主流的 &#8230; <p class="link-more"><a href="http://www.litrin.net/2020/02/24/cpu%e5%90%84%e7%ba%a7%e7%bc%93%e5%ad%98/" class="more-link">继续阅读<span class="screen-reader-text">“CPU各级缓存”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>被问起CPU的各级缓存，才想起我之前一直没有对应的帖子介绍这块的内容。索性就借着这个机会讲讲吧。</p>



<p>对目前主流的x86平台，CPU的缓存（cache）分为L1，L2，L3总共3级。也有部分的文章中有FLC(first-level cache), MLC(mid-level cache), LLC (last-level cache)的方式区分目前的3级缓存。CPU cache通过比内存（Dram）有更低的时延达到了加速数据读取的效果。</p>



<span id="more-3241"></span>



<p>作为Linux的用户，可以使用系统默认安装的lscpu命令简单的列出CPU各级缓存的大小，如我用的一台AMD 7742 CPU具有32K的L1i L1d；512K的L2以及可见的16M的L3（这里埋个伏笔）。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" width="287" height="258" src="http://www.litrin.net/wp-content/uploads/2020/02/image.png" alt="" class="wp-image-3242"/></figure></div>



<p><strong>各级缓存的用途</strong></p>



<p>首先说说L1i和L1d的区别，i指的是instruction指令缓存，d是数据data缓存。作为冯·诺依曼体系的计算机，x86价格的指令和数据在内存中是统一管理的。但由于两者内容访问特性的不同（指令刷新率更低且不会被复写），L1的缓存是做了区分的。当前的Intel平台中L1缓存的时延为3个时钟周期，以2.0GHz的CPU计算约1.5纳秒。这种级别的时延可以极大的加速超线程以及CPU分支预测带来的性能优势。</p>



<p>L2缓存的时延是L1的5倍左右，即8ns。每个CPU的物理核心都有自己独立的L2缓存空间。而L3的时延在50～70个时钟周期，30ns。不同于L2，L3缓存是多个核心共享的，L3在使用场景中最大的用途是减少数据回写内存的频率，加速多核心之间的数据同步。</p>



<p>另：内存和cpu cache的访问统一都是64byte对齐的。也就是说即便你只需要读取1bit的数据，CPU还是会把64byte的数据从内存逐步扔到L1。</p>



<p>此外，CPU中事实上还存在着TLB（Translation Look aside Buffer，页表缓存）的组件类似于cache的功能。它主要负责缓存页表逻辑到物理地址的对应关系。跟L1类似，TLB也分为iTLB和dTLB分别对应了指令页表和数据页表的地址转换结果。</p>



<p><strong>缓存大小的测量方式</strong></p>



<p>自然，你可以像我一样通过lscpu命令获得CPU各级内存的大小。但在这里，我想找一个直观测试方法的例子让大家感受一下不同大小的缓存之间的关系。我使用到了lmbench的一个组件lat_mem_rd这个工具可以创建不同大小的内存对象，通过访问该对象的时延我们可以用来简单的标定各级CPU缓存的大小。</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="851" height="592" src="http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-24-下午3.25.50.png" alt="" class="wp-image-3243" srcset="http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-24-下午3.25.50.png 851w, http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-24-下午3.25.50-300x209.png 300w, http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-24-下午3.25.50-768x534.png 768w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></figure>



<p>上图展示了我在AMD 7742 CPU上得到的测试数据。横坐标为内存对象的大小，纵坐标为对应的参考时延。很明显的，线图展示了“4级阶梯”的模样。分别对应的CPU命中L1～L3以及命中内存的时延差距。</p>



<p>前面说的一个伏笔：AMD官方给出的数据是“7742CPU的L3缓存大小为256M，而lscpu看到的L3缓存仅为16M”。上图中的“第三到第四级”台阶之间，内存对象的大小恰恰是16M，符合lscpu的结果。这说明AMD Rome架构不同于Intel架构，L3并不是一个整体，它是由16个相互独立的分区组成的，每个分区只能被4个CPU核心共享。——如果你看了上面讲到的L3是“加速多核心之间的数据同步”的，那就是说这种架构下L3的优势被限制在了4个CPU核心之间。</p>



<p>L3 缓存的技术主流又有inclusive和non-inclusive的区别，目前IA两家都逐步采用了后者。两者的区别是inclusive L3实现上L3的内容包含了所有L2的内容；而non-inclusive实现中，L2的内容不会再在L3里出现。显而易见地，non-inclusive更好的节省了宝贵的L3空间。</p>



<p><strong>Cache的效</strong>能评估</p>



<p>即便是L3的实现成本也远远大于内存。那如何评价Cache是否物尽其用了呢？</p>



<p>对于CPU cache（包含tlb）的效能，可以用MPI（cache misses per instruction，每指令cache不命中率）由于这个值普遍小于1%，有时也会将这个值乘1000计作MPKI来测算。考虑到大多数的CPU指令都离不开cache的读写。MPI值越低，说明CPU cache被有效使用的比率越高。</p>



<p>个人的经验是MPI一旦大于4%则认为cache的优化是“不合适的”。但事实上确实存在一种类型的业务（比如流计算）数据不会被反复更新，那额外的cache访问非但没有意义反而会通过LRU挤出效应干扰到共享L3 cache的其他核心上的业务。这个时候就可以考虑使用Non-temporal的读写操作，系统将认为此内存的访问是no cacheable的。 </p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2020/02/24/cpu%e5%90%84%e7%ba%a7%e7%bc%93%e5%ad%98/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PMU Event counter的使用状况检测</title>
		<link>http://www.litrin.net/2020/02/11/pmu-event-counter%e7%9a%84%e4%bd%bf%e7%94%a8%e7%8a%b6%e5%86%b5%e6%a3%80%e6%b5%8b/</link>
					<comments>http://www.litrin.net/2020/02/11/pmu-event-counter%e7%9a%84%e4%bd%bf%e7%94%a8%e7%8a%b6%e5%86%b5%e6%a3%80%e6%b5%8b/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Tue, 11 Feb 2020 05:34:13 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[性能调优]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3234</guid>

					<description><![CDATA[题目用中文反而有点绕，How to detect whether PMU event counters are &#8230; <p class="link-more"><a href="http://www.litrin.net/2020/02/11/pmu-event-counter%e7%9a%84%e4%bd%bf%e7%94%a8%e7%8a%b6%e5%86%b5%e6%a3%80%e6%b5%8b/" class="more-link">继续阅读<span class="screen-reader-text">“PMU Event counter的使用状况检测”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>题目用中文反而有点绕，How to detect whether PMU event counters are occupied?</p>



<p>就是在前几天全民在家办公的时候，有个来自A公司的“大客户”发邮件咨询了类似的问题。事发他们正在开发一套基于PMU （performance monitoring unit）event counter的工具用于监控物理主机的硬件资源使用情况。但他们在开发过程中发现有些客户的某些应用同样也会使用到Event counter，导致Event counter的使用出现冲突，数据不可用。于是自然就需要一种event counter是否正在使用的机制。</p>



<span id="more-3234"></span>



<p>PMU event counter（以下简称event counter）顾名思义其实是一个集成在硬件里的计数器，经过编程（或者“re-program”再编程）之后可以对某些系统的事件——比如执行的指令条数等——进行计数。使用者可以通过两次计数器的差值获得在此时间内对应事件发生次数，进而对整个系统的有了更好的了解。</p>



<p>回到那个痛点。基于硬件实现的event counter的数量其实有限（最新一代的CPU只有7个hw event counter），但可用的event的种类却远大于event counter的数量。如果这时候系统中出现两个进程访问同一个event counter且为该event counter指定不同的事件时，即出现了PMU event counter冲突。</p>



<p>X86平台对于这个问题的现实解决方法是MSR（Model Specific Register）“注册法”，简单来说就是：“你在用那个event counter提前说”。对于开发者来说官方给的建议可以参考Intel-SDM vol3, chapter 18.2的内容（注：该章节同时包含了比如event counter的溢出判等多种事件判断，建议PMU开发人员精读。）。这里就简单的说说如何检测哪些event counter被使用了，这里就牵扯到直接读取0x38F这个MSR，只有当返回值为0的时候才意味着没有其他的进程占用了event counter。</p>



<pre class="wp-block-preformatted">～# rdmsr -p &lt;CPU core ID&gt; 0x38F # rdmsr命令依赖于msr-tool安装包</pre>



<figure class="wp-block-image size-large"><img loading="lazy" width="653" height="239" src="http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-11-下午1.10.36.png" alt="" class="wp-image-3235" srcset="http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-11-下午1.10.36.png 653w, http://www.litrin.net/wp-content/uploads/2020/02/屏幕快照-2020-02-11-下午1.10.36-300x110.png 300w" sizes="(max-width: 653px) 100vw, 653px" /></figure>



<p>本来呢，其实到这里就结束了，但考虑到很多时候，有些进程或者说PMU工具并不是遵守了“谁使用谁注册的方式”——我自己不止看到过一种（开源）工具不检查、不注册直接上手program event counter的！或者还有一种情况就是有些PMU的工具并不是安全退出（强行被kill）的。那造成的结果就是没有更新event counter的注册信息。这种rdmsr方式只能针对更“守规矩”的event counter使用方式。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2020/02/11/pmu-event-counter%e7%9a%84%e4%bd%bf%e7%94%a8%e7%8a%b6%e5%86%b5%e6%a3%80%e6%b5%8b/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AMD Rome benchmark数据到架构特征推导</title>
		<link>http://www.litrin.net/2019/12/18/amd-rome-benchmark%e6%95%b0%e6%8d%ae%e5%88%b0%e6%9e%b6%e6%9e%84%e7%89%b9%e5%be%81%e6%8e%a8%e5%af%bc/</link>
					<comments>http://www.litrin.net/2019/12/18/amd-rome-benchmark%e6%95%b0%e6%8d%ae%e5%88%b0%e6%9e%b6%e6%9e%84%e7%89%b9%e5%be%81%e6%8e%a8%e5%af%bc/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Wed, 18 Dec 2019 00:51:03 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3188</guid>

					<description><![CDATA[这几天，拿到了一套最新的AMD Rome主机：EPYC 7742 @1.5GHz x 2; 16根32GB D &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/12/18/amd-rome-benchmark%e6%95%b0%e6%8d%ae%e5%88%b0%e6%9e%b6%e6%9e%84%e7%89%b9%e5%be%81%e6%8e%a8%e5%af%bc/" class="more-link">继续阅读<span class="screen-reader-text">“AMD Rome benchmark数据到架构特征推导”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>这几天，拿到了一套最新的AMD Rome主机：EPYC 7742 @1.5GHz x 2; 16根32GB DDR4-3200内存。这是AMD的顶级配置，即Rome平台目前推出的最高频率的全部128个核心（开启HT显示为256个）且全部开启最高速率内存通道。这次就从几个测试数据推导出AMD的架构特征特别是内存方面的架构特征。</p>



<span id="more-3188"></span>



<p>老实说，这台机器是远程的主机，我没有办法直接接触到CPU本体，只能找一张网上的图片代替。随着x86平台的设计愈发的复杂，良品率成为一个巨大的挑战，而IA两家在提升良品率的方向上可谓是各显神通：I通过SKL之后引入的mesh互联技术允许成品后直接屏蔽有瑕疵的core，成为低档次的产品（俗称“阉割”）；而A则采用了多个小硅片的堆叠互联，这样一旦有成品瑕疵，丢弃的只是这一小片硅片（俗称“众核”）。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/12/image-1.png" alt="" class="wp-image-3193"/></figure></div>



<p>从这张图上可以看出，EYPC7742共有1大8小9块硅片互联而成。8块小硅片称为core die，中每个硅片包含8个CPU core ；中间的“大”硅片称为IO die，包含了CPU的通讯和安全部分的功能。当然，作为通讯的部分，内存访问、跨核通讯都包含在IO die里。需要特别指出的是，在IO die里包含了AMD引以为傲的<strong>infinity febri</strong>（IF）技术，相比Intel的UPI/mesh多层路由打造的网状结构，该技术统一了CPU之间各种的通讯接口，形成了一格类似总线结构的拓扑。IF可以分为数个相连的<strong>Scalable Data Fabric</strong>&nbsp;(<strong>SDF</strong>) 和&nbsp;<strong>Scalable Control Fabric</strong>&nbsp;(<strong>SCF</strong>) 分别负责数据和控制信号的处理和转发。</p>



<p>从拓扑结构上讲，这个双插槽的服务器可谓是空前的复杂。为方便更好的理解这“8乘2”的设置，可以直接使用core-2-core latency对比。我经常挂在嘴边“从CPU的纳秒视角来看，光速是很慢的（30cm/ns），而信号传输更慢”。不同的c2c时延即可直接得出各个core之间的物理距离。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/12/ROME_c2c.png" alt="" class="wp-image-3195"/><figcaption>EYPC 7742 core-to-core latency </figcaption></figure></div>



<p>上表列出了从core#16发起的，到所有127个core的时延，单位是ns纳秒。如果你的显示器对比度说得过去的话，应该可以看到大致上可以分为几组（数学意义上的聚类）实验范围：</p>



<ul><li>&lt; 60 ns：只有core#16发起给自己的本地，52.7ns作为比较基线。</li><li>&lt; 70 ns:  即最绿的部分，4个，其实是共享L3缓存的4个core， 只有额外的7~8ns的增加。</li><li>&lt; 120 ns：32个，参考上面的图片，在IO die同一侧的4个core die。</li><li>&lt; 130 ns:  64个，同一个socket上所有的core。</li><li>&lt; 180 ns:  所有的两个socket上所有的core。有一个有趣的现象是core#80～core#96有着最大的时延，恰恰又跟core#16所在的socker0 core 16 是对称的（core#80 = socket 1 core 16）可以理解为这个通讯走了最远的对角线。</li></ul>



<p>总结下：同die 8 ns；同侧 55ns；横跨IO die 75ns；跨socket 120ns。如果你会做减法的话得出来的数值就是对应的通讯时延开销。CPU是1.5GHz，这个数值可以通过x1.5直接转换成cycles，用以计算IPC/CPI这类性能计数中损耗的那一部分。</p>



<p>由于实现方式不同，怎么对比都有失公允，这么说吧：同等类型的测试，Rome架构是<strong>远远差于</strong>Skylake架构的。</p>



<p>其实在我刚拿到那台机器的时候，惊呆于2个socket居然有8个NUMA node，进BIOS检查了一下，发觉该机器支持每个socket 0，1，2，4 共计4种设置（NPS， NUMA node per socket）。考虑到众核的特点，这种方式也可以理解。该主机为双socket，实际可见的NUMA数量要乘2。</p>



<figure class="wp-block-image size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/12/CaptureScreen-1.png" alt="" class="wp-image-3197"/></figure>



<p>既然有了c2c，那就来个socket-to-socket的时延吧（单位为ns，其中横向第一行为请求发起node，纵向第一列为目标node），把不同配置下8～2个NUMA node s2s贴出来。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/12/rome_s2s_4NPS.png" alt="" class="wp-image-3200"/></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/12/rome_s2s_2NPS.png" alt="" class="wp-image-3201"/></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/12/rome_s2s_1NPS.png" alt="" class="wp-image-3202"/></figure></div>



<p>不同的色块热度代表了不同的node亲和性。同时数据上可见，NUMA node越多，时延指数增大，可以理解为更大的哈希开销导致的。</p>



<p>另一个点是要注意这里的4NPS揭示了另一个重要信息——单socket包含4个内存控制器（考虑到NUMA的实现方式，最大NUMA node数量和内存控制器数量是一一对应的，况且Rome架构中内存控制器就被命名为<em>unified memory controller</em>）！换算一下，每个socket最大8个内存通道，4个控制器意味着每8个core分享一个双通道DDR-3200内存控制器，数值换算为～<strong>42GB/s </strong>。话说后来通过其他测试我发觉这个42GB/s带宽事实上主导了整个IF的设计实现，这里先划个重点。实话说，尽管3200是最高频的DDR4，单如此的分配总有点捉襟见肘的意思。</p>



<p>那就做个简单的内存带宽压力测试好了。分别在全局1，2，4，8，16个core（图中用thread计）上施加内存压力，测量在不同NUMA配置下所能达到的最大内存带宽，后来为8个core的case，有意识的调整了一下core的分布设计了8+case，绘图如下：</p>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/12/image-3.png" alt="" class="wp-image-3206" width="643" height="317"/></figure></div>



<p>由以上数据可见：</p>



<ol><li>所有的1 core case始终无法超过20GB/s的内存带宽。且随着NUMA node数量减少，需要依赖IF传输的数据量不断增加，导致了2，1 NUMA node时可见带宽下降趋势。</li><li>相比单核心而言，2核心可见带宽明显提升，但继续增加到4或8核心时，提升速度明显下降，此时带宽接近42GB/s。</li><li>对于8 core即8node配置下一个NUMA node中所有的core都参与了内存加压，内存带宽始终无法突破42GB/s。</li><li>8core 和8+core连个case唯一的区别在于对于8NUMA node的模式（蓝色）8+强行指定了内核心分布，压测工具特性决定了跨NUMA访问的带宽将会直接体现为最大内存带宽。而事实上采集到的数据略高于42GB/s，其实是符合本人预期的。另外， 由于case8+的核心分布更优， 在除8NUMA node模式以外，可以更好的利用2个以上的memory controller，就是为什么从整体上来说case 8+的内存性能要明显好于case 8。</li><li>8+与16这两个case的所有读数异常相似，意味着在类似本测试的极端使用情况下，共享同一个memory controller 的16个core中只要有4个core同时发起内存访问就足以使内存带宽饱和。4 core case 和8 core case的情况一致也可以作为一个侧面反映。</li><li>另有几个没有体现在图上的数据，当所有127个core都发起请求时，测得2NUMA nodes模式下最大内存带宽为286GB/s，但相比理论上的380GB/s还有一定距离。而1NUMA node模式下，同等测试只得到144GB/s，而这个值事实上是包含了耗损的4个42GB/s。<br><br>（除此以外，以上的部分数据似乎可以直接拿来计算IF/SDF协议的payload rate 哦！）</li></ol>



<p>尽管Rome拥有纸面上的单核8通道DDR4-3200，但事实上内存控制器的零散化导致了对于大多数情况下的内存带宽并不能直接受益于更多内存通道，甚至导致了单核心内存性能远低于台式机水平。但零散化的优势是更好的资源隔离性，多种灵活的NUMA分割方式最大化了这个优势。</p>



<p>从个人观点上说，<strong>Rome这一代的CPU架构的优缺点都体现了云计算时代对服务器CPU的要求！</strong>更零散的设计在降低了成本的同时也很好的提升了硅片表面积以便于散热；7nm的工艺更是大幅减低了功耗和发热，这是提升机房计算密度的必由之路。零散的内存控制器，过少的单核内存带宽和同样零散低效的L3 cache（本文中未展开），确实极大的拖累了整体的性能表现，使其在大规模计算和科学计算方面失分不少。但这种特殊的设计思路恰恰极大地满足了云计算所重视的“可合可分”的痛点，完全可以扳回一城。退一步说，事实上当下业界共识是使用各种加速卡、GPU、FPGA/ASIC替代传统上CPU进行的大规模计算，低耦合分布式也成为CPU侧计算的主导思维。Rome确实做了一个主动迎合主流市场需求的妥协。</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/12/18/amd-rome-benchmark%e6%95%b0%e6%8d%ae%e5%88%b0%e6%9e%b6%e6%9e%84%e7%89%b9%e5%be%81%e6%8e%a8%e5%af%bc/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>从Linux perf的数据采集说起</title>
		<link>http://www.litrin.net/2019/10/31/%e4%bb%8elinux-perf%e7%9a%84%e6%95%b0%e6%8d%ae%e9%87%87%e9%9b%86%e8%af%b4%e8%b5%b7/</link>
					<comments>http://www.litrin.net/2019/10/31/%e4%bb%8elinux-perf%e7%9a%84%e6%95%b0%e6%8d%ae%e9%87%87%e9%9b%86%e8%af%b4%e8%b5%b7/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Thu, 31 Oct 2019 13:15:02 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[性能调优]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3136</guid>

					<description><![CDATA[挺长时间了，不太情愿地做了一个给蝙蝠集团之一的consultant，即给所谓“精细化资源管理”做支持，该系统通 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/10/31/%e4%bb%8elinux-perf%e7%9a%84%e6%95%b0%e6%8d%ae%e9%87%87%e9%9b%86%e8%af%b4%e8%b5%b7/" class="more-link">继续阅读<span class="screen-reader-text">“从Linux perf的数据采集说起”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>挺长时间了，不太情愿地做了一个给蝙蝠集团之一的consultant，即给所谓“精细化资源管理”做支持，该系统通过一个Linux perf为接口的守护工具，不断向控制节点发送当前系统中每一个应用的细粒度资源使用情况，方便从全局入手为应用程序求得最优分配方案。在这个过程中，A-B测试的结果反映这个守护工具会影响到业务的性能——这是意料之中的，对方的认可的心理底线是性能下降3%以内。可问题是对于某个核心应用的特定场景，这个工具居然导致了30%左右的性能下降，这就尴尬了！</p>



<span id="more-3136"></span>



<p>首先，简单看了看守护工具的代码，这家头部字母企业即便是测试的工具，代码质量还是非常不错的：include perf_event.h，中规中矩的定义event什么的，完全无懈可击。查看了多种其他业务的性能影响，几乎都控制到了3%一下，这就让这个关键应用显得更加刺眼。</p>



<p>linux的perf_event.h头文件事实上是调用了内核中的perf模块。作为一个内核模块，Linux的perf事实上是一个涵盖多种功能的大而全工具，而在这个案例中对方仅仅只是用到了perf的counter功能的hardware event的计数器访问而已。也就是下图中右侧PMC(performance monitor counter)部分的event。这块的功能映射到普通的perf命令那就是一句最简单不过的 “perf stat -e &lt;event&gt;,&lt;&#8230;&gt; -G &lt;cgroup&gt; -I 1000 ” 。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image.png" alt="" class="wp-image-3148"/></figure></div>



<p>回来说底层真正用于性能计数的hardware event counter。Perf中这几个硬件计数器的访问需要经由一系列的过程：</p>



<ol><li>寄存器编程</li><li>读取初始值</li><li>触发事件计数</li><li>读取变化后值</li><li>获得delta</li></ol>



<p>这是一个缺一不可的逻辑链，如果需要监控线程级别的事件就必然遭遇到内核级别的上下文切换。这个5大步操作就会显的非常啰嗦。主要对系统性能的影响来自几个方面：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image-1.png" alt="" class="wp-image-3149"/></figure></div>



<p>首先是perf一旦被起用，系统会在每次上下文切换的时候在需要perfile的线程执行之前需要额外执行寄存器编程和读取初始值两个操作；在线程切出之后要执行“读取变化后的值”和“获得delta”部分的操作。这两个过程需要耗费额外的时间开销。</p>



<p>还有软中断，考虑到守护工具有自己固定的统计频率，某些时刻需要通过定时器发送软中断强制打断现有的线程强行进行上下文切换。其实对于高性能、大并发的应用来说特别是已经需要“24小时重症监护”的关键应用来说， 处理上下文切换和各种中断已经称得上是系统最重的额外开销了。</p>



<p>最后，由于硬件计数器的数量有限（尽管Skylake这一代每个HT有4个，而在默认情况下Linux kernel仅允许使用3个），如果需要计数超过4种以上的事件，perf一样还是会采用分时复用的方式轮流监控几个事件——当然这会增加上下文的切换时间和切换次数。下图展示的是不同的event数量带来的上下文切换数量的变化，额外说一句，该主机打通了任督二脉，可以使用4个event counter。而计数4个以上的事件时，上下文切换数量直线上升。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image-8.png" alt="" class="wp-image-3162"/></figure></div>



<p></p>



<p></p>



<p>不仅如此，同时由于硬件计数器数量的限制，还会带来额外的误差。下图的实验是同步取样5个相同的事件，理论上应该拿到相同的5个数值，但由于该主机仅支持同时使用3个event counter，那结果就是其中2个事件的返回结果和其他3个完全不一样。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image-5.png" alt="" class="wp-image-3153"/></figure></div>



<p>相比之下，对于原本相对消耗不小的数值计算部分的资源消耗反而会显得微不足道且无法避免了。</p>



<p>经过这么一分析，原因差不多定位了。但由于我们始终无法得知该应用的特性，多次询问，只能简单得到几个线索：每个应用实例包含多个Java进程/线程数量远大于CPU的核心数（线程超饱和）、有网络接口的微服务（网络开销和系统调用频繁）、大并发应用（业务简洁但碎片化）、时延敏感型……最后就是该应用以container为运行环境。而守护工具是以container为基本监控单位的。</p>



<p>多进/线程，意味着更多的上下文切换，每次切换都要翻来覆去的执行那“5大步”，不用解释肯定会带来更长的应用程序时延。container的封装又让系统不得不花费额外的时间做多级数据合并统计。加上延敏感型业务意味着应用的性能评价会更多的放大上下文切换时“5大步”和“统计合并”带来的影响。</p>



<p>后来复现实验的结果也带来的另一条“重要信息”证明了我的猜测：同一主机部署的应用数量越多，性能下降的越多。</p>



<p>纯属自己好奇，在系统正常运行时，象征性的让对方执行了下：</p>



<pre class="wp-block-code"><code>perf record -e msr:* -a sleep 10 &amp;&amp; perf script | xz -z - > xxx.txt.xz</code></pre>



<p>10秒时间生成了压缩后300M的数据包！捎带秀一把看家的shell操作</p>



<pre class="wp-block-code"><code>[root@localhost perf]# xzcat xxx.txt.xz | awk '{print $5" "$6}' | sort | uniq -c | sort -n -r | head -30 
6879145 msr:write_msr: c0000100, -> FS based 
5430449 msr:write_msr: 38d,  -> write/initial fixed event counter
5430119 msr:read_msr: 38d,   -> read fixed event counter
3268459 msr:write_msr: 38f,  -> write/initial global event counter
2715208 msr:write_msr: 186,  -> write/initial event counter 1 
....
1336535 rdpmc: 40000000      -> fixed controller 0 read by rdpmc
1336514 rdpmc: 1             -> controller 1 read by rdpmc</code></pre>



<p>统计了系统在10秒钟内msr调用的次数，事实上映射回“5大步”都对应了一个或者多个msr调用。居然头部几个读写操作都大几M。<br>简单的纸面推算是这样的：所有的MSR操作都需要fence操作，一个fence差不多15 cycles加上后续的分支预测逻辑开销40 cycles，以及每次event counter读写消耗的大约70~80 cycles，这一个就是135 cycle。10秒钟时间总共出现了50M+的msr操作，就是6.7G个cycle消耗在监控这件事上，至少等于一个CPU核心的3秒以上时间的工作量。</p>



<p>对于这种情况，我第一次给的建议是对每个container分别隔离，这样的出发点是隔离之后多个应用程序之间的相互CPU资源抢占的概率会减少，上下文切换的概率也会随之降低。不久以后得到的回复是：“性能有所改良，但尚未达到要求。”</p>



<p>继续优化到对方满意为止呗。考虑到CPU已经隔离了，那就直接说服对方放弃进程级别的监控，直接监控CPU级别，且将event控制到4个。此时整个过程就变成了：</p>



<ol><li>寄存器编程</li><li>读取初始值</li><li>触发事件计数</li><li>读取变化后值1</li><li>获得delta1</li><li>触发事件计数</li><li>读取变化后值2</li><li>获得delta2……</li></ol>



<p>而秒级别的性能监控数据对整个系统的影响就会变得微乎其微了。但这样的话会带来另一个问题：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image-2.png" alt="" class="wp-image-3150"/></figure></div>



<p>上图是一个进程级别的监控，TSC是系统的CPU时钟周期数，是系统中最准确的计时器。这是一块2.3GHz的CPU，如果不看结果你会觉得既然sleep了1秒整，那系统肯定会有2.3G个TSC。而实际上对于sleep 1命令，尽管给人的感觉是系统停顿了1秒，但对于Linux kernel来说事实上只需要2.2M个TSC之后就将对应的CPU资源释放出来了。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image-3.png" alt="" class="wp-image-3151"/></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="http://www.litrin.net/wp-content/uploads/2019/11/image-4.png" alt="" class="wp-image-3152"/></figure></div>



<p>加了-c参数，perf将直接监控CPU1的TSC而不是之前的sleep进程数据，这个命令已经跟上面一个完全不同了，这才是意义上的1秒钟能有多少个TSC！那结果上2.3G个TSC那是必须的，也自然在两个CPU core参与的时候就成了2.3G*2（当然，在这种粒度下时间是无法做到完全精确的）。这一上一下可是K的数量级。</p>



<p>好在“关键应用”的特点是它几乎没有空闲的时候，即CPU的利用率始终处在高位。加上计算的方法一旦统一之后，事实上用户只是关系数据的微分值而并非数据本身。尽管出乎我的意料，但对方欣然接受了我们的方案。</p>



<p>瞧瞧 所谓“精细化资源管理” 、SLA红线、QoS保障啥的把人都逼成什么样了！</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/10/31/%e4%bb%8elinux-perf%e7%9a%84%e6%95%b0%e6%8d%ae%e9%87%87%e9%9b%86%e8%af%b4%e8%b5%b7/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Linux的serial串口控制台</title>
		<link>http://www.litrin.net/2019/09/20/linux%e7%9a%84serial%e4%b8%b2%e5%8f%a3%e6%8e%a7%e5%88%b6%e5%8f%b0/</link>
					<comments>http://www.litrin.net/2019/09/20/linux%e7%9a%84serial%e4%b8%b2%e5%8f%a3%e6%8e%a7%e5%88%b6%e5%8f%b0/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Fri, 20 Sep 2019 04:23:48 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3120</guid>

					<description><![CDATA[本人大多数情况都是在调试服务器大量的linux服务器，很多情况下也不没有必要专门准备KVM（keyboard, &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/09/20/linux%e7%9a%84serial%e4%b8%b2%e5%8f%a3%e6%8e%a7%e5%88%b6%e5%8f%b0/" class="more-link">继续阅读<span class="screen-reader-text">“Linux的serial串口控制台”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>本人大多数情况都是在调试服务器大量的linux服务器，很多情况下也不没有必要专门准备KVM（keyboard, video, mouse），甚至有些机器根本就没有显示器接口。如何调试的？闲来无事，分享一下。</p>



<span id="more-3120"></span>



<p>有些人说“ 给我个Linux窗口 ”是想要一个：</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/09/2019-09-20-10-57-04screen-1-1024x576.png" alt="" class="wp-image-3124" width="584" height="328"/></figure></div>



<p>另外一些人说“给我个Linux窗口”，就是说他打算挖煤了：</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/09/MobaXterm-screenshot.png" alt="" class="wp-image-3121" width="571" height="387"/></figure></div>



<p>然鹅，很多时候我说“ 给我个Linux”，意思是我要：</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/09/IMG_20190920_081411-1024x768.jpg" alt="" class="wp-image-3122" width="486" height="365"/></figure></div>



<p>串行口（serial port）可以说是少数几个上古时期流传下来的接口了（当年的电话小猫就是走这个接口的）即便你的主机上没有这个接口，著名的某宝上还是很容易找到串口到USB的转接线的。而Linux本身就支持串口终端。</p>



<p>启用串口终端需要修改grub的配置，个人非常不建议直接修改grub.cfg文件，建议从/etc/default/grub配置开始修改。</p>



<p>编辑 /etc/defualt/grub</p>


<p>GRUB_SERIAL_COMMAND=&#8221;serial &#8211;speed=115200 &#8211;unit=0 &#8211;word=8 &#8211;parity=no &#8211;stop=1&#8243;<br />
 GRUB_TERMINAL=&#8221;serial console&#8221; # 这一行的值原本是&#8221;console&#8221;</p>



<p>找到GRUB_CMDLINE_LINUX这一行，在末尾增加 “console=<strong>ttyS0</strong>,115200” 注意引号！ 本例假定你连接的是com1口，即在Linux下被称为ttyS0 </p>



<p>当前的OS启动方式分为EFI和BIOS两种，所以生成grub.cfg的时候容易出问题，所以这里我用了find命令查找grub.cfg</p>


<p>grub2-mkconfig -o $(find /boot -name grub.cfg)</p>



<p>完成后重启OS后Linux kernel配置生效。</p>



<p>然后是在终端，以windows为例，链接好串口线之后首先确认你的本地端口是什么，这个可以通过windows的设备管理器查看，比如我用的USBtoSerial连接线是COM4。</p>



<figure class="wp-block-image"><img src="http://www.litrin.net/wp-content/uploads/2019/09/image-1.png" alt="" class="wp-image-3126"/></figure>



<p>安装并打开putty, connect type 选择serial, Serial line填COM4, Speed 写115200。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/09/image-3.png" alt="" class="wp-image-3128" width="367" height="330"/></figure></div>



<p>如果出现乱码，或者不响应的情况多半是配置不对，请确保putty的serial配置项跟Linux的GRUB_SERIAL_COMMAND保持一致。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/09/image-2.png" alt="" class="wp-image-3127" width="382" height="345"/></figure></div>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/09/20/linux%e7%9a%84serial%e4%b8%b2%e5%8f%a3%e6%8e%a7%e5%88%b6%e5%8f%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>一个测试性能不稳定的问题</title>
		<link>http://www.litrin.net/2019/09/06/%e4%b8%80%e4%b8%aa%e6%b5%8b%e8%af%95%e6%80%a7%e8%83%bd%e4%b8%8d%e7%a8%b3%e5%ae%9a%e7%9a%84%e9%97%ae%e9%a2%98/</link>
					<comments>http://www.litrin.net/2019/09/06/%e4%b8%80%e4%b8%aa%e6%b5%8b%e8%af%95%e6%80%a7%e8%83%bd%e4%b8%8d%e7%a8%b3%e5%ae%9a%e7%9a%84%e9%97%ae%e9%a2%98/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Fri, 06 Sep 2019 14:06:25 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3104</guid>

					<description><![CDATA[经常会通过一些通用的测试工具测试一些服务器的性能，个人非常喜欢SPEC的一系列产品，特别是SPECcpu。SP &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/09/06/%e4%b8%80%e4%b8%aa%e6%b5%8b%e8%af%95%e6%80%a7%e8%83%bd%e4%b8%8d%e7%a8%b3%e5%ae%9a%e7%9a%84%e9%97%ae%e9%a2%98/" class="more-link">继续阅读<span class="screen-reader-text">“一个测试性能不稳定的问题”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>经常会通过一些通用的测试工具测试一些服务器的性能，个人非常喜欢SPEC的一系列产品，特别是SPECcpu。SPECcpu原理上通过接近30种不同特性的组件的执行时间对服务器的计算吞吐能力进行一个客观的评价。当然，由于计算机的硬件配置不同，不同的程序跑分各有千秋，但有一点也是SPECcpu能够成为业界标准之一的原因是跑分结果的稳定。这么说不夸张：如果其中的一个测试组件两次跑分差距超过5%，请仔细检查环境有没有变化。</p>



<span id="more-3104"></span>



<p>偏偏最近就是被是这个“非常稳定”的SPECcpu测试成绩折腾了很久。</p>



<p>事出同事发现某个SPECcpu2017测试组件bwaves（事实上它是浮点性能测试的第一个组件）的测试成绩有着非常明显的测试偏差，误差范围超过7% (取stdev/average即变异系数，下同)。仅针对bwaves且多套类似配置的主机上均无法复现。该主机为双skylake Xeon，搭配8根16G DDR4-2666内存，算是比较常见的服务器搭配。由于测试数据太多，只能让同事单独隔离那台主机翻来复去的跑bwaves试图找出规律。大约2周左右，居然找出完整最小重现规律来了：“当设定bwaves跑在仅跑在socket1（空闲出socket0）且以主机OS重启为节点；前两次测试bwaves的跑分会大幅低于平均值，第三四两次的结果会高于均值，之后测试成绩逐步抖动收敛到均值……”</p>



<p>摸不着头脑，不可理喻！讲道理作为一个软件工程师居然能相信会有这么妖的重现方式，居然不是“清缓存、重启、重装”的三板斧！自己远程登上KVM重装系统、重装应用程序，手工执行个几次，居然还真是100%复现。脱敏（与同机同配置16次结果均值的比，下同）之后的跑分数据如下，误差范围±7.94%：</p>



<table class="wp-block-table aligncenter"><tbody><tr><td><br></td><td><strong>round_1</strong></td><td><strong>round_2</strong></td><td><strong>round_3</strong></td><td><strong>round_4</strong></td><td><strong>round_5</strong></td></tr><tr><td>bwaves rate</td><td>92%</td><td>92%</td><td>111%</td><td>107%</td><td>98%</td></tr></tbody></table>



<p>8%的性能差距被形容起来好比说买了一盒一打装的鸡蛋拿回来结果发现少了一个<img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f61b.png" alt="😛" class="wp-smiley" style="height: 1em; max-height: 1em;" />，当然，相比其他“表现正常”的同配置主机，该主机的bwaves跑分实际只有55.63%，属于严重缩水。</p>



<p>花了大量的时间检查过各种可能的设置错误之后均无果而终，于是决定从bwaves的特性入手看看bwaves到底对什么样的系统配置敏感。得到的结果是内存带宽，于是采集了改主机在运行bwaves时的内存带宽变化情况，但得到内存带宽的变化真的很小，不足以证明为什么会有“低低高高中”的规律。</p>



<p>问题在于特别是在第二轮和第三轮之间到底发生了什么。</p>



<p>该主机有8&#215;16=128G内存，OS同样显示了该主机128G内存。由于NUMA开启，每个socket只能使用64G内存。我们为此主机设置的bwaves的测试需要<strong>实际占用</strong>10～12G内存空间。于是我调整了一下bwaves的测试脚本让它使用更多的内存空间。结果数据就成了“低高中中”。我已经了解问题的所在，要求同事检查下内存插槽，顿时恍然大悟。</p>



<div class="wp-block-image"><figure class="aligncenter"><img src="http://www.litrin.net/wp-content/uploads/2019/09/1200px-skylake_server_overview.svg_-1024x638.png" alt="" class="wp-image-3105"/><figcaption>Skylake xeon</figcaption></figure></div>



<p>Skylake这一代XEON CPU每个socket上有两个独立的内存控制器分管两块不同的内存空间，如上图所示内存控制器即上图中黄色的imc。每个skylake imc支持3通道，每通道2根内存，两个socket总计支持6个通道合12根内存。</p>



<p>和磁盘raid阵列类似，interleave的缘故通道越多性能越好。</p>



<p>但测试中的服务器由于成本考量，只安装了8根内存条合每个socket只安装4根。<br>问题来了，如果你在6根内存槽中选择4根的话你会怎么选？相信大多数人的选择是<strong>前四个</strong>，后面两个留空。这样的做法恰恰是错误的！对于skylake这类每个socket上即成了多个内存控制器的CPU来说，这样的话恰好让一个imc是3通道加持，另一个imc仅有1通道（3+1）。两个imc之间内存带宽不均衡性能自然不均衡。另外该主机的主板走线使得2+2对称插法非常不顺手，于是偷懒似乎成了理所当然。</p>



<p>重新移动了内存条之后（2+2），性能稳定，偏差范围±0.23%，平均分也匹配预期。</p>



<table class="wp-block-table aligncenter"><tbody><tr><td><br></td><td><strong>round_1</strong></td><td><strong>round_2</strong></td><td><strong>round_3</strong></td><td><strong>round_4</strong></td><td><strong>round_5</strong></td></tr><tr><td>bwaves rate</td><td>99.88%</td><td>99.88%</td><td>100.46%</td><td>99.88%</td><td>99.88%</td></tr></tbody></table>



<p>如果你一直看到这里你可能会觉得我好像还是没有解释为什么会有“低低高高中”的规律。</p>



<ol><li>在系统刚刚启动好之后，一般socket1上不会有过任务请求过大量内存空间，也就是说内存调度器在socket1上有大量可分配的连续空闲内存。socket0相对噪声更多，内存碎片化较socket1多。</li><li>不正确的内存插法导致了同一个socket上部分（1/4）内存地址的性能较预期值差。而整体的不平衡内存访问导致整体的性能无法提升。</li><li>当前两次分配bwaves内存时，恰好分配的内存地址大多都是在单通道imc上。Linux kernel的内存页回收机制并不积极的回收内存页（这里不展开讲Linux的内存页管理 buddy system和内存碎片的关系）。</li><li>第三次开始分配的地址空间已经转移到3通道imc上，性能会好很多。高于2+2的内存配置，表现出来的就是较高的性能。</li><li>所有连续内存页分配结束后kernel只能分配相对连续，宏观上导致后期的测试数据逐步趋于两者的平均值。</li></ol>



<p>我第二次测试的结果加大了程序内存大小，自然也加速了这个过程，不过真正触发这个妖异的现象我承认也是有运气的成分在内。而且运气更好的是，通过逆向分析测试数据，我找到了一个通过较低权限软件方式简易识别内存DIMM插法的可行性并实现成功。</p>



<p>考虑到类似的双skylake xeon服务器某家知名大厂居然有脑残的10根内存产品，要知道10无法整除4！于是特地去检查10内存主机的测试数据。怎么说呢？毕竟嘛2:3根1:3比起来还是更接近于1:1，只能说有<strong>类似情况的趋势</strong>，但没足够的理由认定这是问题，只能随他去了。</p>



<p>总结：</p>



<ul><li>在细节上面较真是没错的。确实会用螺丝刀的人就能学会装电脑，可谁知里面细节上其实大有讲究。</li><li>别为了省个几百块钱给几万块钱的东西埋下炸弹。</li><li>另，老外更习惯12进制的偏见是做实了。</li></ul>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/09/06/%e4%b8%80%e4%b8%aa%e6%b5%8b%e8%af%95%e6%80%a7%e8%83%bd%e4%b8%8d%e7%a8%b3%e5%ae%9a%e7%9a%84%e9%97%ae%e9%a2%98/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Linux的用户内存上限</title>
		<link>http://www.litrin.net/2019/08/30/linux%e7%9a%84%e7%94%a8%e6%88%b7%e5%86%85%e5%ad%98%e4%b8%8a%e9%99%90/</link>
					<comments>http://www.litrin.net/2019/08/30/linux%e7%9a%84%e7%94%a8%e6%88%b7%e5%86%85%e5%ad%98%e4%b8%8a%e9%99%90/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Fri, 30 Aug 2019 14:07:33 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3077</guid>

					<description><![CDATA[首先，提个问题：64bit x86 Linux中用户空间最大可以使用多少物理内存？根据Kernel的定义，这个 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/08/30/linux%e7%9a%84%e7%94%a8%e6%88%b7%e5%86%85%e5%ad%98%e4%b8%8a%e9%99%90/" class="more-link">继续阅读<span class="screen-reader-text">“Linux的用户内存上限”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>首先，提个问题：64bit x86 Linux中<strong>用户空间</strong>最大可以使用多少物理内存？根据Kernel的定义，这个值是64T，<a href="https://access.redhat.com/articles/rhel-limits#maximum-memory-2">Redhat官方</a>给的是12T，但含糊的说了64T的限制。那64bit x86的寻址位宽是多少？答：48bit，理论上应该是支持2&lt;&lt;48=256T才是，Linux kernel为什么会强制限定了一个64T的最大内存？</p>



<span id="more-3077"></span>



<p><em>避免复杂化，本文一律不谈大页内存；一律只针对当下主流48bit内存空间和IA32e地址扩展的x86_64 CPU。</em></p>



<p>还是先从逼死强迫症的48bit内存地址位宽说起吧。按道理讲，64bit的CPU，内存地址就应该是64bit/8byte。但这48bit地址是一个x86平台上很有名的历史包袱之一，其中的0～47位为物理内存地址，48～63这16位必须是与第47位保持一致。在编程的层面上表现为所有的内存指针的前16位都是0。X86_64本身并不是一个完全意义上的64位地址空间，考虑向下兼容，内存采用了IA32的扩展方式IA32e，这诡异的设定源自于IA32的多级页表。</p>



<p>先开始讲页表跳转。Intel SDM vol3 figure 4-8 给了一张图。 （4-9，4-10分别是3层转换的2M大页和两层转换的1G大页）</p>



<figure class="wp-block-image"><img src="http://www.litrin.net/wp-content/uploads/2019/08/屏幕快照-2019-08-30-下午8.41.16.png" alt="" class="wp-image-3079"/></figure>



<p>当前内存的管理是通过页表转换的模式实现的，64bit系统当前是4级（跳）页表。48bit带宽的前36bit分为4段，每段9bit页表，后12bit对应的正是一个内存页（即4K）大小。</p>



<p>又一个逼死强迫症的8bit+1bit，又是32bit时代的遗传病。这9bit类似于指针操作，不断地指向的下一级内存地址。亲身经历，为了调试一个底层的bug，当时一个会议室15个人的组了hackthon，在完全解读了转换算法的情况下，一个小时里愣是没有一个人通过人肉的计算成功的完成地址页表转换，可见算法之反人类。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/08/WechatIMG11-768x1024.jpeg" alt="" class="wp-image-3088" width="332" height="442"/><figcaption>摘自《Understanding the Linux Kernel, 3rd edition》Daniel P. Bovet, Marco Cesati </figcaption></figure></div>



<p>几十年前比尔盖兹大叔说过一句：“PC只要640K的内存就够了！”成为了他一生的黑点。当时的8088CPU只支持20bit的寻址0xFFFFF，正巧是一个8位和一个12位的组合。这48bit的强迫症种子从这个时候就埋下了。</p>



<p>等一下！20bit不明明是1M吗，为什是640K内存？——2位保留地址！8088将20bit中的两个bit作为保留地址，当时用来映射BIOS和外设。当时这两个bit带来的仅仅只是被这384K的划分的不连续空间，当这种历史包袱遗传到了现在的48bit，保留的地址空间范围一样存在。同样，48bit空间也要满足“两头顶格”的习惯，整个可用地址范围变成了0～0x7FFF FFFF FFFF和0x8000 0000 0000～0xFFFF FFFF FFFF两个不连续的地址空间上的的几个更加离散的小岛。以首位区分或者理解为正负符号，Linux Kernel使用“1”作为系统地址空间，使用“0”作为用户地址空间（小于47bit可分配给用户空间）。贴图来自SDM vol3 fuigure 3-3局部，实模式即8088模式也可以在此找到。</p>



<div class="wp-block-image"><figure class="aligncenter"><img src="http://www.litrin.net/wp-content/uploads/2019/08/屏幕快照-2019-08-31-上午7.15.23.png" alt="" class="wp-image-3084"/></figure></div>



<p>另一方面就是Linux kernel是用移位的方式确定用户可用内存地址空间范围，相比x86，Linux表现出了必须是2的n次方强迫症逻辑——既然已经注定凑不齐47bit（128T），甚至目前连64T内存的主机都没有，keep easy！那就索性再扣除一个bit，46bit = 64T。 mm.txt如是说：</p>



<blockquote style="text-align:right" class="wp-block-quote"><p>Current X86-64 implementations support up to 46 bits of address space (64 TB), which is our current limit. This expands into MBZ space in the page tables. </p><cite>&#8211;Andi Kleen(Intel.)</cite></blockquote>



<p>眼看着64T内存的主机也越来越近了，再加一级页表，48bit变57bit是一种最偷懒的办法（0b111001，比48还迫害强迫症患者），但如果考虑到云计算的普及，虚拟主机的应用越来越多的成为了主流，页表转换到了VM场景下还要增加额外的虚拟页表跳转，更多的开销已经无法接受了。看来日后只有大页才是趋势。</p>



<p>“向下兼容”这四个字让x86这个平台充斥着各种不可理解；Linux，每一行代码都有一个故事……</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/08/30/linux%e7%9a%84%e7%94%a8%e6%88%b7%e5%86%85%e5%ad%98%e4%b8%8a%e9%99%90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>离奇的CPU利用率</title>
		<link>http://www.litrin.net/2019/07/23/%e7%a6%bb%e5%a5%87%e7%9a%84cpu%e5%88%a9%e7%94%a8%e7%8e%87/</link>
					<comments>http://www.litrin.net/2019/07/23/%e7%a6%bb%e5%a5%87%e7%9a%84cpu%e5%88%a9%e7%94%a8%e7%8e%87/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Tue, 23 Jul 2019 00:09:16 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3061</guid>

					<description><![CDATA[接到一个黑盒的case：一套双志强的服务器上，运行一个典型的索引服务，通过taskset命令将改服务的CPU绑 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/07/23/%e7%a6%bb%e5%a5%87%e7%9a%84cpu%e5%88%a9%e7%94%a8%e7%8e%87/" class="more-link">继续阅读<span class="screen-reader-text">“离奇的CPU利用率”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>接到一个黑盒的case：一套双志强的服务器上，运行一个典型的索引服务，通过taskset命令将改服务的CPU绑定到socket1之后，理论上socket0应该没有工作负载，但实际上的结果是socket0非但有负载，而且对于一颗24core的CPU来说，CPU利用率接近100%——比socket1的负载还高！且更加头疼的是无论怎么对server端施加压力，serve的CPU利用率和性能不会有太大的提升。</p>



<span id="more-3061"></span>



<p>其实我在拿到这个case之后，看了这个描述之后对根本原因已经有了预估。但无奈是黑盒，没有办法从代码里求证，只能靠从监控数据上推论。</p>



<p>首先拿到的一个重要指标是IPC（instruction per cycle）。这个指标的定义是：每时钟周期执行的非空闲指令数。</p>



<p style="text-align:center">IPC = CPU_CLK_UNHALTED.THREAD/INS_RETIRED.ANY</p>



<p>利用perf可以直接抓取IPC。<br>Socket 0 IPC为0.08；Socket 1 IPC 为0.3。这意味着尽管Sokcet0的CPU utilization更高，但大多数的时间内Socket0仅仅只是执行一个“HALT”指令而已，大多数情况下这个指令理解为空指令就好了，不做任何有效的操作。所以公式中的这个“UNHALTED”很妙，只要不是“HALT”指令，都被记作“UNHALTED”，而如果是&#8221;.thread&#8221;，unhalted还要排除“MWAIT”指令数量。而有效指令在所有指令中的占比就是IPC。</p>



<p>问题来了：到底是什么原因导致的socket 0占用了大量的CPU时间去做没有意义的操作？第一反应就是等待操作，结合这个server的数据文件巨大（80G+），沿着IO方向去找不会错。这就牵扯到了Socket 上的PCIe bus的总带宽指标：</p>



<p style="text-align:center">PCIeBW_read = 4 * sum(UNC_IIO_PAYLOAD_BYTES_IN.MEM_READ) <br>PCIeBW_write = 4 * sum(UNC_IIO_PAYLOAD_BYTES_IN.MEM_WRITE)</p>



<p><em>注：<br>x4是因为PCIe的请求都是4byte（double words）对齐。<br>部分平台的 UNC_IIO_PAYLOAD_BYTES_IN被改名为UNC_IIO_DATA_REQ_OF_CPU。</em></p>



<p>这个指标也可以通过PCM之类的工具直接获取。测得PCIe的写带宽很低，K级别水平就直接忽略不计了，但PCIe读带宽超过400MB/s。经确认，该服务器仅有一个480G SATA SSD硬盘，考虑到主流的SATA SSD硬盘一般最大写带宽都在500MB/s以下，这个数据已经可以定位到遭遇到了磁盘IO瓶颈。是磁盘的访问瓶颈导致了服务性能无法提升。</p>



<p>好了，大致上问题定位了，那怎么解释Socket0的CPU利用率高于Socket1呢？</p>



<p>接下去就是如何区分CPU utilization的来源了，这又牵扯两个重要指标：</p>



<p style="text-align:center">CPUUtilization = CPU_CLK_UNHALTED.REF_TSC / TSC <br>CPUUtilization_kernel = CPU_CLK_UNHALTED.REF_TSC:SUP / TSC</p>



<p>分别是系统全局的CPU利用率和kernel的CPU利用率，当然两者的差代表的是User space的CPU利用率。不同于“.thread”，这里的“.ref_tsc”并不排除x86 SIMD中的&#8221;MWAIT&#8221; 指令。区分kernel态的方法则是看当前的指令的ring级别。其实通过系统工具vmstat以及top等命令都可以查看对应的sys/user CPU utilization读数。</p>



<p><em>重点：仅靠一个CPU的利用率指标并不能很好的反映出CPU的真实使用状况。</em></p>



<p>统计了Socket0的kernel CPU利用率为99.9%，结合前面的IPC结果推断socket0在内核态执行了大量的mwait用于做IO返回等待导致了CPU高负载的现象，从而断定了最初的猜测。而kernel态中大量的mwait(mwait本身就是一个ring0指令，即只能通过kernel调用)是由于用户态的密集系统调用请求。</p>



<p>逻辑链还原：</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/07/image.png" alt="" class="wp-image-3064" width="44" height="152"/></figure></div>



<ol><li>尽管所有的socket都可以安装PCIe设备，但在大多数的主板上，SATA控制器、集成网卡、USB等各种板载设备统统无脑连接到Socket0，实验中的服务器主板也不例外。这种设计的优势在于即便在只有一个socket0的产品配置时，板载的各种设备也不会受到影响，本身没有什么错。</li><li>Linux以及NUMA设定的优化配置，导致了系统在使用这些设备的时候，优先绑定Sokcet0处理请求。通过系统调用的方式打开文件句柄——比如本例的操作更是如此。尽管服务被绑死在了socket1上，但大量的系统调用还是依赖于socket0对其下属的板载SATA控制器下SATA设备的访问。</li><li>死就死在SATA的带宽是瓶颈。大量的文件访问无法及时返回数据，逐渐的越来越多的CPU时间就无意义的消耗在了“等待数据返回”上。这也就是为什么Socket0的CPU利用率奇高但IPC不高的直接原因。</li><li>IO是瓶颈，服务器性能自然提升不了，简单的方法是升级磁盘到NVMe，并下挂到socket1。</li></ol>



<p>PS：其实就是一个vmstat就能看出来的问题，我居然啰嗦了这么多……</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/07/23/%e7%a6%bb%e5%a5%87%e7%9a%84cpu%e5%88%a9%e7%94%a8%e7%8e%87/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>DCDC&#8217;19-NUMA的优势和陷阱</title>
		<link>http://www.litrin.net/2019/06/19/dcdc19-numa%e7%9a%84%e4%bc%98%e5%8a%bf%e5%92%8c%e9%99%b7%e9%98%b1/</link>
					<comments>http://www.litrin.net/2019/06/19/dcdc19-numa%e7%9a%84%e4%bc%98%e5%8a%bf%e5%92%8c%e9%99%b7%e9%98%b1/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Wed, 19 Jun 2019 04:21:43 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[服务器]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3037</guid>

					<description><![CDATA[去年的DCDC，我主要介绍了基于RDT技术对云计算场景做优化，转眼就一年过去了。2019年DCDC依旧还是Sa &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/06/19/dcdc19-numa%e7%9a%84%e4%bc%98%e5%8a%bf%e5%92%8c%e9%99%b7%e9%98%b1/" class="more-link">继续阅读<span class="screen-reader-text">“DCDC&#8217;19-NUMA的优势和陷阱”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>去年的DCDC，我主要介绍了基于<a href="http://www.litrin.net/2018/05/16/dcdc2018-%e6%b7%b7%e5%90%88%e9%83%a8%e7%bd%b2%e5%9c%ba%e6%99%af%e4%b8%8brdt%e7%9a%84%e5%ba%94%e7%94%a8/">RDT技术对云计算场景做优化</a>，转眼就一年过去了。2019年DCDC依旧还是Santa Clara、台北、上海、北京4处分会场。不同的是今年SC的presentation已经由我在美国的同事代领；台北今年取消了所有的云计算的专题；仅存京沪两地的演讲计划将在下周开始。照旧，这次分享下基于英文版本的内容(PS：不得不删除部分内容，整体略凌乱见谅！)。</p>



<span id="more-3037"></span>



<p><strong>背景</strong></p>



<p>从当前的路线图来看，不管是Intel还是AMD都推出了“单socket多die”的CPU设计，带来了更复杂的硬件拓扑；加上随着云计算的大行其道，CSP逐渐倾向于更高计算密度的服务器，比如4 Sockets。那是不是说只要把业务平切过去就能享受更多的die/socket带来的提升呢？个人了解，很多CSP还是倾向于UMA，但至少他们一致认为相比UMA，NUMA将会是主流，那我在机房简单地开启NUMA呢是不是就万事大吉了呢？</p>



<p>其实对于NUMA的优势这一部分的内容，本站已经陆陆续续的分享了很多，讲稿中主要介绍的就是SPECCPU2017的不同测试组件在<a href="http://www.litrin.net/2017/12/18/uma-numa%e4%b9%8b%e6%80%a7%e8%83%bd%e5%b7%ae%e5%bc%82/">NUMA和UMA的性能差异</a>。直接贴我自己的fp数据吧（注：非讲稿数据）：</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2017/12/UMA_NUMA_speccpu2006_results.png" alt="" width="591" height="389"/></figure></div>



<p>从全局上看，大多数的组件都能从NUMA上得到性能的提升，部分的测试甚至可以用“magnificent”来形容。</p>



<p>另：增加一次UPI通讯会增加~40ns（20%+）的时延开销；远端最大可测得内存带宽下降8%-21%。</p>



<p>在Skylake这一代server CPU，每个socket预留了3个UPI link ports，基于2个socket的Xeon 系统，当下的设计有2条UPI和３条UPI互联两种。每条UP能够承载的带宽可以通过如下公式进行计算。然而，即便是3条UPI承载的最大带宽68GB/s来说，也只能提供大约3个内存通道的带宽，况且在一个完整的系统中UPI还需要分担PCIe的额外带宽。对于密度更大的4socket 平台，3link/socket的设计仅能满足每两个CPU之间仅有1条UPI互联。不用细算也看得出，UPI带宽变得紧张起来，相比2S上还可以扭扭捏捏，4S上看来NUMA是不得不开了。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/06/image.png" alt="" class="wp-image-3038" width="345" height="92"/></figure></div>



<div class="wp-block-image"><figure class="aligncenter"><img src="http://www.litrin.net/wp-content/uploads/2019/06/image-1.png" alt="" class="wp-image-3039"/><figcaption>4 Socket System CPU connection topology </figcaption></figure></div>



<p><strong>陷阱1: 错误的内存-核心绑定策略导致的NUMA失效</strong></p>



<p>当下很多云计算系统的任务管理器会倾向于使用CPU绑定（CPU affinity）技术为不同的任务分配CPU，这样的又是是可以减少由于CPU频繁切换导致的性能损失。出于减少碎片化的目的，这样的系统往往习惯于连续分配CPU core。但一旦NUMA策略于CPU分配策略出现冲突，往往就会出现某些应用必须跨socket访问远端内存的错误设置。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/06/image-2.png" alt="" class="wp-image-3040" width="471" height="393"/><figcaption> 错误的内存-核心绑定策略导致的NUMA失效 </figcaption></figure></div>



<p><strong>陷阱2：错误的PCIe设备部署</strong></p>



<p>每个CPU socket都有自己的PCIe控制器接口，但大多数的服务器主板不会分开标记PCIe插槽属于某个CPU。图例中假定APP工作在Socket3，有大量的网络访问<strong>不得不</strong>通过UPI绕行到Socket1——可能是IP/网络的设定原因，或者socket本身根本没有独立的网卡。在这种情况下，UPI，特别是socket0-socket3之间的UPI会承担大量的不必要压力，影响到正常的UPI使用。在PCIe Gen4/5之后，PCIe设备带宽提升以后，这种错误的设置产生的影响会更加恶劣。</p>



<p><em>注：本例以网卡为实例，但任何的PCIe设备包含硬盘均有类似的陷阱。</em></p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/06/image-3.png" alt="" class="wp-image-3041" width="361" height="272"/><figcaption> 错误的PCIe设备部署</figcaption></figure></div>



<p><strong>陷阱3：特殊的应用</strong></p>



<p>部分应用的特性决定了增加内存带宽并不会过多的影响到性能，减少内存时延反而会大幅提升性能，且在某些场景下势必多个socket必须访问同一段地址空间（比如全局锁）。单纯的启用NUMA，导致了内地址空间的相对隔离，时延的原因导致性能会由于NUMA的启用而下降。</p>



<p><strong>陷阱4:NUMA auto-balance</strong></p>



<p>Kernel 3.8之后，系统会允许任务在多个NUMA节点之间自动迁移。 Kernel的具体开关</p>



<p style="text-align:center"><em>sysctl -w kernel.numabalancing={0,1} </em></p>



<p>问题在于这个过程对于用户来说得到的结果并不是可预期的，很多实例上的reclaim和swap的操作都与这个开关有关。</p>



<p>总结：优化NUMA架构的的主机并不是单单开启一两个开关就够了，NUMA的优化与否取决于多个子系统的使用情况：</p>



<ul><li>CPU 系统，CPU的使用率是否够高，LLC 的cache是否会有潜在的争抢以及AVX512这类的重指令是否会有频率甚至电力方面的争抢等。</li><li>内存系统，内存的带宽和时延，内存控制器是否饱和，应用程序是否有内存锁等。</li><li>UPI 链接：带宽的消耗等。</li><li>PCIe 设备的部署情况等。</li></ul>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/06/19/dcdc19-numa%e7%9a%84%e4%bc%98%e5%8a%bf%e5%92%8c%e9%99%b7%e9%98%b1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>高性能编程的困境</title>
		<link>http://www.litrin.net/2019/06/05/%e9%ab%98%e6%80%a7%e8%83%bd%e7%bc%96%e7%a8%8b%e7%9a%84%e5%9b%b0%e5%a2%83/</link>
					<comments>http://www.litrin.net/2019/06/05/%e9%ab%98%e6%80%a7%e8%83%bd%e7%bc%96%e7%a8%8b%e7%9a%84%e5%9b%b0%e5%a2%83/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Wed, 05 Jun 2019 01:09:10 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3026</guid>

					<description><![CDATA[之前收到公司一个大牛的PPT，里边详细分析了一个典型的代码段在短短2~3秒钟时间内的内存访问特征。内容翔实紧凑 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/06/05/%e9%ab%98%e6%80%a7%e8%83%bd%e7%bc%96%e7%a8%8b%e7%9a%84%e5%9b%b0%e5%a2%83/" class="more-link">继续阅读<span class="screen-reader-text">“高性能编程的困境”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>之前收到公司一个大牛的PPT，里边详细分析了一个典型的代码段在短短2~3秒钟时间内的内存访问特征。内容翔实紧凑，说的有理有据。技术类PPT的惯例，文中有几页折线图表达了整个过程中每一细微时间粒度上的内存带宽变化，扫了一眼细细密密的横坐标，一方面感慨大牛的数据之精确，另一方面忽然有了放challenge的冲动。</p>



<span id="more-3026"></span>



<p>立即回邮件，请教如此细粒度的时间切片数据是如何抓取的。答，自己修改了<a href="https://github.com/opcm/pcm">PCM</a>的源码，调小了采样间隔到50us（微秒）。再回邮件要求上代码，大牛附上了源码。我一下子推翻了整个PPT。</p>



<p>PCM是一个CPU/硬件级别事件监控软件。它通过硬件上集成的event counter（事件计数器）的读数变化统计需要关注的事件发生次数。过程有点像抄水表：本月用水量=这月抄表数-上月抄表数。对于PCM的实现，这个过程就变成了：读数1-&gt;sleep-&gt;读数2-&gt;计算两次读数的差-&gt;输出。</p>



<p>原本这两次读数之间的sleep都是秒级别，可为什么到了50us级别这个过程就会出现问题呢？这就要从当下的<strong>非实时</strong>操作系统实现来说。</p>



<p>从操作系统context switch上下文切换的过程来看。Linux是一个“时分多路”操作系统，意味着同一个CPU核心在不同的时间上运行的进程或线程是不同的。在多个进程或线程之间切换的过程就叫上下文切换，而sleep或者写屏、写磁盘调用将会触发上下文切换。<br>每一次都会都会触发吗？不一定， /proc/sys/kernel/sched_min_granularity_ns  文件约定了最小的调度间隔，这个间隔大多都在ms(毫秒）级别。且下一次切回到本进程至少也要再等一次最小调度时间间隔。相比50us的取样周期，3个数量级的差异直接导致了采样数据的不可信。</p>



<p>如果你有过编程基础，你可能会觉得解决这个问题的方法只要增加一个时间校准， 减少写屏、落盘就好了，然而问题又来了。</p>



<p>时间校准上如果采用大多数的实现，都是简单的调用glibc中 clock_gettime 。这条指令可以通过系统调用直接读取硬件时钟的数据，纳秒级别的精度按理说已经是足够高了。不过里边还有一个坑。<br> /sys/devices/system/clocksource/clocksource0/current_clocksource 是当前的系统默认时钟源的设定，有几种可用的时钟可用，比如我这台主机可以使用 TSC（Time Stamp Counter），HPET（High Precision Event Timer），ACPI PM Timer（ACPI Power Management Timer）  3个时钟源，默认使用的是TSC。要知道3种硬件时钟的调用方式是不同的，调用时延也是10倍以上的。TSC代价较低，简单的指令返回，100 cycle左右，大约60ns可以完成调用；而hpet则是通过内存映射方式的调用，至少2倍内存延时，超过500ns；ACPI？好吧，ACPI需要通过PCIe bus完成内存地址映射，这个过程相当于读取SSD，微秒级别的操作了！<br>坑的是在不同的主机版本、内核设置上，硬件时钟源的设定是不同的。据说，Kernel优先使用TSC，一旦kernel“觉得tsc有问题”，比如从刚从休眠唤醒或者调整频率之后，就会切换到HPET时钟。</p>



<p>那么另一条路呢？<br>对于输出数据以50us的间隔读写对内存，在这个例子中意味着超过2GB/s的带宽消耗，几乎是一根DDR4-2666带宽的10%。加上这个数据本身就是针对内存性能的分析，如此的误差导致的是数据不可用。如果你对于现有的多核x86架构有所了解的话，一个socket上的多个core是共享内存控制器的，相互的带宽抢占是不可避免的。</p>



<p>最后，你要是接触过event count特别是uncore部分的event counter，你就会知道真正的问题在这里：计算内存带宽需要UNC_M_CAS_COUNT.RD之类的内存读写计数器。这些计数器的访问事实上是通过一个虚拟的PCIe设备进行映射的。说到PCIe设备，之前已经提到了，这是一个需要用us级别访问的通道……</p>



<p>话说到这里，你也许会问我的最终实现方法，肯定不是通过uncore了。这里不做阐述，欢迎私聊！</p>



<p>其实挺想吐槽的，一个常年研究硬件架构的大牛尚且在关键的数据采集上犯了这么大的错误。这些年，所谓高性能高并发、微秒级响应、C25K甚至C100K 困境为啥在某些人嘴里仿佛就成了hello word级别难度？传统上对于一个最小调度时间片是3ms的话意味着单核心只能响应300连接每秒，就算对于100个CPU core的主机来说，光是建立连接这30K的TCP链接就是系统的极限了，更别谈什么数据读写处理。</p>



<p>从CPU的眼光讲讲时间的概念，给大家一个概念。</p>



<ul><li>对于2.0GHz的CPU来说，一个cycle大约是0.5ns。</li><li>访问内存 &gt;150ns，这是300个cycle。</li><li>SSD或者网卡的读取 &gt;150us， 这是 300000个cycle。</li><li>一次内网微服务请求 5ms，这是10000000个cycle。</li><li>一次网页响应 1.5s 这是30000000000个cycle</li><li>……</li><li>光速在一个cycle之内只能传输15厘米。</li></ul>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/06/05/%e9%ab%98%e6%80%a7%e8%83%bd%e7%bc%96%e7%a8%8b%e7%9a%84%e5%9b%b0%e5%a2%83/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>前沿、突出部</title>
		<link>http://www.litrin.net/2019/05/23/%e5%89%8d%e6%b2%bf%e3%80%81%e7%aa%81%e5%87%ba%e9%83%a8/</link>
					<comments>http://www.litrin.net/2019/05/23/%e5%89%8d%e6%b2%bf%e3%80%81%e7%aa%81%e5%87%ba%e9%83%a8/#comments</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Thu, 23 May 2019 02:53:11 +0000</pubDate>
				<category><![CDATA[站长的blog]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=3016</guid>

					<description><![CDATA[这些年，作为某芯片企业的软件工程师，我一直负责技术支持某家国内企业做云计算方向优化。也就是在两周之前，合作方忽 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/05/23/%e5%89%8d%e6%b2%bf%e3%80%81%e7%aa%81%e5%87%ba%e9%83%a8/" class="more-link">继续阅读<span class="screen-reader-text">“前沿、突出部”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>这些年，作为某芯片企业的软件工程师，我一直负责技术支持某家国内企业做云计算方向优化。也就是在两周之前，合作方忽然临时取消了所有的例会，问起原因也是支支吾吾。不料直到上周，后知后觉的我才明白到底发生了什么。</p>



<span id="more-3016"></span>



<p>兴许对于项目而言，这直接意味着“不可控因素”的介入，意味着项目无法结束。从个人角度说，充其量影响到升职加薪之类所谓小人物的“career path”而已。然，CCTV-6临时更换的节目单上，这个突发的事件已经跟立国的那场战争相提并论了起来。</p>



<p>我终于第一次感受到了前线，而且是在双方各自突出部刚刚交火的味道。</p>



<p><strong>其一</strong> 所谓科技依赖度</p>



<p>这些年，老老少少都知道了互联网，知道了手机支付。快递小哥的忙碌，小屏幕上的光怪陆离都是各个互联网巨头的具象化体验。逐渐逐渐的，我们每个人都成为了网上的一个终端而已。然，并非危言耸听，这一切可能都会因为某天半夜某个狂人的一个签名而瞬间崩塌。</p>



<p>那个靠着不断挤垮实体商业的公司；那家靠着盗版和卖假药发家的公司；以及那家靠着学生中饭钱发家的公司。在夺取了绝对的垄断地位之后，建立了一个虚假的、足以欺骗我们的“更加可靠的信息服务”。他们真的有足够的能力去应对吗？在“赶英超美”的口号再次此起彼伏的时候，我们真的没有软肋了吗？相比那场立国之战，我们当下已经有了祖辈们不敢想的软硬实力，但这些软硬实力某种角度上讲恰恰授人以柄。</p>



<p><strong>其二</strong> 所谓盈利能力</p>



<p>诚然，每个人都有贪欲，渴望赚快钱甚至不劳而获。互联网作为当下最成功的印钞机器，不断的造就快钱神话。但作为互联网基础命脉的芯片、半导体行业却是重资产行业。不会有人愿意去做这样一个高投入、高风险、低回报的项目。回头说如果说国内芯片行业的转机在哪？很讽刺的是前些年挖比特币的时候，遍地开花的ASIC研发能力还是世界领先的。</p>



<p>出于融资上的便利，我们习惯于说“互联网行业的”某某企业，而大洋对面则都称自己是“高科技行业的”。这恰恰体现了对于科技、对于创新、对于自己研发实力的两种不同态度。同样见识过不止一家“互联网”公司，自打从还没搞清自己能做什么开始起，张口闭口的说要纳斯达克上市，似乎不这么说就成了low B一只。言外之意就是：“老子一不为赵，二不为李，单为这一个钱字！” 如此的浮躁谈什么创新力驱动？仅仅依靠那种“风口上猪会飞”的错觉吗？很多的甚至于说某些著名企业的成功，可能只是因为合适的时候搭上了国际资本的顺风车而已，只要你不是司机，你永远受制于司机！能让你上车的人同样也有随时让你下车的权利。况且备胎这种东西，是绝不会被允许带上车的……</p>



<p>最后，如果你看到过“In God We Trust”这句话，请注意G是大写，而且前置定语是单数。意味着这种类型的冲突从一开始就是注定的，希望备胎是全尺寸的，而且型号齐全。另，只写这么多。</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/05/23/%e5%89%8d%e6%b2%bf%e3%80%81%e7%aa%81%e5%87%ba%e9%83%a8/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>从核心性能不一致到sub-numa</title>
		<link>http://www.litrin.net/2019/03/13/%e4%bb%8e%e6%a0%b8%e5%bf%83%e6%80%a7%e8%83%bd%e4%b8%8d%e4%b8%80%e8%87%b4%e5%88%b0sub-numa/</link>
					<comments>http://www.litrin.net/2019/03/13/%e4%bb%8e%e6%a0%b8%e5%bf%83%e6%80%a7%e8%83%bd%e4%b8%8d%e4%b8%80%e8%87%b4%e5%88%b0sub-numa/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Wed, 13 Mar 2019 00:23:58 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=2996</guid>

					<description><![CDATA[既然都是9102年了，云计算早就成了服务器的主要任务，就如同我之前说过的：“那种期待一个应用就把一台主机塞满的 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/03/13/%e4%bb%8e%e6%a0%b8%e5%bf%83%e6%80%a7%e8%83%bd%e4%b8%8d%e4%b8%80%e8%87%b4%e5%88%b0sub-numa/" class="more-link">继续阅读<span class="screen-reader-text">“从核心性能不一致到sub-numa”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>既然都是9102年了，云计算早就成了服务器的主要任务，就如同<a href="http://www.litrin.net/2017/10/31/%e6%b7%b1%e6%8c%96numa/">我之前说过</a>的：“那种期待一个应用就把一台主机塞满的时代是回不来了！”不同于服务传统的称之为“科学计算”的模式，云计算的业务场景要求CPU的隔离性更优于性能，尽可能的各个core之间是搭积木似的即可随意打通（单虚机多CPU）又要互不干扰的。Xeon为代表的服务器CPU给出的主流解决方式是单socket，更多核心。</p>



<span id="more-2996"></span>



<p>PS:如果对NUMA概念比较模糊的话，建议阅读本站几篇NUMA的帖子:</p>



<p> <a href="http://www.litrin.net/2014/06/18/linux%e7%9a%84numa%e6%9c%ba%e5%88%b6/">Linux的NUMA机制</a><br><a href="http://www.litrin.net/2017/10/31/%e6%b7%b1%e6%8c%96numa/">深挖NUMA</a><br><a href="http://www.litrin.net/2017/12/18/uma-numa%e4%b9%8b%e6%80%a7%e8%83%bd%e5%b7%ae%e5%bc%82/">UMA-NUMA之性能差异</a></p>



<p></p>



<p>更多的核心带来更大面积的CPU芯片，而更远的传输距离势必将会带一个工程上的问题：核心之间的通讯 （C2C）以及内存访问（C2M）的时延问题。且不说在CPU时钟周期以纳秒为单位的时代，接近光速的信号电流传输速度事实上已经成为了一面看得见摸得着且随时可以撞上的墙(<em>c=30cm/ns</em>)。更要命的是，同样是远近的不一致性，导致了同一块CPU上局部核心之间存在由于时延不同带来的性能差异，即位置决定性能。</p>



<ul><li>C2C：各个CPU core之间需要通过L3同步数据以及保持L2缓存以下的一致性问题。</li><li>C2M：core没有自己的内存控制器，所有的core共享的是socket上少数几个内存控制器。</li></ul>



<p>如何设计c2c,c2m的通讯结构成为了一个阻碍CPU核心数增加的难题。</p>



<p>最初，Xeon最早给出的设计是一个双线环网结构，有点类似于上海地铁4号线。每个CPU core是一个ring stop，此外内存控制器，QPI，PCIe控制器各有一站。 没错！顾名思义这里的stop真的就是地铁站的stop概念。“乘5站”的核心自然比“乘1站”的性能差。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="https://en.wikichip.org/w/images/4/48/E5_v4_LCC.png" alt="E5_v4_LCC.png (533Ã758)" width="267" height="379"/></figure></div>



<p>环网系统最大的缺点就是，随着核心数量的增加，“最后一站”带来的时延会越发明显，不具备无限扩张的能力，于是在更多核心的版本上出现了两个环网的设计。每个环网有自己的内存控制器，同时为保持整个系统的一致性，两个环网之间又增加了两个“换乘站”用于环网之间的数据交换。</p>



<p>那么，问题来了，内存要求强一致性，两个内存控制器访问的内存地址是相互独立的，本地的core需要访问远程内存的话必须要经过“换乘站”，而换乘站为控制流量很可能要动用队列，时延会有显著的放大，时延跟CPU的核心性能直接挂钩。这跟NUMA面临的问题是一致的。这个时候解决这种不一致的方式被称为cluster-on-die（COD），但是sub-numa也就是在同一个核心上细分两个NUMA节点的设计呼之欲出。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="https://en.wikichip.org/w/images/f/f7/E5_v4_HCC.png" alt="" width="661" height="384"/></figure></div>



<p>题外话：如果核心数介于“一个环太多，两个环太少”时会有怎样的设计，那你听说过上海地铁3号线和4号线的关系吗？够随意了吧！这也就是为什么没有在v4这一代推出sub-numa的原因之一吧。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="https://en.wikichip.org/w/images/f/f6/E5_v4_MCC.png" alt="" width="493" height="381"/></figure></div>



<p>双环的结构实际上已经走到了尽头，从结构上推算就能看的出所谓3环及以上的设计是不可能实现的。对更多核心数的要求不会停止，于是在Skylake这一代（V5）终于推翻了环网的结构，取而代之的是更为复杂的mesh。</p>



<p>Mesh结构更像是互联网通讯的模式，数据的发送需要一系列的路由跳转从出发点到目的地。尽管内存控制器MC已经放置在了整块CPU die居中最外侧的位置，但对应的时延还是存在一致性问题，C2M这边离内存控制器较近的core会享受到更低的时延，较远的core就会有较高的时延；c2c也有类似的问题，牵扯另一更为复杂的设计，不提。</p>



<p>于是sub-NUMA的技术被正式推出。每个内存控制器只负责离自己较近的14个core，保证所有的c2m访问都在6跳以内以控制各种访问时延。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="https://en.wikichip.org/w/images/a/a2/snc_clusters.png" alt="" width="460" height="347"/></figure></div>



<p>之后的问题又来了，所谓的IA架构，I和A两家都不约而同地在之后的服务器CPU中采用“胶水设计”（A: Naples, I: CooPer Lake），即在一个socket上封装两个以上的die，把传统意义上的CPU看成一块扩展电路板。c2c或者说die-to-die的时延就会变得更加明显（且不可理喻）。</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="https://en.wikichip.org/w/images/0/0a/amd_naples_mcp.png" alt="" width="288" height="223"/></figure></div>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/03/image-1.png" alt="" class="wp-image-2997" width="295" height="189"/></figure></div>



<p>下一步怎么玩呢？谁知道呢？</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/03/13/%e4%bb%8e%e6%a0%b8%e5%bf%83%e6%80%a7%e8%83%bd%e4%b8%8d%e4%b8%80%e8%87%b4%e5%88%b0sub-numa/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Hyper Thread超线程的种种</title>
		<link>http://www.litrin.net/2019/02/22/hyperthread%e8%b6%85%e7%ba%bf%e7%a8%8b%e7%9a%84%e7%a7%8d%e7%a7%8d/</link>
					<comments>http://www.litrin.net/2019/02/22/hyperthread%e8%b6%85%e7%ba%bf%e7%a8%8b%e7%9a%84%e7%a7%8d%e7%a7%8d/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Fri, 22 Feb 2019 05:25:14 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=2977</guid>

					<description><![CDATA[说起HT技术，这早就是15年以前的技术了，当下主流的X86 CPU就没见过不支持HT技术的。简而言之，HT技术 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/02/22/hyperthread%e8%b6%85%e7%ba%bf%e7%a8%8b%e7%9a%84%e7%a7%8d%e7%a7%8d/" class="more-link">继续阅读<span class="screen-reader-text">“Hyper Thread超线程的种种”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>说起HT技术，这早就是15年以前的技术了，当下主流的X86 CPU就没见过不支持HT技术的。简而言之，HT技术就是一个把物理CPU core虚拟成多个逻辑CPU并允许并行执行指令的功能。之所以写这篇文章主要是回答几个常见的问题：</p>



<ul><li>为什么有HT技术，为什么HT只有单核心双线程？</li><li>HT和真实的core到底有什么区别？</li></ul>



<span id="more-2977"></span>



<p>首先第一个问题：X86到底属于复杂指令集CPU CISC（Complex Instruction Set Computer）还是精简指令集CPU RISC（Reduced Instruction Set Computer）？</p>



<p>这个问题还是有点“意识形态”的味道。RISC的指令集简单精练，比较容易提升CPU频率从而提升性能。这是一把双刃剑，一方面过于简单的指令意味着更高的开发、调优成本，没有足够经验丰富工程师支持的CPU平台注定吸引力不足。反观CISC这边，一切都是相反的——难以快速地提升硬件性能，却有大(lian)量(jia)的足够经验的工程师。</p>



<p>于是当今的X86搞定了一个相对骑墙的做法：用户层面依然保留着CISC那冗长且强大的指令集，增加了一个Front-end，将CISC翻译成RISC跑在底层电路上。这种方法一方面降低了软件工程师的门槛，另一方面也同时保持住了x86沿用至今40多年的向下兼容指令集或者说历史包袱。用户层的代码通过编译器譬如C/C++得到的CISC指令集代码称为机器码，而内部的RISC指令集则称为微码。这里要指出的是，“升级微码”事实上只是升级了从机器码到微码的编译器，并不是直接字面意义上修改什么指令。<br>一句题外话，跟X86作为CISC的扛把子一样，ARM作为RISC界的扛把子也在RISC之上实现了部分的CISC的指令集，殊途同归。——可笑的是这两家还是市场上的冤家，只是因为现在实在找不出其他家的产品可以达到这个市场占有量了。</p>



<p>说了半天，貌似这些指令集的事情跟HT一点关系也没有，其实不然。</p>



<p>CISC的指令比RISC重很多，借鉴了工业化流水线的思路，一条CISC可以被翻译成若干RISC指令。于是CPU内部处于加速指CISC速度的原因采用了多种技术：</p>



<ul><li>集成了多个处理单元执行不同的RISC，达到了并行化RISC的目的。</li><li>RISC微码经过解耦之后，支持乱序执行，执行时间规划，分支预测等时间统筹方法。</li></ul>



<figure class="wp-block-image"><img src="http://www.litrin.net/wp-content/uploads/2019/02/屏幕快照-2019-02-22-上午11.47.17.png" alt="" class="wp-image-2979"/><figcaption>X86 Architecture Execution Engine</figcaption></figure>



<p>上图是某代X86平台执行引擎的示意图。 scheduler是负责时间规划的调度器，相当于企业的销售部门。有0-7总共8个端口并发执行微码，端口在这里可以理解为工厂的车间。如果留意到端口之后的处理单元的话，你会发现不同的处理单元的数量是不一样的。不同配置的处理单元可以理解为各个车间职责的不同。比如INT ALU整数加减单元就大于INT MUL整数乘单元，这是综合了实现复杂度、处理时间、指令统计占比等多重考虑的结果。同时，理论上多条指令只要没有依赖性，存在空闲的执行模块就可以并行操作。这就好比一个流水线上生产的某一个通用螺丝，它本身并不在意会被用在产品A还是产品B上。</p>



<p>在OS层面上，线程的实现本身就是软件上降低依赖性，提升并发度的方法。而“假装自己是两个处理器”是最简单的硬件解耦，于是HT技术就这么产生了。首先解码逻辑核心1的机器码，送入执行单元，等待结果的同时解码逻辑核心2的机器码，如果出现的空闲的执行单元恰好是逻辑核心2的微码需要的，就直接送入执行单元不必一定要等待逻辑核心1的代码完全执行完毕。这一来一去，两个逻辑核心变得相对独立了。</p>



<p>回答另一个问题，决定一个物理核心的HT数量有几个限制条件：</p>



<p>执行单元的限制，从上面的介绍你应该有这个概念。假设一个物理核心上的两个逻辑核心都抢占某个处理单元，那逻辑上的“并发”就无从谈起。</p>



<p>执行时间的长短，即便是等价于RISC指令的微码，每个微码的执行时间也是长短不一的，比如ALU加减法和MUL乘法的门电路实现就比DIV除法的门电路实现简单的多，一条除法运算的时间足够做几条加法计算的。并发在这个时候就是有意义的，如果所有微码的执行时间都是一致的，“共线生产”的方式就变得低效率，执行时间规划和分支预测就意义不大了。</p>



<p>还有就是，当前2HT的设定还考虑到了Front-end的解码能力和CPU的占空比，这是相对宏观的设定。即便在当下的L1缓存下，CPU访存照样还有3个cycles左右的时延，这个过程中完全可以执行多条其他逻辑核心的指令。</p>



<p>以上可知，HT的初衷是“不让RISC执行单元空闲下来”，而讽刺的是为了提升HT性能，近几年冗余的RISC执行单元却越来越多。这似乎成了x86平台历来的诅咒。从ALU的数量上以及当前指令中front-end耗时占比来看（推荐我的<a href="http://www.litrin.net/2018/02/14/top-down%e6%80%a7%e8%83%bd%e5%88%86%e6%9e%90%e6%a8%a1%e5%9e%8b/">另一篇帖子</a>），HTx4 也似乎不是实现上的问题。</p>



<p><br></p>



<blockquote class="wp-block-quote"><p> <br><em>牛顿：我看的远，是因为我站在巨人的肩膀上！<br>X86 CPU架构师：我看的不远，是因为巨人站在我肩膀上……</em> </p></blockquote>



<p>补充：</p>



<p>找到AMD zen+ 的架构图，尽管和上面Intel有不同地方，比如dispath相较scheduler是包含在front-end中的；整数计算和浮点计算相互分离的设计；一堆回流通道等。但对于冗余RISC执行单元，任务分发等操作还是完全一致的。</p>



<figure class="wp-block-image"><img src="http://www.litrin.net/wp-content/uploads/2019/03/image-1024x494.png" alt="" class="wp-image-2993"/></figure>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/02/22/hyperthread%e8%b6%85%e7%ba%bf%e7%a8%8b%e7%9a%84%e7%a7%8d%e7%a7%8d/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Linux CFS调度器</title>
		<link>http://www.litrin.net/2019/01/29/linux-cfs%e8%b0%83%e5%ba%a6%e5%99%a8/</link>
					<comments>http://www.litrin.net/2019/01/29/linux-cfs%e8%b0%83%e5%ba%a6%e5%99%a8/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Tue, 29 Jan 2019 08:21:09 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[算法]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=2950</guid>

					<description><![CDATA[一直没有写过关于Linux内核调度器的内容，这几天被问起，简单的讲了讲，收到一堆challenge，这次决定做 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/01/29/linux-cfs%e8%b0%83%e5%ba%a6%e5%99%a8/" class="more-link">继续阅读<span class="screen-reader-text">“Linux CFS调度器”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>一直没有写过关于Linux内核调度器的内容，这几天被问起，简单的讲了讲，收到一堆challenge，这次决定做一个通篇总结方便自己整理思路。<br>要说Linux2.4和2.6最大的差异就在于CFS调度器的引入。CFS是 Completely Fair Scheduler 的缩写。不过讲真话，个人并不完全认同“完全公平”调度是这个算法的本意，如何裁决资源抢占（preempt，字面上是优先权）才是这个调度器的本意。</p>



<span id="more-2950"></span>



<p>对于时分多任务操作系统来说，可以理解为调度器维护着一个任务池，确定某一时刻CPU到底应该执行哪个任务。简单粗暴一点的调度器往往就是一个round robin，也就是分配相同的时间间隔，大家简单轮流使用CPU。很显然，这种调度是最公平的，但无法满足复杂场景下的调度，于是就有了CFS模型。</p>



<p>至于调度，本质上是时间片管理，CFS的模型主要的改进是在round robin采用的真实运行时间片的基础上实现了一个虚拟运行时间的概念vruntime。每个线程（这里理解为系统调度的最小单位，下同）都有一个vruntime值，具体算法为：</p>



<p style="text-align:center"><strong>vruntime= 权重占比 * 运行完成的时间片 * 每时间片时常</strong></p>



<p>这里的权重比是nice 0 的权重和线程权重的比值，nice 0权重默认为1024。cgroup/cpu的键值cpu.shares包含的就是这个值，调整这个值可以改变vruntime的计算方式。</p>



<pre class="wp-block-preformatted">[root@localhost proc]# cat /sys/fs/cgroup/cpu/cpu.shares<br> 1024</pre>



<p>得到了这个vruntime之后，系统将会根据每个线程的vruntime排序，vruntime最小的线程则会最早获得调度。而一旦vruntime的次序发生变化，系统将尝试触发下一次调度。也就是说调度器尽可能的保证所有线程的vruntime都一致，而权重高的县城vruntime提升的慢，容易被优先调度；权重低，同样的时间上vruntime上升的快，容易被轮空。一个进程的vruntime可以通过/proc/&lt;PID&gt;/sched中的se.vruntime选项查看</p>



<pre class="wp-block-preformatted">[root@localhost 1352]# grep vruntime /proc/1352/sched<br> se.vruntime : 1258213807.999425</pre>



<p>这个时候，有个比较典型的例子就是当系统中存在大量vruntime相似的线程之后，类似多米诺效应，线程调度将会被过于频繁的触发，这明显不合理！于是就如何定义“vruntime触发调度”时，CFS引入了一个阈值，即如果前后两个线程vruntime保持在一个阈值之内，系统不会触发调度。而这个阈值大小就是最小的调度时间片。<br></p>



<p>此外，CFS本质上是CPU运行时间导向的调度，这就有了另一个规则：对于sleep/IO这类的操作，由于相对来说并不占用过多资源，vruntime并不会被马上结算，仍会保持最初的vruntime。 跟到这里，你可能马上出现了一个头脑实验：一个线程A，初始值小但一直sleep，所以vruntime始终不增长；线程B，vruntime初始较大重始终排队等待，这种策略就变得不可理喻了。 所以系统在该线程被重新唤醒之后会重新计算vruntime，vruntime将取当期线程的vruntime和当前系统内最小vruntime-阈值这两个值中的最大值。也就是说如果当前系统中有两个及以上线程，当sleep/IO线程被唤醒之后，无论如何该线程将无法获得第一优先调度，最好的情况也要等当前最小vruntime接受调度之后再次触发调度。</p>



<p>另外，在cgroup的目录下还有两个跟CFS调度有关的设定：cpu.cfs_quota_us，cpu.cfs_period_us。 这两个值确定的是CPU时间的占空比。cpu.cfs_period_us是调度的周期，而cpu.cfs_quota_us是在这个调度周期内绝对CPU时常，单位都是微秒（us)。</p>



<p>cpu.cfs_quota_us / (cpu.cfs_period_us*CPU个数) = 线程CPU utilization</p>



<p>总结一下，如果用上下文切换的方式评价调度器的差异性：</p>



<ul><li>更多的线程分配到更少数量的CPU core上（不区分物理core和逻辑core），上下文切换的次数会增多。如果各个线程的优先级一致，足够多的线程分配最终的结果就是线程的切换频率等于vruntime阈值。</li><li>不同的优先级调度只在需要多线程切换时才有效，即便系统中只有一个优先级很低的线程，系统仍有可能达到full utilization（满负载）。</li><li>CPU share的方式采用了占空比来控制cpu的utilization，跟上下文切换次数无关。</li></ul>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/01/29/linux-cfs%e8%b0%83%e5%ba%a6%e5%99%a8/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>LLC缓存一致性保障原理</title>
		<link>http://www.litrin.net/2019/01/02/llc%e7%bc%93%e5%ad%98%e4%b8%80%e8%87%b4%e6%80%a7%e4%bf%9d%e9%9a%9c%e5%8e%9f%e7%90%86/</link>
					<comments>http://www.litrin.net/2019/01/02/llc%e7%bc%93%e5%ad%98%e4%b8%80%e8%87%b4%e6%80%a7%e4%bf%9d%e9%9a%9c%e5%8e%9f%e7%90%86/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Wed, 02 Jan 2019 10:59:38 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=2933</guid>

					<description><![CDATA[如果大家了解一点CPU的知识，应该能够了解到当前的x86架构的CPU都通过多级缓存实现内存的快速访问。目前较为 &#8230; <p class="link-more"><a href="http://www.litrin.net/2019/01/02/llc%e7%bc%93%e5%ad%98%e4%b8%80%e8%87%b4%e6%80%a7%e4%bf%9d%e9%9a%9c%e5%8e%9f%e7%90%86/" class="more-link">继续阅读<span class="screen-reader-text">“LLC缓存一致性保障原理”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>如果大家了解一点CPU的知识，应该能够了解到当前的x86架构的CPU都通过多级缓存实现内存的快速访问。目前较为流行的做法是3级缓存的设置，L1/L2为每个CPU core独享，而L3 或者LLC（last level cache）则由同一个socket上的所有物理core共享。这个时候如果考虑双路甚至多路CPU的话，由于内存中的数据有且只有一份，但多个CPU很可能在此基础上创建属于自己socket的缓存。对于内存的读访问来说，多份数据并不会影响到一致性，但一旦有一个socket上的某个core修改了数据，一致性问题则会凸显。究竟CPU是如何实现一致性保障的呢？</p>



<span id="more-2933"></span>



<p>还是先从CPU的硬件实现来讲起。<br>CPU 内部有两个与内存/LLC相关的组件：CA（cache agent，负责cache内容的管理）和HA（home agent，负责内存的读写操作）。由于内存在系统视图上是一个整体，具有独立的一致性，即无论有多少物理socket，在整个系统中有且仅有一个逻辑HA。而Cache（特指LLC，下同）仅在每个socket内有效，故逻辑上CA有多个，每个socket的CA在逻辑上是独立的。根据上面描述，CA和HA之间仅存在如下几种通讯方式：CA 到其他CA、CA到HA、HA到CA。<br></p>



<p>对于CA中管理的Cache来说，分为如下的几个状态，习惯上称之为MESI或者MESIF状态机：<br></p>



<ul><li>Modified：已被编辑状态，表示当前CA中的cache已经被修改且尚未同步到内存。</li><li>Exclusive：当前的CA已经获取了cache对应的内存写引用，但目前尚未修改。</li><li>Shared：CA仅获取了cache对应内存的读引用，无法直接修改内存。</li><li>Invalid：CA没有获取对应内存的任何引用，即系统不需要读/写对应的内存。</li><li>Forward：已经在最新的架构中被取消，原指CA被指派需要向其他node传输信息的状态。</li></ul>



<p>这几种状态中，由于M状态的前提必须经由E状态转换而来（没有写权限就不会有被修改且尚未同步到内存的Cache数据），而是否能够取得E状态则成为了整个Cache一致性的关键。对于S状态的只读cache，只要在内存数据得到更新之后直接通知CA更新cache即可，并不存在一致性问题。I状态就更简单了，没有任何引用，自然就没有任何一致性的问题。</p>



<p>CA通过一个叫做snoop的方式来确保数据的一致性。假设目前有一个2Socket的系统，在默认的状况下socket 0需要update某块数据段，snoop的过程如下：<br></p>



<ol><li> Socket 0上的CA-0向HA发起获取E-0状态的请求，同时向Socket 1 的CA-1发送snooping，确保socket 1没有E以上的权限。</li><li>Socket 1的CA-1在收到snooping之后检查cache中是否饮用到了对应的数据段，对应了三种可能性：<ul><li>没有引用，I-1状态，最简单，直接回复HA确认消息。</li><li>S-1状态，直接丢弃当前的数据段，向HA确认之后，等待数据更新。</li><li>E-1状态，说明Socket 1 有计划向此数据段写入内容，等待至数据写入后向HA回复消息，或者直接要求HA裁决先后顺序。</li><li>M-1状态，数据已经修改，则直接回复HA要求数据回写。</li></ul></li><li>HA在收到Socket 1的回复消息之后发送最新的数据段到socket 0，同时赋予socket 0的E-0权限。当然，对于M-1这种情况，HA在这个过程中就向真实的物理内存中写入数据。</li></ol>



<p>这个过程的时序图表达如下：</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img loading="lazy" src="http://www.litrin.net/wp-content/uploads/2019/01/socket_snoop_no_c2c.png" alt="" class="wp-image-2937" width="523" height="473"/><figcaption>socket snoop without cache to cache feature</figcaption></figure></div>



<p>其实在当前的CPU架构中支持两种形式的snooping：由CA发起的被称为socket snooping；CA请求后再由HA直接发起的被称为home snooping。简而言之，home snooping更适合对于冲突的裁决而socket snooping则具有更好的性能。同时，由于当前的Xeon CPU支持Cache to cache优化，即数据不需要回写而直接将M状态的数据段通过CA在socket之间来回传递。如果你看懂了上面的时序图，那下面的几种情况也就不难理解了。</p>


<figure class="wp-block-image"><img class="wp-image-2938 aligncenter" src="http://www.litrin.net/wp-content/uploads/2019/01/socket_snoop_c2c.png" alt=""><figcaption>Socket snooping with cache to cache copy</figcaption></figure>
<figure class="wp-block-image"><img class="wp-image-2939 aligncenter" src="http://www.litrin.net/wp-content/uploads/2019/01/home_snoop_no_c2c.png" alt=""><p></p>
<p></p>
<figcaption>Home snooping without cache to cache</figcaption>
</figure>
<figure class="wp-block-image"><img class="wp-image-2940 aligncenter" src="http://www.litrin.net/wp-content/uploads/2019/01/home_snoop_c2c.png" alt=""><p></p>
<p></p>
<figcaption>Home snooping with cache to cache copy</figcaption>
</figure>


<p>至于系统中多余2个socket的情形事实上跟2socket情况下没有什么太大的区别，无非多发送几个CA到CA或者HA到CA的snoop请求而已。在这里我也不再累述了。还有需要注意的是，本文介绍的几种基于snoop的一致性管理仅限于介绍“原理”，实际在工程实现上还会有各种优化方式，而事实上snoop的数量并不需要满足“每个socket都发”的条件。这里牵扯到冗余数据的问题，有机会单独开帖了。</p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2019/01/02/llc%e7%bc%93%e5%ad%98%e4%b8%80%e8%87%b4%e6%80%a7%e4%bf%9d%e9%9a%9c%e5%8e%9f%e7%90%86/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CPU的电源状态分类</title>
		<link>http://www.litrin.net/2018/12/28/cpu%e7%9a%84%e7%94%b5%e6%ba%90%e7%8a%b6%e6%80%81%e5%88%86%e7%b1%bb/</link>
					<comments>http://www.litrin.net/2018/12/28/cpu%e7%9a%84%e7%94%b5%e6%ba%90%e7%8a%b6%e6%80%81%e5%88%86%e7%b1%bb/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Fri, 28 Dec 2018 09:25:44 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=2926</guid>

					<description><![CDATA[有感于CPU的各种电源状态描述复杂且混乱无比。常见的诸如S0/S1/S3，C0/C3一会儿又成了P开头的P0， &#8230; <p class="link-more"><a href="http://www.litrin.net/2018/12/28/cpu%e7%9a%84%e7%94%b5%e6%ba%90%e7%8a%b6%e6%80%81%e5%88%86%e7%b1%bb/" class="more-link">继续阅读<span class="screen-reader-text">“CPU的电源状态分类”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>有感于CPU的各种电源状态描述复杂且混乱无比。常见的诸如S0/S1/S3，C0/C3一会儿又成了P开头的P0，P1，还有一堆P01~P0n之类的让人一时半会儿摸不着头脑，这次就一次性归纳一下吧。</p>



<span id="more-2926"></span>



<p>首先是G字母开头的0-3，G = global是从系统全局出发给出的系统级别的电源状态。工作、休眠、软硬关机4个状态。G状态本质上并不是对CPU状态的直接描述，但CPU会在不同的全局状态下有自己对应的状态。</p>



<p>S开头的0-5，S个人理解为sleep，描述各种CPU的休眠状态。S0代表了CPU在工作状态，S1-4则代表CPU处于某种休眠状态，数字越大睡得越香，S5则是软关机状态。</p>



<p>C开头的0-10（没有C2/C4/C5的具体描述，待后续完善），C=CPU（似乎CPU core更为确切）。代表的CPU core级别的工作状态，0为工作状态，数字越大说明CPU处在越节能的状态。从单个Core的角度上说C6时core已经处在了关闭的状态，但之后的C7+则进一步从core上升到了socket，关闭同一socket上的LLC，降低电压——直至关闭电源。</p>



<p>P开头的0-n，P=performance，代表当前系统的性能设定状态。当P0时，CPU会以标称频率运行，数字越大频率越低，CPU将会采用更加激进的方式通过降低频率来实现节能的效果。与之相关的技术如speed step技术。本身这是一个socket级别的技术，但后期有了PCPS以及后续的HWP之后，这个技术就成为了core级别的设定。</p>



<p>P0之上还有个特例，就是基于turbo技术的P01~P0n，n越大，CPU将会跑在更高的高于CPU标称频率之上，这里的n等于实际倍频和标称倍频之差。受制于CPU的热功耗上限，不同工况下CPU core能承载的n值是不同的。换而言之，由于热功耗是一个固定的值，在某些核心处于低功耗状态时，允许另外核心提升到更高的频率，即可以使n更大。</p>



<p>最后，附上一张关联图：</p>



<figure class="wp-block-image"><img src="http://www.litrin.net/wp-content/uploads/2018/12/image-1024x573.png" alt="" class="wp-image-2927"/></figure>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2018/12/28/cpu%e7%9a%84%e7%94%b5%e6%ba%90%e7%8a%b6%e6%80%81%e5%88%86%e7%b1%bb/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>应用程序状态切换检测</title>
		<link>http://www.litrin.net/2018/11/21/%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e7%8a%b6%e6%80%81%e5%88%87%e6%8d%a2%e6%a3%80%e6%b5%8b/</link>
					<comments>http://www.litrin.net/2018/11/21/%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e7%8a%b6%e6%80%81%e5%88%87%e6%8d%a2%e6%a3%80%e6%b5%8b/#respond</comments>
		
		<dc:creator><![CDATA[litrin]]></dc:creator>
		<pubDate>Wed, 21 Nov 2018 00:17:07 +0000</pubDate>
				<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[Virtualization]]></category>
		<category><![CDATA[服务器]]></category>
		<category><![CDATA[系统架构]]></category>
		<guid isPermaLink="false">http://www.litrin.net/?p=2909</guid>

					<description><![CDATA[任何一个合理的应用程序的运行时间内，微观上都会存在或多或少的“状态切换”。所谓状态切换的定义就是在用户可感知的 &#8230; <p class="link-more"><a href="http://www.litrin.net/2018/11/21/%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e7%8a%b6%e6%80%81%e5%88%87%e6%8d%a2%e6%a3%80%e6%b5%8b/" class="more-link">继续阅读<span class="screen-reader-text">“应用程序状态切换检测”</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>任何一个合理的应用程序的运行时间内，微观上都会存在或多或少的“状态切换”。所谓状态切换的定义就是在用户可感知的特征变化。可感知的特征可以理解为对各种资源的需求变化的特性。<br>有点绕，举个简单例子：一个SQL数据库，在只有一个用户连接的时候，每次用户的访问都会出发一个状态切换事件，而当用户使用长连接（pipe line）去执行多个SQL命令时，由于SQL的不同，每个SQL之间就是一个状态切换。</p>



<span id="more-2909"></span>



<p>对于用户自己的应用程序来说，就比如上面例子中的SQL DB，每次的状态切换用户自己是知道的。比如你要准备做某个DB表维护，你可能会提前检查磁盘空间，这就是一个典型的资源分配确认的流程。而对于虚拟机的应用场景来说，对于虚拟机的托管商是无法通过类似的方式正确的理解每个租户的用户行为的。然而在超售成为必然的云计算行业中，理解用户行为是非常重要的（自黑：正如<a href="http://www.litrin.net/">本站</a>自从使用了云计算服务托管之后，总是会出现莫名其妙的卡顿）。那是不是存在一个方法可以允许主机平台直接可以检测到应用程序（虚机）的状态切换呢？</p>



<p>有一个方法是通过CPU指令中内存的读写变化来区别每个状态的切换。通过Linux的perf命令，我们可以拿到load instrucation/store instruction两个指标。而这两个指标的微分（斜率）变化既可以直接获得状态切换。</p>



<div class="wp-block-image"><figure class="aligncenter"><img src="http://www.litrin.net/wp-content/uploads/2018/11/relationship-load-store-1-1024x506.png" alt="" class="wp-image-2910"/><figcaption><em>dcat 状态切换检测</em></figcaption></figure></div>



<p>我们的在虚机中通过简单的批处理依次执行了3个常见的应用模拟虚机租户的状态变化。上面包含了两个曲线图，上图为IPC（instruction per cycle），下图为load instruction/store instruction的两个曲线以及他们的斜率（点阵）混合图。</p>



<p>区间1-2为一个矩阵计算，区间3-4为内存搬运压力测试，其中包含了3个相同的周期，区间5-6为文件压缩测试。从测试结果上来看，每个状态的切换都可以很好的被检测出来。</p>



<p>如果你之前有过一定的性能分析经验，你可能会提出疑问：“为什么不直接使用IPC的变换来确定应用程序的状态切换？” Litrin自己也曾经有过这个疑问，但IPC的变化相对来说是一个更加连续线性的过程，正如1-2区间内，IPC的变换是连续的下降；剩余的状态切换IPC的变化程度远没有load/store instruction的变化来的剧烈。而这种状态切换检测的依据就是要找到一种“不连续”的曲线来。所以尽管IPC是一个更加常见的性能指标，获取代价也更加低廉，但它并无法准确的反映出状态切换的整个过程。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://www.litrin.net/2018/11/21/%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e7%8a%b6%e6%80%81%e5%88%87%e6%8d%a2%e6%a3%80%e6%b5%8b/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
