<?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>技术 | Easton Man's Blog</title>
	<atom:link href="https://blog.eastonman.com/blog/category/tech/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.eastonman.com</link>
	<description>临渊羡鱼，不如退而结网</description>
	<lastBuildDate>Tue, 19 Mar 2024 12:47:06 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.5</generator>

<image>
	<url>https://blog.eastonman.com/wp-content/uploads/2021/02/cropped-Logo-e1613298891313-32x32.png</url>
	<title>技术 | Easton Man's Blog</title>
	<link>https://blog.eastonman.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>龙芯3A6000：国产CPU中的一颗明星</title>
		<link>https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/</link>
					<comments>https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/#respond</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Tue, 19 Mar 2024 12:45:48 +0000</pubDate>
				<category><![CDATA[ChipsAndCheese 翻译]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[CPU]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=1592</guid>

					<description><![CDATA[<p>计算能力已成为全球经济体至关重要的资源，对中国来说也不例外。中国正在大力投资于国产 CPU 的开发，龙芯就处于国产 CPU 的前沿。我们之前已经介绍过该公司的 3A5000 CPU：这是一个四核处理器，提供了还算可以的 IPC，但是由于时钟频率过低，与竞争对手相比而言并不具有竞争力。</p>
The post <a href="https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/">龙芯3A6000：国产CPU中的一颗明星</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 37 分钟</p>
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文翻译自 <a href="https://chipsandcheese.com/" target="_blank" rel="noopener" title="">ChipsAndCheese</a>，原文为英文，原文链接：<a href="https://chipsandcheese.com/2024/03/13/loongson-3a6000-a-star-among-chinese-cpus/" target="_blank" rel="noopener" title="">https://chipsandcheese.com/2024/03/13/loongson-3a6000-a-star-among-chinese-cpus/</a> 原作者：clamchowder</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文中文翻译首发自我的博客 <a href="https://blog.eastonman.com/blog/2024/03/oongson-3a6000-a-star-among-chinese-cpus/" target="_blank" rel="noopener" title=""></a><a href="https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/" target="_blank" rel="noopener" title="">https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/</a> ，翻译已取得 ChipsAndCheese 编辑/原作者授权。因英文原文无公开的授权协议，本译文也禁止转载，如需转载请先联系我或 ChipsAndCheese。</p>
</blockquote>



<figure class="wp-block-image aligncenter size-large"><img fetchpriority="high" decoding="async" width="1024" height="575" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-1024x575.jpg" alt="" class="wp-image-1596" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-1024x575.jpg 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-300x169.jpg 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-768x431.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image.jpg 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">龙芯视频的截图，视频位于&nbsp;<a href="https://www.loongson.cn/news/show?id=638" target="_blank" rel="noopener" title="">https://www.loongson.cn/news/show?id=638</a></figcaption></figure>



<p>现在，我们将看看龙芯新一代的 3A6000 CPU。3A6000 同样是一个四核的 CPU，时钟频率在 2.5GHz，但使用了新的 LA664 核心。相较于 3A5000 的 LA464 核心，LA664 是一个巨大且雄心勃勃的改进。尽管龙芯保持了相同的总体架构，但 LA664 拥有更宽、更深的流水线和更多的执行单元。让事情看起来更好的是，LA664 支持了 SMT（同时多线程）。如果正确实现的话，SMT 可以在很小的面积开销下增加多线程的性能。然而，要把 SMT 做好并不容易。</p>



<h2 class="wp-block-heading">3A6000的性能测试</h2>



<p>7-Zip 是一个文件压缩程序，它有很高的压缩率，但对CPU的要求也很高。7-Zip 几乎完全使用标量整数指令，因此 SIMD 扩展并不提供什么加速。这里，我们通过压缩一个大型的 ETL 性能追踪文件来对比性能。</p>



<p>3A6000 相比它的前身 3A5000 有 38% 的巨大性能提升。如果考虑 SMT 的话，这个提升还更大。在这个 workload 中每个核心只使用一个线程时，四核 LA664 与四核 Zen1 差不多。因此，LA664 的 IPC 性能非常好，因为它只运行在 2.5GHz 的频率上，但低频阻止了它超越 AMD 更新的产品。</p>



<figure class="wp-block-image aligncenter size-large"><img decoding="async" width="1024" height="585" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-1024x585.png" alt="" class="wp-image-1599" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-1024x585.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-300x171.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-768x438.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image.png 1228w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>当使用所有线程时，SMT 为 3A6000 提供了 20% 的性能提升。与此同时 AMD 的 SMT 在 Zen1 和 Zen2 上有 40% 以上的收益。SMT 的作用是提供了更多的显式并行性，帮助 CPU 隐藏延迟并更好地填满流水线。一方面，高 SMT 收益表明核心的 SMT 实现是经过精心调整的。另一方面，这又意味着核心在运行单线程时没有很好地隐藏延迟。</p>



<p>与 7-Zip 不同，libx264 视频编码大量使用 SIMD 指令。在 x86 CPU 上，编码器将使用 SSE、AVX、AVX2 这些扩展来加速，甚至是使用 AVX-512。在龙芯的 CPU 上，libx264 会使用 LSX 和 LASX 这两种 SIMD 扩展。在这里，我转码一个4K的《守望先锋》游戏剪辑来对比性能。</p>



<figure class="wp-block-image aligncenter size-large"><img decoding="async" width="1024" height="591" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-1-1024x591.png" alt="" class="wp-image-1601" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-1-1024x591.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-1-300x173.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-1-768x443.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-1.png 1230w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>3A6000 和 Zen1 性能大概差不多，在使用所有线程和每个核心只运行一个线程的情况下互有胜负。Zen1 较高的 SMT 收益可能受到其弱的 AVX2 实现的限制。Zen2 和 3A6000 都有 30% 以上的 SMT 收益。因为架构经过了全面改进和有坚实的 AVX-512 实现，AMD 最新的 Zen4 架构在这些测试中遥遥领先。和之前一样，对于一个 2.5GHz 的 CPU 来说，3A6000 的表现还是非常令人钦佩的。</p>



<p>LA664 核心将龙芯从低性能的区域带入了能够与 AMD 和 Intel 的旧型号竞争的行列。Zen1 也和 Haswell 性能差不多，而这两种架构即使在今天也是仍能一战的。接下来让我们来看看是什么样的架构能让龙芯与那些高时钟频率的设计竞争。</p>



<h2 class="wp-block-heading">核心架构</h2>



<p>LA664 是一个 6 发射的乱序核心，拥有丰富的核内资源和较大的乱序窗口。它在这些方面已经可以与较新的 Intel 和 AMD 核心相媲美。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="624" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-2.jpg" alt="" class="wp-image-1613" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-2.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-2-300x244.jpg 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>LA664 在 LA464 的基础上进行了改进。龙芯 3A5000 中的 LA464 核心是一个四发射设计，但在所有方面都相对保守。LA464 总体上是一个经得起考验的核心，没有明显的弱点，并为龙芯在 LA664 的改进提供了坚实的基础。毫不意外地，LA664 继承了 LA464 的总体架构。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="706" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-2-1.jpg" alt="" class="wp-image-1614" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-2-1.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-2-1-300x276.jpg 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>当然，架构图不能覆盖全部的细节。类似分支预测、执行单元延迟和访存性能等因素可能会对性能产生巨大的影响。</p>



<h2 class="wp-block-heading">龙芯3A6000的前端</h2>



<h3 class="wp-block-heading">分支预测</h3>



<p>CPU 分支预测器的责任是指导前端取指，告诉 CPU 分支将会走向哪个方向。这非常重要，因为如果分支预测器预测错误，CPU 的后端将会在错误路径上浪费大量的性能、功耗和时间。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="517" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-2.png" alt="" class="wp-image-1615" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-2.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-2-300x202.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>3A6000 的分支预测器拥有令人印象深刻的模式识别能力，在我们迄今为止看到的中国 CPU 中显然是最优秀的。它与我们在 3A5000 中所见到的大相径庭，并且几乎能够与最新的 Intel 和 AMD 的 CPU 相媲美。但是 AMD 一直在分支预测器能力上投入了大量精力，这使他们的 Zen3 架构仍然领先一些。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="450" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-3.png" alt="" class="wp-image-1620" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-3.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-3-300x176.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>Zen3 通过使用覆盖重定向的预测器来实现这一点：大多数分支行为简单，可被一级预测器迅速处理；仅对具有长期历史依赖的分支，能力更强但速度较慢的二级预测器才需要介入。这种方案使 Zen3 的分支预测器能在不牺牲预测速度的前提下跟踪非常长的分支历史。</p>



<p>即使 3A6000 无法与 AMD 最新的核心匹敌，龙芯在这一领域取得的进步仍值得赞扬。3A5000 的预测器看起来更适合 2000 年代中期到 2010 年代初的高性能核心，而不是近十年的产品。龙芯在分支预测器的改进无疑是 3A6000 改善性能的一大因素。</p>



<h3 class="wp-block-heading">分支预测器速度</h3>



<p>分支预测器要快速且准确，以避免出现供指问题。分支目标缓冲（BTB）缓存分支的目标，让预测器在实际分支指令被从 ICache 中取出并译码之前就能提供一个推测的指令流。LA664 拥有 64 项的 L1 BTB，能够连续（或者说Zero-Bubble地）处理 taken 的分支。BTB 缺失的情况很可能简单地等待 64KB 的 L1i 中的指令被取出和译码，然后才计算分支目标。这在实际效果上相当于 L1i 充当一个 1K-4K 项的 L2 BTB。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="403" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-4.png" alt="" class="wp-image-1623" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-4.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-4-300x157.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>相比之下，AMD 和 Intel 最近的架构都使用解耦的大型 L2 甚至 L3 BTB。只要分支 footprint 能被 BTB 覆盖，从下级 BTB 获取地址可以比从 ICache 获取更快，AMD 的 Zen4 就利用这一点来做到非常低延迟的分支处理。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="406" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-5.png" alt="" class="wp-image-1627" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-5.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-5-300x159.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>此外，从指令缓存解耦 BTB 有助于维护高 IPC，因为预测器得以免受 L1i 缺失延迟的影响。但龙芯决定放弃一个大的、解耦的 BTB 并非独一无二。Tachyum Prodigy 也做出了同样的决定，因为他们发现用标准库达到他们的目标频率将会太贵。如同 Tachyum 的 Prodigy，3A6000 通过使用一个大的 64KB 指令缓存来补偿这一点。如果 L1i 缺失变少，那这种做法弱点就不那么明显了。</p>



<p>3A6000 似乎还有更加积极的 nextline 指令预取。上面的测试中，分支仅仅跳到到下一个 16B 对齐的块，因此 nextline 指令预取器可以很好地工作。相比之下，AMD 的预取完全由分支预测器驱动。一旦超过了 BTB 容量，就无法隐藏 L2 延迟。</p>



<h3 class="wp-block-heading">间接分支预测</h3>



<p>与直接分支相比，间接分支更难预测。间接分支并不是直接编码跳转目标，而是跳转到寄存器中的地址。3A6000 在间接分支预测方面做得非常好。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="542" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-6.png" alt="" class="wp-image-1631" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-6.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-6-300x212.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>3A6000 可以跟踪总共 1024 个间接跳转目标，是 3A5000 的两倍。3A5000 只能跟踪大约 24 个间接分支，而 3A6000 可以轻易跟踪超过 128 个间接分支。相比之下，Zen2 也可以跟踪 1024 个间接跳转目标，因此 3A6000 在这一点上与较新的 x86 CPU 非常接近。</p>



<h3 class="wp-block-heading">返回地址预测</h3>



<p>返回指令作为一种特殊类型的间接分支，经常是以 call-return pair 的形式出现。许多处理器倾向于使用一个专用的返回地址栈。当分支预测器遇到一个调用指令时，它会将一个地址压入这个栈中。当它遇到一个返回指令时，它会从栈中弹出一个地址。有趣的是，3A6000 将返回地址栈的容量从 3A5000 的 32 项减少到了仅仅 16 项。如果核心的两个 SMT 线程都在运行，有效容量甚至可能进一步降低到 8 项。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="346" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-7.png" alt="" class="wp-image-1633" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-7.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-7-300x135.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>将返回栈容量降低到 16 项在某种程度上是有道理的，因为即使是一个较小的返回栈也能覆盖到大多数调用/返回情况。龙芯可能是在返回地址栈溢出示回落到间接分支预测器，因为溢出时 3A6000 只会遭受轻微的性能损失。Intel 采用同样的这种策略。在这种情况下，返回栈地址可以被视为一个功耗和性能优化的措施，而不是性能关键路径上的一部分。</p>



<h3 class="wp-block-heading">分支预测准确率</h3>



<p>7-Zip 和其他压缩程序通常对 CPU 的分支预测器构成巨大的挑战。3A6000 的分支预测器表现出色，与 Zen1 在 MPKI 上持平。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="688" height="798" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-8.png" alt="" class="wp-image-1635" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-8.png 688w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-8-259x300.png 259w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<p>在准确率方面，3A6000 与 Zen2 不相上下。在跨 ISA 对比时，准确率是一个更好的衡量标准，因为 3A6000 能够以更少的执行指令完成工作负载。MPKI 较高可能是因为龙芯的指令流中分支的比例更大。AMD 最新的 Zen4 架构仍然领先，但龙芯在分支预测器方面取得了值得赞扬的进步，它远远优于3A5000。</p>



<p>libx264 拥有较少并且更好预测的分支，但我们仍然可以看到测试 CPU 之间的差异。在这个测试中，ISA 差异对龙芯不利。3A6000 的 MPKI 较低，但这仅是因为它完成工作负载执行了更多的指令。当有更多的非分支指令要处理时，误预测的影响就变得不那么重要了。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="878" height="1024" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-9-878x1024.png" alt="" class="wp-image-1643" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-9-878x1024.png 878w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-9-257x300.png 257w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-9-768x896.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-9.png 1109w" sizes="(max-width: 878px) 100vw, 878px" /></figure>



<p>3A6000 的分支预测器表现可以与 AMD 的 Zen2 相媲美。龙芯再次显示出他们在设计分支预测器能力上的竞争力。</p>



<h3 class="wp-block-heading">指令获取</h3>



<p>一旦分支预测器决定了指令流，就轮到指令缓存来为核心提供指令。与它的前辈一样，3A6000 拥有一个大型的 64KB 4 路 L1i。Intel 和 AMD 的 CPU 只有 32KB 的 L1i，相比来说 3A6000 有一个相当大的L1i。64KB 的 L1i 向一个 6 宽的译码器供指，这使 3A6000 有比它的前任宽 50% 的前端。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="503" height="1024" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-10-503x1024.png" alt="" class="wp-image-1644" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-10-503x1024.png 503w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-10-147x300.png 147w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-10.png 527w" sizes="(max-width: 503px) 100vw, 503px" /></figure>



<p>AMD 和Intel 的高性能 CPU 使用 uOp Cache，这可以让它们在提供更高吞吐量的同时避免指令译码开销。自 Zen 架构以来，AMD 的 uOp Cache 理论上可以在每周期提供一整行的 8 个 uOp，但是下游的重命名宽度限制了核心的吞吐量。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="509" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-11-1024x509.png" alt="" class="wp-image-1645" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-11-1024x509.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-11-300x149.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-11-768x382.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-11.png 1374w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>当代码溢出 L1i 时，龙芯 3A6000 保持了良好的吞吐量。3A5000 在运行 L2 中的代码时出现了不明原因的指令带宽不足。龙芯在 3A6000 中解决了这个问题，它可以在代码被 L2 覆盖时维持每周期 3 条指令的吞吐。</p>



<p>不幸的是，在指令 footprint 增大到 L3 以后，情况并没有那么乐观。看起来龙芯在 3A6000 上并没有改进他们从 L3 获取指令的能力。这是有些遗憾，因为 Golden Cove 和 Zen3 都可以每周期获取超过三个 4 字节指令，而龙芯的 CPU 连每周期两个都不行。</p>



<h2 class="wp-block-heading">重命名和分配</h2>



<p>指令被解码成 uOp 后，CPU 需要分配后端资源。这些资源追踪指令状态，允许 CPU 尽快执行指令，同时确保正确的程序行为和异常处理。这个阶段还通过寄存器重命名机制打破假依赖，并在执行引擎中实现了其他技巧，以暴露更多的指令级并行性。</p>



<p>LA664 的重命名阶段有 move elimination，对寄存器置零有特殊处理，类似于 x86 CPU 识别原地 XOR 的行为。重命名完全消除了这样的操作，意味着它们不会消耗更下游的执行资源。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td>测试项目</td><td>3A6000 IPC</td><td>Zen 4 IPC</td></tr><tr><td>有依赖的move</td><td>2.00</td><td>5.71</td></tr><tr><td>无依赖的move</td><td>3.64</td><td>5.73</td></tr><tr><td>寄存器置零</td><td>5.35 (li.d)</td><td>5.73 (XOR)</td></tr></tbody></table></figure>



<p>龙芯针对寄存器 move 进行了一些优化。有依赖的 move r,r 指令可以每周期执行两条，所以 LA664 有时可以在重命名表中通过指针操作以打破这种依赖。然而，它不能以全速执行move，寄存器 move 仍然需要通过 ALU。</p>



<p>Intel 和 AMD 都有更激进的消除，能够全速消除寄存器 move，无论是否存在依赖。Intel 的 Golden Cove 还可以在重命名阶段消除带小立即数的加法，进一步减轻执行阶段流水的负担。</p>



<h2 class="wp-block-heading">乱序执行</h2>



<p>为了实现乱序执行，重命名和分配阶段需要在必要的队列和缓冲区中找到空位才可以继续。更大的结构允许核心有更大的乱序执行窗口，使其更能隐藏延迟并利用指令级并行性。龙芯 3A6000 拥有非常大型的乱序引擎，这是在 3A5000 基础上的巨大进步。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td>结构</td><td>何时需要占用</td><td>LA664 （龙芯 3A6000）</td><td>LA464（龙芯 3A5000）</td><td>Zen3</td><td>Golden Cove</td></tr><tr><td>Reorder Buffer</td><td>&#8211;</td><td>256 项</td><td>128 项</td><td>256 项</td><td>512 项</td></tr><tr><td>整数寄存器堆</td><td>目的寄存器为整数寄存器</td><td>192 项</td><td>128 项</td><td>192 项</td><td>280 项</td></tr><tr><td>向量/浮点寄存器堆</td><td>目的寄存器为向量/浮点寄存器</td><td>256bit 192 项 ，6KB 总容量</td><td>256bit 128 项 ，4KB 总容量</td><td>256bit 160 项 ，5KB 总容量</td><td>512bit 228 项 + 256bit 104 项，16.875KB 总容量</td></tr><tr><td>调度队列</td><td>等待执行</td><td>48 项整数, 48 项浮点, 48 项访存</td><td>32 项整数, 32 项浮点, 32 项访存</td><td>4×24 项整数 ( 其中3个与AGU共享)<br>2×32 项浮点+ 64 项顺序队列</td><td>97 项运算，70 项Load，38 项Store</td></tr><tr><td>Load Queue</td><td>读内存</td><td>80 项</td><td>64 项</td><td>116 项*</td><td>192 项</td></tr><tr><td>Store Queue</td><td>写内存</td><td>64 项</td><td>44 项</td><td>64 项</td><td>114 项</td></tr><tr><td>分支缓冲</td><td>控制流指令</td><td>64 项</td><td>26 项</td><td>48 项 Taken, 117 项 Not Taken</td><td>128 项</td></tr></tbody></table><figcaption class="wp-element-caption">*Zen优化手册显示，Load队列有72项，但核心可以同时处理116个Load。为了与其他架构保持一致，使用了实测的116这一数据。</figcaption></figure>



<p>与 3A5000 相比，诸如寄存器堆和 LSQ 等主要结构至少增加了 25% 的大小。LA464 的分支缓冲看起来太小了，而 LA664 解决了这个问题。LA664 最终拥有与 AMD 的 Zen3 相当的乱序能力。但 Zen3 和 LA664 与 Intel 的 Golden Cove 相比起来仍然小，Golden Cove 有着巨大的 512 项 ROB 和其他更大的结构。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="432" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-12.jpg" alt="" class="wp-image-1659" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-12.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-12-300x169.jpg 300w" sizes="(max-width: 768px) 100vw, 768px" /><figcaption class="wp-element-caption">来自龙芯视频的另一个截图，右侧展示了微结构参数。它们与微基准测试结果一致。</figcaption></figure>



<h3 class="wp-block-heading">SMT实现</h3>



<p>更大的乱序缓冲区对于提高单线程性能至关重要，但会遇到收益边际效应。SMT 通过向操作系统暴露多个逻辑线程，并在这些线程之间分配资源来对抗这种收益递减。因此，一个启用了 SMT 的 CPU 可以保持单线程的高性能，与此同时如果有多个线程活跃，它就像几个较小的核心一样工作。AMD、Intel 和龙芯都决定为每个核心设计两个 SMT 线程。</p>



<p>虽然 SMT 的好处很明显，但实现难度很大。CPU 必须能够动态地在多个线程之间切换。工程师必须决定在两个线程的模式下如何管理各种核心结构。具体来说这种情况下，一个结构的处理方式可以是：</p>



<ul class="wp-block-list">
<li>复制。每个线程获得一份完整的资源。在单线程模式下，第二份资源不使用。这不是一个面积高效的方式，但可能更容易 tuning 和验证。不需要担心线程饥饿，如果第二个线程变得活跃，也不需要清空部分资源。</li>



<li>静态分区。每个线程获得一半的资源。当一个线程处于停止状态时，另一个线程可以使用所有资源，所以这种方法是面积上更高效的。它更难验证，因为当两个线程都工作时，资源需要清空一半并重新配置。但由于将结构切分为两半确保了线程之间某种程度的公平性，所以 tuning 仍然不是太难。</li>



<li>使用 water-mark。在 2T 模式下，一个线程可以占用资源直至 high water-mark。这种做法更灵活、有更多潜在的性能，但 tuning 更难。较高的 water-mark 可能会提高一个线程的性能，但对另一个（饥饿的）线程有严重的影响。</li>



<li>竞争性共享。这就是自由竞争：即使另一个线程活跃，一个线程也可以使用所有的资源。这种情况灵活性和潜在的性能都可以达到最大。例如，如果一个线程运行浮点代码，另一个运行纯整数代码，竞争性共享调度器将允许两个线程用满他们最需要的资源。但 tuning 和验证变得更难。线程饿死可能会更加常见，工程师必须小心避免这种情况。</li>
</ul>



<p>龙芯选择了一个保守的 SMT 实现，其中大多数资源都是静态分区的，包括 ROB、寄存器堆和 LSQ。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="311" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-12.png" alt="" class="wp-image-1662" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-12.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-12-300x121.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>3A6000 的调度器是用 water-mark 的。运算单元调度器的高水位标记似乎在 30 项左右，所以在 3A6000 上运行的线程即使在其 SMT 兄弟线程活跃时也几乎可以使用与在 3A5000 上一样多的调度器大小。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td>结构</td><td>3A6000</td><td>Zen 2</td></tr><tr><td>Reorder Buffer</td><td>静态分区<br>每线程约 122 项</td><td>静态分区</td></tr><tr><td>整数寄存器堆</td><td>静态分区<br>每线程约 69 项</td><td>竞争共享（可通过 QoS 等调整）</td></tr><tr><td>向量/浮点寄存器堆</td><td>静态分区<br>每线程约 64 项</td><td>竞争共享（可通过 QoS 等调整）</td></tr><tr><td>Load Queue</td><td>静态分区<br>每线程约 38 项</td><td>竞争共享</td></tr><tr><td>Store Queue</td><td>静态分区<br>每线程约 30 项</td><td>静态分区</td></tr><tr><td>整数调度队列</td><td>Watermarked<br>每线程最大 30 项</td><td>竞争共享</td></tr><tr><td>访存调度队列</td><td>Watermarked<br>每线程最大 36 项</td><td>竞争共享</td></tr><tr><td>浮点调度队列</td><td>Watermarked<br>每线程最大 30 项</td><td>Watermarked<br>每线程最多 64 项 SQ + NSQ （总共 100 项）</td></tr></tbody></table></figure>



<p>AMD Zen2 的重排序缓冲使用静态分区，并对浮点调度队列和非调度队列采用某种水位标记方案。但在其他方面，AMD 采取了非常激进的 SMT 策略。寄存器堆、LSQ 和整数调度队列是竞争性共享的。这可以部分解释Zen2 令人印象深刻的 SMT 收益。</p>



<p>对于龙芯来说，采取一个不那么激进和更容易验证的方法是合理的。3A6000 是他们首个支持 SMT 的 CPU，过于雄心勃勃总是容易导致失败。</p>



<h3 class="wp-block-heading">整数执行</h3>



<p>3A6000 的整数执行单元看起来相比前代变化最少，但调度器容量增加 50% 应该会有更好的 ALU 端口利用率。与 3A5000 一样，3A6000 有四个端口的 ALU 能够执行常见操作。两个端口可以处理分支，两个用于整数乘法。这个配置与 Zen2 大致相似，但龙芯有两个整数乘法单元，而 Zen2 只有一个。Zen2 有更大的总调度容量，但 Zen2 是分布式调度器，与龙芯的统一调度器并不能直接比较。Zen2 上可能会出现其中一个 16 项的队列提前填满，这在重命名阶段将会由于资源不足发生阻塞。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="593" height="404" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-13.png" alt="" class="wp-image-1669" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-13.png 593w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-13-300x204.png 300w" sizes="(max-width: 593px) 100vw, 593px" /></figure>



<p>龙芯在整数除法的吞吐量和延迟上进行了改进，从 3A5000 的每周期 0.11 个指令和 9 个周期的延迟提高到每周期 0.25 个指令和 4 个周期的延迟。除法器的改进是一个奇怪的选择，因为大多数代码会避免使用 DIV 指令，因为它在历史上就非常慢。也许是因为龙芯需要从头开始构建他们的软件生态系统，加速除法操作可能是一个值得的投资。</p>



<h3 class="wp-block-heading">向量和浮点执行</h3>



<p>龙芯的 3A5000 拥有 256 位的向量能力和 LASX 扩展，但实现较为保守，只有两个 256 位执行单元。3A6000 彻底改进了FPU，它现在有四个执行单元。所有四个管道都可以处理 256 位向量加法，这给 3A6000 提供了非常强大的浮点性能。竞品的 x86 CPU 只能每个周期执行两个 256 位向量加法。256 位向量乘法和基础向量整数操作性能与 Zen2 相似。</p>



<p>奇怪的是，标量浮点操作没有得到同样的增强。只有两个单元可以处理标量浮点加法。更奇怪的是，标量浮点乘法似乎和向量乘法使用不同的流水线。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="577" height="526" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-14.png" alt="" class="wp-image-1673" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-14.png 577w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-14-300x273.png 300w" sizes="(max-width: 577px) 100vw, 577px" /></figure>



<p>即使龙芯增加了额外的浮点单元，融合乘加（FMA）的峰值吞吐量还是保持不变。LA464 和 LA664 都可以每个周期执行一个 FMA 操作，它们的 FMA 吞吐量是 AMD 的 Zen2 或 Intel 的 Skylake 的一半。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td>指令</td><td>LA664</td><td>LA464</td><td>Zen3</td><td>Golden Cove</td></tr><tr><td>256bit FP64 向量加法吞吐量</td><td>4 per cycle</td><td>2 per cycle</td><td>2 per cycle</td><td>2 per cycle</td></tr><tr><td>256bit FP64 向量加法延迟</td><td>3 cycle latency</td><td>5 cycle latency</td><td>3 cycle latency</td><td>3 cycle latency</td></tr><tr><td>256bit FP64 向量乘法吞吐量</td><td>2 per cycle</td><td>2 per cycle</td><td>2 per cycle</td><td>2 per cycle</td></tr><tr><td>256bit FP64 向量乘法延迟</td><td>5 cycle latency</td><td>5 cycle latency</td><td>3 cycle latency</td><td>4 cycle latency</td></tr><tr><td>256bit FP64 向量 FMA 吞吐量</td><td>1 per cycle</td><td>1 per cycle</td><td>2 per cycle</td><td>2 per cycle</td></tr><tr><td>256bit FP64 向量 FMA 延迟</td><td>5 cycle latency</td><td>5 cycle latency</td><td>4 cycle latency</td><td>4 cycle latency</td></tr></tbody></table></figure>



<p>除了吞吐量外，龙芯改善了执行延迟。浮点加法的延迟为 3 个周期，与 Zen3 相等。然而，AMD 的 Zen3 和 Intel 的 Golden Cove 仍然有更低的浮点执行延迟。特别是 Intel，它可以在 2 个周期延迟内完成浮点加法，而且还是以更高的时钟频率运行。</p>



<p>和整数调度队列一样，浮点调度队列容量增加了 50% 至 48 项。单独就这一点来说可能就已经提供了比增加执行单元更多的浮点性能提升，两者结合使 3A6000 在向量和浮点工作负载中表现强大。</p>



<p>AMD 的 Zen2 也有一个四流水线 FPU，并且也在所有四个单元处理基础向量整数操作。然而，它使用一个聪明的非调度队列，即使它的 36 项调度队列填满，也能让后端保持更多的inflight浮点或向量操作，尽管它不能利用这些指令来找到额外的指令级并行性。</p>



<h3 class="wp-block-heading">地址生成</h3>



<p>龙芯在 LA664 中相比于 LA464 显著强化了地址生成功能。LA464 拥有两个通用的 Load/Store 单元。LA664 将这分为两个 Load 和两个 Store 单元。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="640" height="308" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-15.png" alt="" class="wp-image-1680" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-15.png 640w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-15-300x144.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></figure>



<p>这意味着，LA664 每周期可以处理比 Zen2 更多的标量内存操作，与 Golden Cove 处理的内存操作量相同。Zen3 和 Golden Cove 在灵活性方面稍微领先于 LA664，可以每周期发出 3 个 Load。但 LA664 的 2 Load 每周期已经是从 LA464 的 1 Load 每周期的大幅升级。在 Store 方面，LA664、Zen3 和 Golden Cove 都是每周期 2 次标量 Store，而 LA464 只能每周期处理一个 Store。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td></td><td>LA664</td><td>LA464</td><td>Zen3</td><td>Golden Cove</td></tr><tr><td>每周期标量 Load</td><td>2</td><td>1</td><td>3</td><td>3</td></tr><tr><td>每周期标量 Store</td><td>2</td><td>1</td><td>2</td><td>2</td></tr><tr><td>AGU 数量（标量内存操作发射宽度）</td><td>4</td><td>2</td><td>3</td><td>4</td></tr></tbody></table></figure>



<p>LA664 可以每周期处理两个 256 位向量访存，这些访存可以是任意 Load/Store 组合。</p>



<h3 class="wp-block-heading">内存序</h3>



<p>一旦地址计算完毕，Load/Store 单元必须确保内存访问符合 ISA 的内存模型。Load 可能需要从之前的 Store 中获取结果。与之前的 3A5000 一样，3A6000 可以在大约 7 个周期的延迟中处理 Load 被包含于之前的 Store 的情况。只有部分是重叠的情况会产生 14 个周期的惩罚，可能是因为 Load 被阻塞直到 Store 退休并写回到 L1d。</p>



<p>Zen2 具有相同的 7 周期转发延迟和 14 周期的部分重叠惩罚。但 Zen2 可以以一个额外周期的惩罚处理 64B 缓存行间转发，而龙芯在这种情况下表现不佳。由于 Zen2 的时钟频率更高，所以龙芯 7 周期的转发延迟感觉有些长。Goldmont Plus 的目标频率也是 2.xGHz 接近 3GHz 的范围，它就是 5 周期的 Store-Load 转发延迟以及转发不能处理时的 10 周期延迟。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="493" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1024x493.jpg" alt="" class="wp-image-1684" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1024x493.jpg 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-300x145.jpg 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-768x370.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16.jpg 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">使用 Henry Wong 的方法测量的 Store-Load 转发延迟，代码由 Clam 编写。</figcaption></figure>



<p>LA664 的 Store-Load 转发行为看起来与其前代相似。但 LA664 消除了非对齐 Store 的 10 周期惩罚，将其降低到仅 3 周期。这比 Zen2 稍好，Zen2 对于非对齐的 Store 需要 2 到 5 个周期的处理时间。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="496" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1-1024x496.jpg" alt="" class="wp-image-1688" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1-1024x496.jpg 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1-300x145.jpg 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1-768x372.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1.jpg 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>跨越 64B 边界的 Store-Load 转发仍然处理不佳，但惩罚从 31 周期降低到了可以接受的 21 周期。</p>



<h2 class="wp-block-heading">缓存和内存访问</h2>



<p>良好的缓存和内存层次结构对于任何现代高性能 CPU 的数据供给至关重要。3A6000 保留了类似的缓存层次结构，但在各个点上有细微的改进。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="250" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1024x250.png" alt="" class="wp-image-1691" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-1024x250.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-300x73.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16-768x188.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-16.png 1146w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>由于龙芯未能提高时钟速度，他们通过减少缓存访问路径中的流水线长度来降低延迟。</p>



<h3 class="wp-block-heading">延迟</h3>



<p>L1d 延迟从四个周期降低到三个周期。我认为低频 CPU 的设计目标应该是 3 周期 L1d 延迟，很高兴看到3A6000 实现了这一点。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="571" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-17-1024x571.png" alt="" class="wp-image-1692" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-17-1024x571.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-17-300x167.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-17-768x428.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-17.png 1093w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>许多现代 CPU 使用 L2 作为中级缓存，使 L1 缺失不需要到相对高延迟的 L3 中查询。3A6000 继续使用256KB L2 缓存，类似于较早的 Intel 架构。最近的 AMD 和 Intel CPU 趋向于使用更大的 L2 缓存。Zen4 已经转向使用 1MB L2 缓存，而 Intel 的 Raptor Lake 选择了巨大的 2MB L2。尽管龙芯未能实现更大的 L2，但他们将延迟从 14 个周期降低到 12 个周期。假设 L1 以同样的速度处理命中和缺失的话，L1 到 L2 的路径上可能只减了一拍。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td>内存层级</td><td>3A6000</td><td>3A5000</td><td>Zen 2 (3950X)</td></tr><tr><td>L1d</td><td>64 KB<br>3 周期延迟</td><td>64 KB<br>4 周期延迟</td><td>32 KB<br>4 周期延迟</td></tr><tr><td>L2</td><td>256 KB<br>12 周期延迟</td><td>256 KB<br>14 周期延迟</td><td>512 KB<br>12 周期延迟</td></tr><tr><td>L3</td><td>16 MB<br>38 周期延迟</td><td>16 MB<br>40 周期延迟</td><td>16 MB<br>37 周期延迟</td></tr><tr><td>DRAM</td><td>104.19 ns<br>~263 周期延迟</td><td>144.52 ns<br>~361 周期延迟</td><td>77.25 ns<br>~341 周期延迟</td></tr></tbody></table></figure>



<p>Zen2、3A6000 和 3A5000 都有一个四核共享的大型 16MB L3 缓存。3A6000 减少了几个周期的 L3 延迟，尽管这可能是因为查询 L2 加快了两个周期。</p>



<p>最后，DRAM 延迟从 144ns 改进到了 104ns。3A5000 的 DDR4 控制器很糟糕。它只是恰好被较低的时钟频率“拯救”了，使得它对 IPC 的影响没那么大。3A6000 获得了大大改善后的内存控制器。104ns 还是不够好，但以周期数计的话，它的延迟降低到了比高时钟频率的 3950X 要低。因此 3A6000 至少通过减少 DRAM 访问延迟的周期数来缓解了它的低时钟频率劣势。然而，104ns 对于一个单片 DDR4-2666 的配置来说并不是很好。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="571" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-18-1024x571.png" alt="" class="wp-image-1695" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-18-1024x571.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-18-300x167.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-18-768x428.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-18.png 1093w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>尽管 3A6000 在延迟的周期数上具有竞争力，我们必须考虑实际的延迟，因为龙芯无法将其 CPU 的时钟频率提升到现代 AMD 和 Intel CPU 运行的水平。这并不是对 LA664 有利的情况。在包括 L1 在内的每一缓存它都较慢。这解释了为什么 LA664 在拥有更多乱序能力和更高 IPC 的情况下，仍然稳定地输给 Zen2。</p>



<h3 class="wp-block-heading">带宽</h3>



<p>带宽对于向量化、多线程应用程序尤其重要。3A6000 在很大程度上继承了它前身的内存层次结构，但龙芯再次在各个地方进行了改进。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="639" height="186" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-19.png" alt="" class="wp-image-1697" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-19.png 639w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-19-300x87.png 300w" sizes="(max-width: 639px) 100vw, 639px" /></figure>



<p>3A5000 已经拥有与 Intel 的 Skylake 或 AMD 的 Zen2 相似的每周期 L1d 带宽。3A6000 通过加倍写入带宽进行了改善。基本上 3A6000 的 L1d 可以每周期完成两次 256 位访问，无论是读取还是写入。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="877" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-20-1024x877.png" alt="" class="wp-image-1698" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-20-1024x877.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-20-300x257.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-20-768x657.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-20.png 1063w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>因此，尽管时钟频率较低，3A6000 拥有令人印象深刻的 L1d 写入带宽。LA664 因此成为了和 Golden Cove 一样唯二的两个具有每周期 512 字节存储带宽的消费级 CPU。</p>



<p>LA664 的 256KB L2 表现与其前身基本类似，每周期读带宽为 21-22 字节，写带宽相同。因此，3A6000 的 L2 带宽仍然低于 AMD 或 Intel 最近的任何 CPU。与 Intel 的 CPU 相比差距特别大，后者有每周期 64字节的 L2 带宽。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="447" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-21-1024x447.png" alt="" class="wp-image-1700" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-21-1024x447.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-21-300x131.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-21-768x336.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-21.png 1213w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>在 L3 中，LA664 比其前代增加了 33％ 的带宽。每周期从 L3 获取 18.7 字节的带宽让龙芯可以对抗较老的 Intel CPU，但 AMD 特别强大的 L3 仍然遥遥领先。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="533" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-22-1024x533.png" alt="" class="wp-image-1701" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-22-1024x533.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-22-300x156.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-22-768x400.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-22.png 1134w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>设计共享缓存引入了额外的挑战，因为缓存带宽在服务多个客户时还要增加。当所有硬件线程都活跃时，3A6000 可以给每个核心每周期提供 16.55 字节。这比单个核心在没有其他核心竞争的情况下只少了一点点，是非常好的表现。旧的 3A5000 在所有四个核心加载时只能为每个核心每周期提供 10 字节多一点，与单一核心的 13.7 字节相比。这表明 3A5000 的 L3 结构存在竞争，而 3A6000 在很大程度上解决了这个问题。AMD 在多个核心一起冲击缓存时再次提供了出色的每核心 L3 带宽，每核心每周期超过 24 字节。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="688" height="472" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-24.png" alt="" class="wp-image-1706" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-24.png 688w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-24-300x206.png 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<p>龙芯的 3A5000 有一个极其糟糕的 DDR4 控制器。幸运的是，3A6000 有一个更好的控制器。技术上它支持 DDR4-3200，但我们在使用这个速度运行双通道内存时无法稳定启动。当装配双通道 DDR4-2666 时，3A6000 实现的 DRAM 读带宽大致与 Core i5-6600K 相当。这个酷睿芯片的第一代 DDR4 控制器只能在完全稳定的情况下处理 DDR4-2133 的速度（至少在我的样本上是这样），但仍然比 3A6000 用更快的内存实现更好的 DRAM 读带宽。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="573" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-1024x573.jpg" alt="" class="wp-image-1707" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-1024x573.jpg 1024w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-300x168.jpg 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-768x430.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-25.jpg 1149w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>尽管 3A6000 的 DRAM 读取性能一般，但它也有一个小 trick：当检测到缓存行被完全覆写而且没有对其先前内容的依赖时，可能会避免获取这个缓存行的所有权。这可能有助于某些访问模式，但好处可能会受限，因为程序通常有更多的读取而不是写入操作。</p>



<h2 class="wp-block-heading">核心到核心延迟</h2>



<p>这个测试使用原子比较和交换操作在核心之间传递值。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="356" height="198" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-23.png" alt="" class="wp-image-1702" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-23.png 356w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-23-300x167.png 300w" sizes="(max-width: 356px) 100vw, 356px" /></figure>



<p>龙芯的性能没有带来任何惊喜（惊吓），这是一件好事。</p>



<h2 class="wp-block-heading">结语</h2>



<p>龙芯的工程师有许多值得骄傲的地方。设计与 Zen2 相当的分支预测器并不容易，考虑到龙芯在 3A5000 中的表现，这一成就更显卓越。同样地，SMT 非常难以实现正确。龙芯在大幅扩展 3A5000 的乱序引擎和修复其 DDR4 控制器的同时，设法完成了这两件事。这些巨大的性能提升使 LA664 与 Zen1 在单核性能上相当。</p>



<p>3A6000 是中国为使其经济减少对外国 CPU 依赖所做努力的一部分。在这方面，3A6000 是向前迈出的一步。Zen1 在今天仍然非常可用，所以中国消费者可能会发现 3A6000 的性能对于轻量级日常任务已经是可以接受的了。龙芯的软件生态系统比性能更影响芯片的可用性。</p>



<p>但龙芯还有一个成为世界级 CPU 制造商的次级目标，与西方公司如 Intel 和 AMD 并肩。在这方面，他们还有很长的路要走。Zen1 级别的单线程性能值得赞扬。但我们必须记住，Zen1 之所以能够从 Intel 那里夺取市场份额，是因为它将便宜的的 6 核和 8 核产品带入了消费者平台，而不是因为它能够以核心对核心的方式赢得 Skylake。3A6000 只是一个四核产品，因此缺少了 Zen1 最大的优势。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="779" height="518" src="https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-1.jpg" alt="" class="wp-image-1711" srcset="https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-1.jpg 779w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-1-300x199.jpg 300w, https://blog.eastonman.com/wp-content/uploads/2024/03/image-25-1-768x511.jpg 768w" sizes="(max-width: 779px) 100vw, 779px" /><figcaption class="wp-element-caption">来自&nbsp;<a href="https://mp.weixin.qq.com/s/dMXDPrsCS4rOj7AOPyYOcg">https://mp.weixin.qq.com/s/dMXDPrsCS4rOj7AOPyYOcg</a></figcaption></figure>



<p>龙芯还将 3A6000 与 Intel 的 Core i3-10100 相比较——一个带有 6MB 缓存和 4.3 GHz 睿频时钟的四核 Skylake 产品。虽然从名义上看是第 10 代产品，i3-10100 更类似于 2015 年的 Core i7-6700K。Intel 的第 10 代更为人所知的是将 10 核 CPU 带入消费者阵容，而 6 核和 8 核构成中端产品。除了更多核心，像 i5-10600K 和 i7-10700K 这样的部件还享有更高的睿频频率。3A6000 将无法与这些产品竞争。针对同一时代的 Zen2 产品，龙芯也将遇到挑战。我们已经看到，3950X 在相同核心数量的测试中轻松超过 3A6000。当 Zen2 的更多核心开始发挥作用时，这一差距将只会变得更大。</p>



<p>今天，Intel 的 Golden Cove 衍生产品和 AMD 的 Zen4 在 3A6000 之上还有更大的领先优势。四核产品在 AMD 和 Intel 的消费者产品阵容中已经几乎消失。龙芯的 3A6000 可能是我们从中国看到的最有前途的 CPU，远比围绕 A72 进行的笨拙尝试更加令人兴奋。但龙芯的工程师们仍有大量工作要做。我们期待看到他们接下来能够实现什么。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文翻译自 <a href="https://chipsandcheese.com/" target="_blank" rel="noopener" title="">ChipsAndCheese</a>，原文为英文，原文链接：<a href="https://chipsandcheese.com/2024/03/13/loongson-3a6000-a-star-among-chinese-cpus/" target="_blank" rel="noopener" title="">https://chipsandcheese.com/2024/03/13/loongson-3a6000-a-star-among-chinese-cpus/</a> 原作者：clamchowder</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文中文翻译首发自我的博客 <a href="https://blog.eastonman.com/blog/2024/03/oongson-3a6000-a-star-among-chinese-cpus/" target="_blank" rel="noopener" title=""></a><a href="https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/" target="_blank" rel="noopener" title="">https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/</a> ，翻译已取得 ChipsAndCheese 编辑/原作者授权。因英文原文无公开的授权协议，本译文也禁止转载，如需转载请先联系我或 ChipsAndCheese。</p>
</blockquote>The post <a href="https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/">龙芯3A6000：国产CPU中的一颗明星</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2024/03/loongson-3a6000-a-star-among-chinese-cpus/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>HotChips 2023: Ventana 不寻常的 Veyron V1</title>
		<link>https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/</link>
					<comments>https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Sat, 10 Feb 2024 15:25:09 +0000</pubDate>
				<category><![CDATA[ChipsAndCheese 翻译]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[RISC-V]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=1454</guid>

					<description><![CDATA[<p>近年来 RISC-V 正在变得越来越火热。CPU IP 设计商中 SiFive 已经进入到高性能的范围中了。阿里巴巴的平头哥一直在开发 RISC-V 芯片，以期发展成为一条可行的本土服务器 CPU 产品线。现在，Ventana 也加入了这场派对，带来了他们的 Veyron V1 核心。这篇文章将分析 Veyron V1 的设计，并特别关注一些 Veyron V1 的不寻常特性。</p>
The post <a href="https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/">HotChips 2023: Ventana 不寻常的 Veyron V1</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 24 分钟</p>
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文大部分翻译自 <a href="https://chipsandcheese.com/" target="_blank" rel="noopener" title="">ChipsAndCheese</a>，添加有个人理解和资料补充 ，原文为英文，原文链接：<a href="https://chipsandcheese.com/2023/09/01/hot-chips-2023-ventanas-unconventional-veyron-v1/" target="_blank" rel="noopener" title="">https://chipsandcheese.com/2023/09/01/hot-chips-2023-ventanas-unconventional-veyron-v1/</a> 原作者：<a href="http://chlamchowder" target="_blank" rel="noopener" title="">chlamchowder</a></p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文中文翻译首发自我的博客 <a href="https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/" target="_blank" rel="noopener" title="">https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/</a> ，翻译已取得 ChipsAndCheese 编辑/原作者授权。因英文原文无公开的授权协议，本译文也禁止转载，如需转载请先联系我或 ChipsAndCheese。</p>
</blockquote>



<h2 class="wp-block-heading">总体设计</h2>



<p>Veyron V1 是一个八发射的乱序核心（这里一般指重命名/分派宽度）。分支误预测惩罚是 15 周期，乱序调度队列容量十分可观。与 ARM 和 SiFive 的设计类似，Veyron V1 以适中的时钟频率换取较低的面积和功率，这使它能扩展到非常多的核心。Ventana 给 Veyron V1 的目标频率是 3.6GHz，但 Veyron V1 还可以降频以减少功耗，例如在 2.4GHz 时，核心的功耗少于 0.9W。</p>



<p>译者：总体来说 8 宽度的重命名可以做到 3.6GHz 的最高频率，2.4GHz 下功耗小于 0.9W，这个设计能力并不算最好，但鉴于 RISC-V 各家都没有太多的基础，在较短时间内能迭代到这样的效果也属于不错了。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="761" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-1024x761.png" alt="" class="wp-image-1463" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-1024x761.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-300x223.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-768x571.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image.png 1130w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">根据 Vetana 演讲中信息绘制的 Veyron V1 的框图</figcaption></figure>



<h2 class="wp-block-heading">流水线设计</h2>



<p>乍一看，Veyron V1 的流水线深度可能会显得比较深（15 周期的分支误预测惩罚）。作为比较，AMD Zen4 的最小分支误预测惩罚是 11 周期，ARM 的 Neoverse N2 的流水线深度是 10 周期（我们假设 ARM 在之前的 HotChips 演讲中提到的流水线深度就是指最小分支误预测惩罚）。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="575" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-1-1024x575.png" alt="" class="wp-image-1465" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-1-1024x575.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-1-300x169.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-1-768x431.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-1.png 1200w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Ventana 在 HotChips 2023 中的演示，此处是 V1 的流水线</figcaption></figure>



<p>Ventana 表示他们新版的 V2 架构改善了分支误预测惩罚，在他们的演示中也提到了一点。在整数执行阶段1（IX1）检测到的误预测会触发在RPS（Restart Pipe Start）阶段的流水，和 IX1 重叠。这个周期离标记为 PNI 的预测流水有两个周期的距离。PNI大概率表示 Predict Next IP，意思是生成 Next Instruction Pointer 的地方。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="121" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-2-1024x121.png" alt="" class="wp-image-1466" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-2-1024x121.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-2-300x36.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-2-768x91.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-2.png 1200w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">添加了分支误预测重定向通路的流水线</figcaption></figure>



<p>通过比较实际的分支方向与预测的方向或比较跳转的目标，可以检测出分支误预测。如果直接将真实的目标地址或正确方向反馈到分支预测器中，应该能让流水线在 PNI 之后立即重新启动，将误预测惩罚减少两个周期（降至13个周期）。</p>



<h2 class="wp-block-heading">分支预测器：单级 BTB</h2>



<p>像其他一些现代 CPU 一样，Veyron V1 使用了解耦前端（分离式前端）。同时，解耦前端的分支预测结果也充当了准确的指令预取器。为了最大限度地利用这种技术，预测器必须能够同时且准确地跟踪多个分支目标。因此大的分支目标缓冲（BTB）对于分支预测器而言至关重要，这可以让分支预测器无需从指令缓存中获取指令后才能解码出跳转的目标，降低了延迟。</p>



<p>但是无论是存储分支目标还是直接存储指令数据，大而快的缓存都难以制造。因此现代 CPU 一般选择多级 BTB。通常快速的一级 BTB 能够背靠背地预测跳转的分支，而较慢的下级 BTB 则有助于保持在较大分支足迹下的良好性能。像 Intel 就有 3-4 级的 BTB 结构。</p>



<p>尽管在速度、容量和复杂性之间通常需要权衡，Ventana 说“我全都要”。V1 有一个 12K 项的巨大单级 BTB，和一个“单周期 next-line 预测器”。与 Berkerly 的 BOOM 相似，“next-line 预测器”包括有 BTB 查询，因此如果按照 Ventana 演示的说法，12K 项的 BTB 是可以做到背靠背地预测跳转分支的。这是相当了不起的成就，这让 V1 能够以最小的吞吐量损失处理非常大的分支足迹。对比之下，以 Intel 的 Golden Cove 为例，它也有一个 12K 条目的 Last-level BTB，但是延迟为三个周期。</p>



<p>译者：这可能和 Intel 的目标频率较高有关系，但是 Ventana 的单级 BTB 设计还是不太寻常，也许和尚未完全挖掘工艺制程的潜力有关。我猜测 Ventana 使用这样的设计仅是因为没有做更好的设计，巨大的单级 BTB 还会带来比较大的功耗开销，毕竟每周期都需要开启。结合 Ventana 提供的功耗数据，我觉得他们的低功耗设计还有很大的空间。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="556" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-3-1024x556.png" alt="" class="wp-image-1484" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-3-1024x556.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-3-300x163.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-3-768x417.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-3.png 1279w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Ventana 在 HotChips 2023 上的演示文稿</figcaption></figure>



<p>如果 BTB 未提供正确的目标，V1 声称能做到“three-cycle redirect on mispredict”。这可能意味着静态预测器（预译码）距离指令取指有三个周期，或者在解码的第一周期，因为 V1 的指令缓存延迟为两个周期。在有这么大的 12K 项 BTB 的情况下，这样的静态预测器（预译码）重定向应该会比较罕见。</p>



<h2 class="wp-block-heading">前端：512KB L1/L2指令缓存</h2>



<p>除了 BTB 的层级，Ventana 也减少了指令缓存层级。Veyron V1 有一个大的 512KB 指令缓存，充当 L1/L2 指令缓存的角色。一个小型循环缓冲区位于指令缓存的上方，但这主要是对密集循环的 Power 优化。V1 预计大部分时间都会从 512KB 的 L1/L2 缓存中获取指令。</p>



<p>Veyron V1 的前端设计适合于大指令足迹的软件，这些足迹溢出了我们通常看到的 32KB 或 64KB 的指令缓存。在这种情况下，其他 CPU 会浪费功耗和时间在检查 L1i 上，并需要在两级缓存之间交换指令数据。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="330" height="311" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-4.png" alt="" class="wp-image-1492" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-4.png 330w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-4-300x283.png 300w" sizes="(max-width: 330px) 100vw, 330px" /><figcaption class="wp-element-caption">Veyron V1 的 floorplan，红框是指令和数据 L2 缓存</figcaption></figure>



<p>V1 的设计使用强制性的 L2 分区，牺牲了我们在其他 CPU 上看到的缓存容量灵活性。如果程序是少量代码在大量数据上循环，一个统一的 1MB L2 可以几乎使用其所有容量来存储数据。或者，如果一个应用的数据足迹很小，但代码足迹很大，则可以为指令缓存分配更多容量。</p>



<p>如果 Ventana 尝试使用一个统一的 L2 缓存，取指的流量可能会与数据侧的 L2 访问引起冲突。CPU 的前端是缓存带宽的重要消费者，因为每条指令都必须经由前端访问下级缓存系统来获得，但不是每条指令都会有数据侧的内存访问。此外，不完美的分支预测会导致在一个误预测分支之后的指令有多于一个的前端内存访问（因为第一次尝试取错了东西）。假设一个统一的 1MB 缓存充当 L1/L2 指令缓存和 L2 数据缓存，可能会遇到指令和数据访问之间的冲突。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="518" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-5-1024x518.png" alt="" class="wp-image-1497" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-5-1024x518.png 1024w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-5-300x152.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-5-768x388.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-5.png 1313w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">即使是一个不怎么高性能的 CPU 也需要大量的供指带宽</figcaption></figure>



<p>因此，Veyron V1 的 L2i 和 L2d 缓存各自需要足够的容量，以便在广泛的工作负载中都能充当有效的中层缓存。一个 512+512KB 的 L2 设置无疑会提供比统一的 512KB L2（如 AMD 的 Zen1 到 Zen3 中的）更好的缓存容量，但与 AMD 的 Zen4（以及一堆 ARM 的设计）上的统一 1MB L2 相比，可能会比较差。</p>



<p>译者：这样的设计也比较激进，这种设计的思路来源是依靠 BTB 存储信息的密度优势，减少靠近流水线部分的 memory 数量，然后依靠预取来掩盖较远的 L2 的延迟。但是这样设计也有很多不好解决的问题，例如下面提到的非对齐取指。</p>



<h2 class="wp-block-heading">非对齐取指支持</h2>



<p>如果你仔细看 Ventana 的演示文稿，他们宣称支持&#8221;每周期最多完整 64B 的的非对齐取指&#8221;。通常来说前端缓存访问是对齐的，也就是说向指令缓存发送的地址中最低的几位是零。例如，一个 32B 对齐的取指会隐式地将五个最低地址位归零。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="1025" height="279" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-6.png" alt="" class="wp-image-1500" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-6.png 1025w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-6-300x82.png 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-6-768x209.png 768w" sizes="(max-width: 1025px) 100vw, 1025px" /><figcaption class="wp-element-caption">非对齐的前端取指是什么，为什么可以提高供指带宽</figcaption></figure>



<p>支持非对齐的取指是挺不错的，因为即使分支跳转到一个缓存行的中间，也可以维持高取指带宽。但不知道 Ventana 是如何实现这一点的。通常，非对齐访问是通过在底层进行两次访问并合并输出的数据来处理的。数据缓存通常有足够的 SRAM 带宽，可以每个周期这样做。但是一个指令缓存能够做到，尤其是一个512KB的缓存，感觉有点过分。</p>



<p>译者：这里就发现 Ventana 这种设计的问题了，目前各家 CPU 支持非对齐的取指都是通过较细粒度的分 bank 来实现，比如 Apple 和 AMD。然而 L2 通常需要追求高密度，做太细粒度的分 bank 不太合适。观察 Ventana 放出的 floorplan，他们 L2 的分 bank 粒度也较粗，猜测可能就是 64B 或者更大。这样会带来新的功耗问题，因为每周期都需要访问两个这么大的 SRAM，还需要在前端处理 128B 的裁切，这个开销可不小。这个其实也和 Ventana 给出的功耗数据相印证，说明他们的功耗还有很大的优化空间。</p>



<h2 class="wp-block-heading">巨大的单级 TLB </h2>



<p>接着减少缓存层级的主题，Ventana 选择了单级指令和数据 TLB。每个都有 3K 项，与其他家的大 L2 TLB 容量相匹配。作为比较，AMD 的 Zen4 有一个3072 项的 L2 dTLB，而英特尔的 Golden Cove 有一个 2048 项的 L2 TLB。 TLB（地址翻译缓冲器）是虚拟地址到物理地址转换的缓存。在你的电脑上运行的程序通过虚拟地址访问内存，这些地址通过操作系统管理的页表映射到物理地址。通常，每个内存访问都需要一个地址转换，因此 CPU 有一个小但高度优化的L1 dTLB，它记住了最常访问页面的转换。</p>



<p>在指令方面，512KB 的 L1/L2 指令缓存的访问需要同时访问 L1 iTLB。这意味着大多数指令访问将会引起大 iTLB 的查找。因为代码通常顺序执行直到遇到分支，所以一个单读口的 TLB 应该足以满足多条指令。</p>



<p>在数据方面，Ventana 删除了L1 dTLB，并且采用了虚拟地址 L1 DCache。主 TLB 在通向 L2 的路上被访问，使其不需要像传统L1 dTLB那样对延迟和功耗敏感。</p>



<p>译者：这个其实也很不利于功耗的控制，Ventana 看起来就和香山的开发团队一样，首先追求性能，然后才是功耗面积等。这也不难理解，毕竟现在在 PPT 上性能最吸引人。</p>



<h2 class="wp-block-heading">VIVT 的 L1 数据缓存</h2>



<p>Veyron V1 有一个 64KB 的数据缓存。它是虚拟地址索引和虚拟地址标记的（VIVT），与其他CPU中使用的虚拟索引、物理标记的（VIPT）缓存不太一样。VIPT 缓存的重要之处在于它的行为和纯粹的物理地址缓存是一样的。传统上，虚拟地址的缓存有很多缺点：它们会在上下文切换或 TLB 维护操作时被刷新，因为更改虚拟地址到物理地址的映射意味着一个虚拟地址的缓存的内容不再是最新的。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="431" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-7.png" alt="" class="wp-image-1503" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-7.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-7-300x168.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>然而，Ventana 尝试通过在 L1d 标签中跟踪 ASID 来避免这些缺点。这让他们可以避免在上下文切换时刷新 L1 数据缓存。此外，L1d 可以处理多个虚拟地址映射到同一个物理地址的情况。也许 V1 在 L2 TLB 中记录一定数量的 VA 到 PA 别名，并在 snoop 请求出现时一次性使这些别名失效。</p>



<p>V1 的 L1d 仍然有四个周期的延迟，与 AMD 和 ARM 近期的 CPU 非常相似。他们没有从使用虚拟地址的 L1d 中获得任何延迟优势，但他们避免了维护 L1 dTLB 的功耗和面积开销。Ventana 在使用 VIVT 缓存上面临不少质疑，但我认为他们会没问题的。他们显然已经努力避免了虚拟地址缓存带来的最大问题。即使这种做法最终的缺失率高于同等大小的 VIPT L1d，那么他们 L2 的命中延迟其实也相对较低，只有 11-12 个周期，不会有太大的问题。</p>



<p>译者：这个 VIVT 缓存肯定是引起很多质疑的，因为历史上使用 VIVT 缓存的设计好像都活得不久。现在的操作系统似乎也很少认为修改页表会引起数据缓存上的开销。更别说这种设计还需要新的一致性处理机制，我猜测他们的验证成本会比较高。</p>



<p>译者：我还是很难理解 Ventana 这个减少各种 memory 的层级的设计，感觉并没有给他们带来什么可观的好处。如果他们的 L1d 延迟能进一步降低（降低一周期），我觉得用 VIVT 缓存 + 单级 TLB 就可以说是值得的。但是他们大费周章最终并没有体现在性能上，这就让我感觉不太值得。也许他们这个结构还有降低一拍的潜力，只是还未解决工程上的问题。 </p>



<h2 class="wp-block-heading">乱序执行引擎</h2>



<p>通常 CPU 会有多种执行端口。这是因为不同的执行单元专门用于不同的操作，而且足够的执行端口必须为 CPU 可能遇到的不同指令组合提供服务。Ventana 选择使用更少、更灵活（功能更多）的端口。尽管重命名宽度是8，Veyron V1 只有四个整数和访存共用的执行端口。直觉上，核心的吞吐量会受到缺乏执行资源的限制。</p>



<p>然而，即使前端的宽度很宽，执行资源很少被持续完全利用，因为在今天的高性能 CPU 中，吞吐量通常受到内存延迟的限制。Sandy Bridge 是一个 Intel 的例子，它的端口数量也相对较少。即便有时会遇到端口争用，也是因为某些工作负载压迫了特定端口后面的执行单元，而不是CPU整体的执行端口数量不足。</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="688" height="355" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-8.png" alt="" class="wp-image-1518" style="width:850px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-8.png 688w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-8-300x155.png 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<p>因此，Veyron 出于减少功耗和面积的考虑，选择使用四个被充分利用的整数执行端口，而不是更多利用率较低的执行端口。</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="768" height="113" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-9.png" alt="" class="wp-image-1519" style="width:1020px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-9.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-9-300x44.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>虽然 Veyron 的执行端口布局在 HotChips 上肯定引起了一些关注，但我不认为它有什么不寻常的。其他 CPU 通过让端口服务不同的指令类别也减少了执行端口的数量。英特尔长期以来将浮点和整数执行单元放在同一个端口后面。富士通的 A64FX 也有两个端口同时服务 AGU 和 ALU。</p>



<p>这种方法的好处也很明显。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>（Zen4 的向量寄存器堆）自寄存器堆的面积主要受读写端口的宽度和数量限制，而不是存储的限制以来，仅有很小的增长。 </p>
<cite>Kai Troester 在 HotChips 2023 上，关于 Zen4 的 AVX-512 实现</cite></blockquote>



<p>更多的端口意味着要么有更多的调度队列，要么使用更复杂的调度器，可以每周期选择多个操作。寄存器堆的面积是另一个考虑因素，更多的执行单元/发射端口就需要更多的寄存器堆端口来满足它们。避免这种面积增大是一个明智的举动。</p>



<h2 class="wp-block-heading">面积</h2>



<p>面积对 Ventana 来说也是重要的，因为他们的目标是高核心数量，减少面积有助于降低成本。Veyron V1 的面积小于 AMD 的 Zen4c 和 ARM 的 Neoverse V2。Ventana 平衡面积的设计决策在此起了作用，但 Veyron V1 为了减少面积使用也牺牲了性能：它不支持向量扩展，仅有一个标量浮点流水。相比之下，AMD 的 Zen4c 具有非常高的向量和标量浮点吞吐量。与 Neoverse V2 相比，Veyron V1 的 L2缓存容量较小。Ventana 无疑实现了良好的核心面积效率，但其非传统的设计决策使它至少在面积方面不是 Game Changer。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="768" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-10.jpg" alt="" class="wp-image-1521" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-10.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-10-300x300.jpg 300w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-10-150x150.jpg 150w" sizes="(max-width: 768px) 100vw, 768px" /><figcaption class="wp-element-caption">与其他在 HotChips 2023 上展示的 CPU 核的对比</figcaption></figure>



<p>ARM 的 Neoverse N2 是另一个有趣的比较。它不如 Veyron V1 宽，具有较小的 BTB 和 TLB。V1 可能在整数应用上表现得更好，特别是如果那些代码具有较大的指令侧内存占用。但 N2 也有优势，比如一个非常基础的 2×128b 向量流水配置。</p>



<h2 class="wp-block-heading">Cluster 面积</h2>



<p>核心面积只是一部分，最近的手机和服务器 CPU 在互连和缓存这样的外围逻辑上花费了大量的面积。AMD 的 Bergamo 可以作为一个有趣的比较，因为 Ventana 计划使用类似 AMD 的 hub-spoke chiplet 配置来扩展到大量的核心。也就是说，Veyron V1 和 Zen4c 都使用计算模块连接到中心 IO 模块的这种封装形式。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="431" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-10.png" alt="" class="wp-image-1527" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-10.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-10-300x168.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>Ventana 在他们的 HotChips 演讲中提供了一个 16 核心 Veyron V1 Cluster 的图片，以及在 TSMC 5nm 工艺上的面积数据。AMD 也在他们的演讲中提供了一个 Zen4c 16 核心 CCD 的照片。在这里使用了 Fritzchens Fritz 的 69.49 平方毫米的数据作为 Zen4 CCD 的大小用于面积比较，并假设 Zen4c 的 CCD 占用类似的面积。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="431" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-11.jpg" alt="" class="wp-image-1528" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-11.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-11-300x168.jpg 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>乍一看，具有缓存和支持互连逻辑的 16 个 Veyron V1 核心相对于 AMD 的 Bergamo 有轻微的面积优势。然而，仔细查看 die shot 显示，Ventana 没有在他们的图片中包含芯片到芯片的接口。这种接口将占用大量的面积。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="438" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-11-1.jpg" alt="" class="wp-image-1530" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-11-1.jpg 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-11-1-300x171.jpg 300w" sizes="(max-width: 768px) 100vw, 768px" /><figcaption class="wp-element-caption">通过 GIMP 将 CPU 核，L3 slice 和互联部分合并到一起</figcaption></figure>



<p>因此，Veyron V1 的计算模块大小可能与 AMD Bergamo 的不相上下。Veyron V1 每核享有更多的 L3 缓存容量：每核3MB，相比之下 AMD 是每核2MB。Ventana 的 Cluster 内互连还覆盖了 16 个核心及其关联的 L3 Slice，为任意核心提供 48MB 的 L3 容量。对 AMD 来说，他们的 Zen4c 核心拥有更灵活的统一 L2 缓存和更强大的浮点和向量执行能力。</p>



<h2 class="wp-block-heading">Cluster 内互连</h2>



<p>Ventana 使用类环形的互连。他们在演示上说有两个环，但在 2.5GHz 频率下 160GB/s 的对分带宽意味着每周期 64B。如果环每周期可以传32B，那么可能就只有一个环，然后环两侧都在传输。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="432" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-11.png" alt="" class="wp-image-1532" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-11.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-11-300x169.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>也许它是双环配置，然后每周期只能移动 16B。我不完全确定 Ventana 的互连在这里是什么样子，但基于环的互联往往能提供相当好的 L3 性能。希望Ventana 在这里做得很好，因为他们的分离的 512+512KB 的 L2 设计可能意味着 L3 看到的流量比具有 1MB 统一 L2 的设计更多。</p>



<h2 class="wp-block-heading">写在最后</h2>



<p>Veyron V1 有一套非常独特的设计决策。偏离常规是有风险的，因为历史上工程师已经弄清楚了在 CPU 设计上什么有效以及什么无效。这就是为什么成功的 CPU 有很多共同的设计特点。Ventana 似乎做得相当不错，他们没有落入任何明显的漏洞。他们通过硬件别名处理和 ASID 过滤缓解了 VIVT 缓存带来的问题。他们的分离 L2 设计在指令和数据两侧都有足够的缓存容量。他们大型的单级 BTB 和 TLB 要么具有足够低的延迟不会阻碍性能，要么不在性能关键路径上。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="768" height="432" src="https://blog.eastonman.com/wp-content/uploads/2024/02/image-12.png" alt="" class="wp-image-1533" srcset="https://blog.eastonman.com/wp-content/uploads/2024/02/image-12.png 768w, https://blog.eastonman.com/wp-content/uploads/2024/02/image-12-300x169.png 300w" sizes="(max-width: 768px) 100vw, 768px" /></figure>



<p>尽管 Veyron V1 是 Ventana 的首款设计，它有望成为在标量整数应用领域的强力竞争对手。但 Ventana 在处理吞吐量密集型应用时将遇到更大的挑战。由于 V1 缺乏向量单元，即便 V2 搭载了向量单元，RISC-V 生态系统的成熟程度仍旧落后于 ARM，这就需要软件来支持 RISC-V 的向量扩展功能。许多吞吐量受限的软件如视频编码器、渲染器和图像处理应用是由社区维护的，而不同社区的对支持新指令集的优先程度不同，这使得对新指令集扩展的支持通常进展缓慢。ARM在努力成为x86可行替代品的过程中已经遇到了软件支持的难题，而RISC-V在这方面面临的挑战则更为严峻。</p>



<p>译者：Ventana 这个核心从开发的角度上来说感觉与香山处在类似的阶段，但是商业化上比香山做得更早更好。但是不知道是不是因为不寻常的设计决策拉长了验证周期，感觉 Ventana 的 IP 在进入硅的速度上比 SiFive 慢一点。</p>



<p>我们要感谢 Ventana 为他们的 Veyron V1 核心整理了非常好的演讲，并期待看到该公司在即将推出的 V2 设计上做了些什么。</p>



<p></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文大部分翻译自 <a href="https://chipsandcheese.com/" target="_blank" rel="noopener" title="">ChipsAndCheese</a>，添加有个人理解和资料补充 ，原文为英文，原文链接：<a href="https://chipsandcheese.com/2023/09/01/hot-chips-2023-ventanas-unconventional-veyron-v1/" target="_blank" rel="noopener" title="">https://chipsandcheese.com/2023/09/01/hot-chips-2023-ventanas-unconventional-veyron-v1/</a> 原作者：<a href="http://chlamchowder" target="_blank" rel="noopener" title="">chlamchowder</a></p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>本文中文翻译首发自我的博客 <a href="https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/" target="_blank" rel="noopener" title="">https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/</a> ，翻译已取得 ChipsAndCheese 编辑/原作者授权。因英文原文无公开的授权协议，本译文也禁止转载，如需转载请先联系我或 ChipsAndCheese。</p>
</blockquote>The post <a href="https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/">HotChips 2023: Ventana 不寻常的 Veyron V1</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2024/02/hot-chips-2023-ventanas-unconventional-veyron-v1/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>现代分支预测：从学术界到工业界</title>
		<link>https://blog.eastonman.com/blog/2023/12/modern-branch-prediction-from-academy-to-industry/</link>
					<comments>https://blog.eastonman.com/blog/2023/12/modern-branch-prediction-from-academy-to-industry/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Sun, 31 Dec 2023 12:51:26 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[计算机系统]]></category>
		<category><![CDATA[Branch Prediction]]></category>
		<category><![CDATA[CPU]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=1282</guid>

					<description><![CDATA[<p>预计阅读时间： 24 分钟 Branch Prediction Is Not A Solved Problem [&#8230;]</p>
The post <a href="https://blog.eastonman.com/blog/2023/12/modern-branch-prediction-from-academy-to-industry/">现代分支预测：从学术界到工业界</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 24 分钟</p>
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Branch Prediction Is Not A Solved Problem</p>
<cite>&#8211;Intel</cite></blockquote>



<p>我从 2022 年底开始在<a href="https://github.com/OpenXiangShan" target="_blank" rel="noopener" title="香山开源高性能处理器">香山开源高性能处理器</a>团队里实习，到现在已满一年了，一直主要负责前端取指模块中分支预测单元的改进。到了年底了，略有一点空闲时间来写一下目前为止在分支预测方面的见闻。</p>



<p>本文假定读者对数字电路设计和高性能 CPU 设计都有一定了解，知道什么是“分支预测”以及“分支预测”对于高性能 CPU 来说的重要意义，如果你完全不了解这部分内容，但又感兴趣的话，建议先去看一些相关的基础书籍和文章。</p>



<p>另外本文基本上仅探讨条件分支的预测，也即只进行 T/NT 的方向预测，其他类型的分支如果大家感兴趣我以后再写。</p>



<p>希望本文能被一些刚参加完龙芯杯，或者通过一生一芯学习的同学阅读。如果想要尝试做分支预测器方面的工作，或者到香山前端团队来实习，那么在雄心壮志开始准备大战预测算法提高准确率之前，请先停下来了解一下真正的高性能处理器设计对分支预测的需求。</p>



<h2 class="wp-block-heading">分支预测发展历史</h2>



<p>最古老的分支预测方法就是饱和计数器，这里假设读者已经充分了解什么是饱和计数器和用饱和计数器来预测分支的方法了。</p>



<p>饱和计数器只能作出偏向性的预测，并不能充分利用单个分支内部和分支之间的关联信息，因此很快就发展出了能够利用分支历史信息的预测器，例如 GShare 预测器:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="567" height="309" src="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.01.12.png" alt="" class="wp-image-1293" srcset="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.01.12.png 567w, https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.01.12-300x163.png 300w" sizes="(max-width: 567px) 100vw, 567px" /><figcaption class="wp-element-caption">GShare预测器示意图<br>来自 <a href="https://courses.cs.washington.edu/courses/cse548/05wi/files/Patt-An-Analysis-of-Correlation-and-Predictability.pdf" target="_blank" rel="noopener" title="">An Analysis of Correlation and Predictability:<br>What Makes Two-Level Branch Predictors Work</a></figcaption></figure>



<p>GShare 预测器将分支的 PC 与一个全局历史寄存器 XOR 以后作为 index 来选择 PHT 中的一个饱和计数器，然后使用这个计数器给出预测方向，更新时同样也通过同样的方式索引到同一个饱和计数器并更新。</p>



<p>这种方式将不同全局分支历史下的同一条分支的预测分给了不同的饱和计数器，使得 GShare 预测器能够区分不同的历史。这样做带来的第一个好处是能够正确预测循环退出了。</p>



<p>另一个好处是，分支之间的相关性可以得到有效利用，下图是常见的分支间相关性的例子</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="549" height="423" src="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.05.05-edited.png" alt="" class="wp-image-1297" srcset="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.05.05-edited.png 549w, https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.05.05-edited-300x231.png 300w" sizes="(max-width: 549px) 100vw, 549px" /><figcaption class="wp-element-caption">分支间相关的例子，来源同上面的图</figcaption></figure>



<p>以上的两个例子恰好对应局部分支历史和全局分支历史。循环退出的预测只需要单个 PC 的历史就可以解决，而分支间相关的例子则必须要全局的分支历史。可见不同的分支需要的历史是不同的，由此发展出了锦标赛预测器，由局部历史和全局历史的两个预测器分别独立预测，然后再由一个饱和计数器构成的选择结构选择使用哪个子预测器提供的方向预测。著名的 Alpha 21264 处理器就是使用了锦标赛预测器。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="704" height="414" src="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.24.28.png" alt="" class="wp-image-1300" srcset="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.24.28.png 704w, https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.24.28-300x176.png 300w" sizes="(max-width: 704px) 100vw, 704px" /><figcaption class="wp-element-caption">Alpha 21264 的分支预测器，来自其论文</figcaption></figure>



<p>Alpha 21264 是经典的超标量乱序处理器，它的论文值得大家一看 <a href="https://ieeexplore.ieee.org/document/755465" target="_blank" rel="noopener" title="">The Alpha 21264 microprocessor</a></p>



<h2 class="wp-block-heading">现代分支预测算法</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Branch prediction research is basically about separating easier and more difficult branches, and using a simple predictor for the easy branches and a complex predictor for the difficult ones.</p>
<cite>&#8212; Onur Mutlu (Computer Architecture and Digital Design, ETHZ)</cite></blockquote>



<p>现代的分支预测器算法只有一种——TAGE，几乎所有的学术研究和商业高性能处理器都使用 TAGE 或者 TAGE 的变种。TAGE 预测器是最早在 2006 年由 Andre Seznec 提出的，提出当时即获得当届分支预测锦标赛冠军。然后接下来直到 2016 年的所有分支预测锦标赛都由TAGE预测器的变种获得。</p>



<p>我在 2022 年参加龙芯杯比赛复现 TAGE 预测器的时候，对 Mutlu 教授在课程中的这句话感受颇深。分支本身的预测难度不尽相同， 如果使用复杂的分支预测器预测简单的分支，那么大概率会浪费存储空间。TAGE 预测器的设计和这一个思想不谋而合，它使用多个不同历史长度的子预测器，并且使用一套机制自动地在这些子预测器中分配表项。这样能够做到使用短历史预测简单分支，长历史预测复杂分支，同时也能够避免存储空间的浪费。</p>



<p>以下是 TAGE 预测器的框图</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="810" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-09.44.12-1024x810.png" alt="" class="wp-image-1316" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-09.44.12-1024x810.png 1024w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-09.44.12-300x237.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-09.44.12-768x607.png 768w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-09.44.12-1536x1215.png 1536w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-09.44.12.png 1808w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>对预测机制细节感兴趣的读者可以参考原论文 <a href="https://jilp.org/vol8/v8paper1.pdf" target="_blank" rel="noopener" title="">A case for (partially) <strong>TA</strong>gged <strong>GE</strong>ometric history length branch prediction</a></p>



<h2 class="wp-block-heading">解耦合前端与覆盖重定向</h2>



<p>像 TAG E这种复杂程度的预测器是没有可能当拍就能得到预测结果的，本文撰写的时期（2023年）附近的半导体工艺对于 SRAM 微缩已经遇到了困难，目前 SRAM 访问就需要接近一个时钟周期或者一个完整的时钟周期了。另外考虑到每个 PC 都访问复杂预测器在功耗上的开销，商业处理器也很少做非常激进的省拍策略（如提前流水等）。</p>



<p>故 TAGE 预测器的返回结果到下一次的预测之间就出现了空拍，这是难以接受的，对性能的影响非常大。所以很自然的就出现多级预测器——覆盖重定向这种策略。这种设计一般使用简单的预测器进行 zero-bubble 的预测，然后再使用复杂预测器对简单预测器的结果进行验证，如果预测不一致，就会发出一个前端重定向，重新开始一个预测流水。这样的做法可以在简单预测器准确率较高的情况下，省去复杂预测器的访问延迟。</p>



<p>下图是一个 SiFive P870 的设计，可以明显得看出具有 Cascading BP 的的设计。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="406" src="https://blog.eastonman.com/wp-content/uploads/2023/12/image-1024x406.png" alt="" class="wp-image-1318" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/image-1024x406.png 1024w, https://blog.eastonman.com/wp-content/uploads/2023/12/image-300x119.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/image-768x305.png 768w, https://blog.eastonman.com/wp-content/uploads/2023/12/image-1536x610.png 1536w, https://blog.eastonman.com/wp-content/uploads/2023/12/image.png 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>覆盖重定向的设计带来了新的问题——预测流水和取指流水，也即 ICache 流水，的速度不匹配。预测流水出现前端重定向的时候，ICache 可能仍能取指，ICache 出现缺失的时候，BP 就处于阻塞状态。为了避免相互影响，现代高性能处理器普遍采用了解耦合前端（或称为分离式前端），只有少量的处理器设计采用传统的设计。</p>



<p>下图是高通在 HPCA 2019 上发表的论文 <em><a href="https://ieeexplore.ieee.org/document/8675212" target="_blank" rel="noopener" title="Elastic Instruction Fetching">Elastic Instruction Fetching</a></em> 中的配图</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="521" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-10.18.35-1024x521.png" alt="" class="wp-image-1323" style="width:694px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-10.18.35-1024x521.png 1024w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-10.18.35-300x153.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-10.18.35-768x390.png 768w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-10.18.35-1536x781.png 1536w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-10.18.35.png 1546w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">学术与产业落地</h2>



<p>我曾经一度认为商业公司秘密发展了新的分支预测算法，而且不予公开，并且感到十分不高兴。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="524" height="128" src="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.32.07.png" alt="" class="wp-image-1305" srcset="https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.32.07.png 524w, https://blog.eastonman.com/wp-content/uploads/2023/10/Screenshot-2023-10-10-at-20.32.07-300x73.png 300w" sizes="(max-width: 524px) 100vw, 524px" /><figcaption class="wp-element-caption">年轻的我.jpg</figcaption></figure>



<p>后来我很快认识到工业界中使用的分支预测算法非常的保守，甚至大部分的商业处理器都只使用了 TAGE 预测器或者 TAGE-L 等价的预测器。</p>



<p>香山处理器的分支预测器也从雁栖湖的 TAGE-SC-L 变成了南湖和昆明湖的 TAGE-SC，然后甚至正在考虑去掉 SC。</p>



<p>这样的情况可能和很多人的认识相悖，尤其是刚参加完龙芯杯或者一生一芯学习的同学。也和一些刚毕业的研究生或者长时间从事学术研究的人的认识有冲突。分支预测准确率对于高性能处理器的影响是非常巨大的，为什么这些在相同存储下能够取得更好预测准确率的算法没有被工业界采用？</p>



<p>接下来我们就讨论一下工业界对于分支预测算法更关注什么？</p>



<h2 class="wp-block-heading">工业界关注什么？</h2>



<p>首先，工业界和学术界的区别在哪里？学术界相信大家都很好理解，只要一个算法在相同的存储空间下能够取得更好的准确率，就可以认为这是一个更好的算法。但是在商业高性能处理器的设计中，还必须要考虑实际的延迟，以及性能、功耗、面积的平衡。</p>



<h3 class="wp-block-heading">更新延迟</h3>



<p>首先是一个非常明显的问题，部分学术界的研究也已经涉及到了，那就是在真实处理器中，分支预测器的更新只能在后端执行完毕以后才能拿到准确的分支执行情况。现代的预测算法都依赖分支的历史，如果历史不准确，将会极大地影响预测的准确率。然而下一条分支的预测开始时，上一条分支还远没有执行完毕，现代处理器中 in-flight 的分支数量普遍可达数十条。那么下一条分支如何拿到前一条分支的方向用于历史的计算？</p>



<p>目前普遍采用的做法是推测更新历史，也就是在预测后立即更新分支历史供下一条分支使用，然后在分支误预测发生时对历史进行恢复。这个做法能够较好的解决历史不及时的问题，但是 Pattern History Table（PHT）的更新依然可以是可以探讨的设计。是在分支执行完就更新？还是等到 commit 以后才更新？总体来说越早更新 warmup 越快，越晚更新更新得越准确。</p>



<p>推测更新历史的做法就引起了下一个问题——为什么现代处理器的分支历史都是全局历史？前文刚刚讨论了局部历史和全局历史对分支预测有不同的贡献，给任何预测器加入局部历史几乎都可以进一步提高预测准确率，那么为什么没有人用？只有部分处理器使用了专门的 Loop 预测器（Loop iteration count 就是一种特殊的局部历史）。这就涉及局部历史推测更新后的恢复困难问题。</p>



<h3 class="wp-block-heading">历史维护</h3>



<p>局部历史维护的困难可以参考 MICRO 2019&#8217;的论文 <em><a href="https://dl.acm.org/doi/10.1145/3352460.3358315" target="_blank" rel="noopener" title="Towards the adoption of Local Branch Predictors in Modern Out-of-Order Superscalar Processors ">Towards the adoption of Local Branch Predictors in Modern Out-of-Order Superscalar Processors </a></em>。</p>



<p>因为全局历史整个处理器只需要维护一份（或少量几份用于推测更新-恢复用），一般都直接使用寄存器来存储全局历史。但是局部历史是需要按照分支来维护，即便是只维护部分活跃的分支，也需要较大的表，因此几乎必须使用 SRAM 来存储。</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1020" height="892" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-11.02.03.png" alt="" class="wp-image-1334" style="width:464px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-11.02.03.png 1020w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-11.02.03-300x262.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-11.02.03-768x672.png 768w" sizes="(max-width: 1020px) 100vw, 1020px" /></figure>



<p>上图是按照分支预测锦标赛中软件实现的 Loop 预测器设计的对应硬件的框图，可以看到需要一个 Branch History Table（BHT）用于记录目前的循环计数。</p>



<p>使用 SRAM 存储带来的问题就是，恢复历史时无法在一拍内恢复或者甚至无法在短时间内恢复。目前已有的几种解决办法是</p>



<ul class="wp-block-list">
<li>维护 in-flight queue，预测时同时查询 queue 获得新的历史，SRAM 内仅存储 commit 后的分支信息，不需要恢复 SRAM，对预测时序和功耗影响大。</li>



<li>维护 recover queue，SRAM 内推测更新历史，恢复时通过 walk queue 来恢复 SRAM 内的信息，对恢复速度要求高。</li>



<li>设计机制，让 SRAM 内历史不准确的时候不参与预测，可以较慢恢复 SRAM 内的信息。</li>
</ul>



<p>更多设计细节的评估和对比可以参考上面的论文。</p>



<p>这种维护的困难使得局部历史在现代处理器的分支预测器中很少见到。</p>



<h3 class="wp-block-heading">Path History</h3>



<p>无论是哪一种分支历史，由于推测更新的维护方式，都必须先出现在 BTB 中，才能进入历史。然而，正常程序中会出现大量从不跳转的分支（如 debug 或错误处理），没有人愿意为了存储这些分支耗费多几倍的 BTB 存储开销。现代处理器普遍只有曾经跳转过的分支才会存储进入 BTB 中，这给维护 Branch History 带来了困难，如果有新分支突然 taken，那么它附近的分支由于历史的变化，基本需要重新 warmup。有的处理器为了让有限的历史寄存器放入更多有意义的信息，可能会对分支做进一步的过滤，只有 T/NT 均出现至少一次的分支才会进入历史，这样需要重新 warmup 的概率也就上升了。</p>



<p>因此工业界更倾向于使用 Path History。Path History就是将跳转的 Target 经过哈希计算进入历史中，这种历史天然地不受不跳转的分支影响。通过论文观察到 ARM 和 Intel 都使用了 Path History 来代替 Branch History。</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="818" height="136" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.55.28.png" alt="" class="wp-image-1339" style="width:517px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.55.28.png 818w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.55.28-300x50.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.55.28-768x128.png 768w" sizes="(max-width: 818px) 100vw, 818px" /><figcaption class="wp-element-caption">ARM的PHR</figcaption></figure>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="852" height="246" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.17.png" alt="" class="wp-image-1340" style="width:513px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.17.png 852w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.17-300x87.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.17-768x222.png 768w" sizes="(max-width: 852px) 100vw, 852px" /><figcaption class="wp-element-caption">Intel PHR 中 footprint 的哈希</figcaption></figure>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="826" height="224" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.22.png" alt="" class="wp-image-1341" style="width:546px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.22.png 826w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.22-300x81.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-16.56.22-768x208.png 768w" sizes="(max-width: 826px) 100vw, 826px" /><figcaption class="wp-element-caption">Intel 的 PHR 历史维护</figcaption></figure>



<h3 class="wp-block-heading">主频 &amp; 预测延迟</h3>



<p>如果说前面的几个方面只是增加了复杂分支预测算法落地的难度，那么预测延迟就是阻碍复杂算法落地的直接障碍。举一个例子，TAGE-SC-L 预测器中的 Statistical Corrector（SC）是为了纠正 TAGE 对于一些偏向性大的分支预测不好的部件，同时 SC 还可以作为 TAGE 的补充，如果 TAGE 由于哈希或者别的 corner case 出现失效，那么 SC 可以在一定程度上挽救问题。</p>



<p>但是 SC 需要使用 TAGE 的结果作为输入，还需要做所有表项的加法规约，因此在现代处理器的主频下，延迟比 TAGE 多1-2拍。这样多出来的延迟可能就把将存储分给 SC 的优势给抵消了，如果 SRAM 时序允许的话，给 TAGE 增加额外的 10% 的空间可能能取得更好的效果。SC 增加的延迟在学术上是欠评估的，因为 SC 提出的场景是分支预测锦标赛，该比赛并不要求模拟实际的预测延迟，因此复杂的预测算法更有可能因为合理的存储空间分配而获得更好的效果。</p>



<p>根据我的观察，SC 目前在大的商业处理器上应用很少，可能是加入这个部件增加的功耗、面积、验证成本等已经超过了它带来的误预测率降低的好处了。</p>



<h3 class="wp-block-heading">Multi-branch ahead</h3>



<p>根据统计，桌面应用的平均基本块长度约在5条指令附近（4-10），因此在设计宽译码、宽发射的高性能处理器时，如果每周期只预测一条分支，那么供指能力是不足的。现代高性能处理器普遍具备每周期预测两条指令的能力，但是根据设计能力和目标的不同，一般对两条指令的性质会有一些限制。最常见的是要求本周期的第一条指令不能跳转，这种限制大大降低了设计难度，因为在这样的限制下，两个基本块是连续的，每周期只会产生一个新的 PC，不需要做大量额外的设计就可以支持。</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="551" src="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-17.21.24-1024x551.png" alt="" class="wp-image-1344" style="width:552px;height:auto" srcset="https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-17.21.24-1024x551.png 1024w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-17.21.24-300x161.png 300w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-17.21.24-768x413.png 768w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-17.21.24-1536x827.png 1536w, https://blog.eastonman.com/wp-content/uploads/2023/12/Screenshot-2023-12-31-at-17.21.24.png 1758w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">平均基本块长度和平均跳转长度的对比（注意单位是Bytes）</figcaption></figure>



<p>从上图中可以看到，基本上仅支持这种限制较严格的 2 Branch per cycle 就可以提升每周期的平均供指数量。香山处理器昆明湖目前也仅支持这种情况，实际在 SPEC CPU 2006 上可以做到平均每周期6-7条指令的供指能力。</p>



<p>近年来也有处理器能够支持 2-Taken per cycle的情况，如 ARM 和 Intel 的最新产品，但同时也有对两条分支的类型或者支持的数量做限制。</p>



<h3 class="wp-block-heading">BTB设计</h3>



<p>BTB 设计也是重要的一环，目前的设计方向有以下几个：</p>



<ul class="wp-block-list">
<li>多级 BTB</li>



<li>下级 BTB 与 Cache 共用存储</li>



<li>BTB 压缩</li>



<li>L0 BTB / 快速预测器的设计</li>



<li>L1 BTB 的类型</li>
</ul>



<p>其中除了 L1 BTB 的类型，其他的都比较好理解。那么为什么需要单独讨论 L1 BTB 的类型呢？因为 L0 BTB 大多使用寄存器，排布灵活，L2 BTB 有压缩等设计，类型也可以灵活。唯独 L1 BTB 在预测的关键路径上，不能使用复杂排布，又需要足够大的容量，因此需要使用 SRAM，对读写有较为严格的限制。因此 L1 BTB 的表项设计和索引方式是前端设计中的重点，这里的设计也会大幅影响 TAGE 预测器和整个前端的设计。</p>



<p>今年的 MICRO 2023&#8217;恰好有一篇BTB的类似综述文的文章 <em><a href="https://dl.acm.org/doi/10.1145/3613424.3623774" target="_blank" rel="noopener" title="Branch Target Buffer Organizations">Branch Target Buffer Organizations</a></em> ，感兴趣的读者可以找来看看。其中有对 L1 BTB 的三种常见类型的描述和对比</p>



<ul class="wp-block-list">
<li>I-BTB：紧耦合前端，使用 ICache+预译码充当大容量 BTB。采用这样设计的有苹果和 SiFive。这种设计的优点是不需要为 BTB 使用额外的存储，缺点是紧耦合前端供指能力稍弱，另外 ICache 存储分支的效率是较低的，超过 ICache 容量之后，将无法使用分支信息指导指令预取。</li>



<li>R-BTB：使用减少的空间来存储一定范围内的分支（如 64B），在范围内支持一定数量（如8条）分支，BTB 使用对齐的 PC 索引，通过 offset 来确定具体的分支。优点是使用独立的 BTB 和解耦前端后，前端对分支的处理能力大幅加强，IBM 的 z15 处理器设计中，BTB 的总容量甚至可以 cover L2 Cache 的大小。缺点是当分支密度高的时候无法全部覆盖。</li>



<li>B-BTB：每个表项内存储一个基本块（或一个 Fetch Stream）的信息（下称取指块），取指块内可以有多个分支，有最长的取指块大小，使用 PC 低位索引，高位做 Tag。这种做法解决了 R-BTB 对分支密度的限制，能够处理绝大多数的情况。缺点是一个分支可能出现在多个表项内，会浪费一定的存储空间。</li>
</ul>



<p>香山南湖和昆明湖的设计都是使用 B-BTB 的设计，取指块中允许两个 Branch Slot，第一个仅允许条件分支，第二个允许所有类型的分支。然后预测时如果第一个分支预测不跳转，那么可以按照离第二个分支的距离来供指。</p>



<h3 class="wp-block-heading">功耗</h3>



<p>功耗这一块其实我知之甚少，因为香山还没有开始做低功耗设计（狗头保命），但其实有许多大方向是低功耗设计共性的。首先，因为分支具有难度的差别，还有类型的差别，复杂的预测器和 ITTAGE 这类的间接跳转预测器是没有必要每个PC每个周期都访问的。另外 TAGE 中，每个历史表对于程序片段中的有用程度也是不一样的，所以通过统计的方法，是可以在保留大部分性能的情况下省去很多 SRAM 的功耗。</p>



<p>另外 SRAM 与 SRAM 天差地别，如果设计时给 SRAM 留了时序裕度，选用稍慢的 SRAM 可能可以大幅降低功耗。</p>



<h2 class="wp-block-heading">一定要预测吗？</h2>



<p>最后想谈一下从体系结构的角度，分支一定要预测吗？近年来分支预测算法发展陷入停滞，原因是 TAGE 预测器的算法已经足够优秀，能够在存储耗尽之前充分地挖掘一条分支的“可预测性”，后续的改进也基本针对 corner case 的优化和存储替换算法、置信度等的一些小优化。</p>



<p>那么，剩下的分支误预测怎么办，还有办法吗？此时就需要跳出固有的思维框架，不再仅思考纯硬件的做法，就会发现有新的天地。实际计算机的设计是一个系统工程，需要每一层紧密配合才能达到最好的性能。此处我们介绍两个已经实用的方法，有没有别的新的方法留待读者探究。</p>



<h3 class="wp-block-heading">谓词化指令</h3>



<p>谓词化指令其实就是赫赫有名的条件执行指令，例如 cmov 指令就是著名的谓词化指令。设计这种指令的初衷是很多难预测的分支是和程序数据紧密相关的，甚至直接依赖于数据，那么如果将这些分支转化为 cmov 或类似的条件转送/执行指令，就可以只出现数据流的变化，而不出现控制流的变化。</p>



<p>谓词化指令在 x86 体系和 ARM 体系中都有大量的使用，RISC-V 在 2023 年也通过了 Zicond 扩展。</p>



<h3 class="wp-block-heading">软硬结合</h3>



<p>这里讲的软硬结合大致是指 hint 类型的指令，苹果已有专利说明其设计的处理器中，有针对 macOS/iOS 进程间调用返回的长跳转做 hint 和指令预取的优化，具体的优化只能翻专利了。</p>



<h2 class="wp-block-heading">结语</h2>



<p>其实还有更多的具体设计细节，这里由于篇幅考虑（<s>实际是我偷懒</s>）就不详细展开了，在这里提一个列表共大家思考</p>



<ul class="wp-block-list">
<li>单端口 SRAM 的读写冲突处理</li>



<li>预测器 ctr 降低更新延迟的影响，加快 warmup 的方法</li>



<li>B-BTB 中的多个分支对于方向预测器容量需求不对称的问题</li>



<li>预测器中 SRAM 分 bank 的方法</li>



<li>TAGE 折叠历史的维护方法</li>



<li>各种预测器和 BTB 的索引哈希</li>
</ul>



<p>现在看了这么多设计考虑，你还认为预测准确率是分支预测器唯一的评价指标吗？</p>



<h2 class="wp-block-heading">参考文献</h2>



<ul class="wp-block-list">
<li><a href="https://ieeexplore.ieee.org/document/755465" target="_blank" rel="noopener" title="">The Alpha 21264 microprocessor</a></li>



<li><a href="https://jilp.org/vol8/v8paper1.pdf" target="_blank" rel="noopener" title="">A case for (partially) <strong>TA</strong>gged <strong>GE</strong>ometric history length branch prediction</a></li>



<li><a href="https://ieeexplore.ieee.org/document/8675212" target="_blank" rel="noopener" title="Elastic Instruction Fetching">Elastic Instruction Fetching</a></li>



<li><a href="https://dl.acm.org/doi/10.1145/3352460.3358315" target="_blank" rel="noopener" title="Towards the adoption of Local Branch Predictors in Modern Out-of-Order Superscalar Processors ">Towards the adoption of Local Branch Predictors in Modern Out-of-Order Superscalar Processors</a></li>



<li>Branch Target Buffer Organizations</li>



<li>Half&amp;Half: Demystifying Intel’s Directional Branch Predictors for Fast, Secure Partitioned Execution</li>



<li>Re-establishing Fetch-Directed Instruction Prefetching: An Industry Perspective</li>



<li>Rebalancing the core front-end through HPC code analysis</li>



<li><a href="https://xiangshan-doc.readthedocs.io/zh-cn/latest/frontend/bp/" target="_blank" rel="noopener" title="香山处理器分支预测文档">香山处理器分支预测文档</a></li>
</ul>The post <a href="https://blog.eastonman.com/blog/2023/12/modern-branch-prediction-from-academy-to-industry/">现代分支预测：从学术界到工业界</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2023/12/modern-branch-prediction-from-academy-to-industry/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>NSCSCC2022 龙芯杯参赛总结</title>
		<link>https://blog.eastonman.com/blog/2022/08/nscscc2022/</link>
					<comments>https://blog.eastonman.com/blog/2022/08/nscscc2022/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Thu, 25 Aug 2022 03:32:37 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[计算机系统]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[NSCSCC]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=1081</guid>

					<description><![CDATA[<p>我参加2022年龙芯杯的参赛总结</p>
The post <a href="https://blog.eastonman.com/blog/2022/08/nscscc2022/">NSCSCC2022 龙芯杯参赛总结</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 21 分钟</p>
<p>一转眼已经是八月下旬，暑假只剩一个星期了，<s>当然去年也是这样</s>。上一次写内容还是在年初，基本上半年以来的课外时间基本都投入在龙芯杯和相关的事情上了。龙芯杯就如字面上的意思是由龙芯中科公司和高等学校计算机教育研究委员会共同主办的一个赛事，赛事的主要内容是使用RTL设计一个完整的CPU，并在FPGA上进行验证和展示。2022年是龙芯杯的第六年，今年的比赛也和以往有所不同，新增了LoongArch挑战赛，我就是来吃螃蟹的人（<s>踩大坑</s>）</p>



<h2 class="wp-block-heading">新增赛道</h2>



<p>如上面所讲，2022年龙芯杯最大的不同就是除了往年的MIPS指令集的个人赛和团体赛，新增了LoongArch挑战赛赛道。这个新的赛道的赛制，日程和评价方式都和MIPS的赛道有相当大的区别，区别体现在：</p>



<ul class="wp-block-list"><li><strong>指令集区别</strong>：这个当然是最主要的区别，LoongArch挑战赛使用的是LA32 Reduced指令集，而以往的个人赛和团体赛都使用MIPS指令集。</li></ul>



<ul class="wp-block-list"><li><strong>日程区别</strong>：团体赛和个人赛分为初赛和决赛，2022年初赛在8月5日截止，决赛提交在8月19日截止，线上答题在8月19日，决赛答辩在8月20日。而LoongArch赛道没有区分初赛和决赛（<s>因为本来就没几支队伍</s>），性能测试的提交截止日期是8月14日，没有答辩，ppt展示视频的提交时间是8月18日.</li><li><strong>决赛门槛区别</strong>：MIPS团体赛的决赛门槛是性能分数，决赛队伍数量一般是较为恒定的，只要性能排名能够达到一定水平，就可以进入决赛。而LoongArch挑战赛虽然没有实质性的决赛，但是仍有一个门槛，就是<strong>启动Linux</strong>。</li><li><strong>评价方式区别</strong>：MIPS的团体赛最终评分是50%性能，50%系统展示和答辩，LoongArch则是70%性能，30%系统展示和答辩。</li><li><strong>官方提供的开发工具区别</strong>：MIPS团队赛只有提供功能测试、系统测试等一系列测试，而LoongArch提供了差分测试框架和随机验证的指令流，相比而言LoongArch的仿真验证手段更为先进。</li><li>一些细节要求的区别：比如LoongArch挑战赛性能测试样例不同，性能测试不允许修改SoC，也不允许使用自己编译的Linux。以及LoongArch允许研究生参赛。</li></ul>



<p>当然基本上参加比赛要完成内容是相近的，无非就是设计一个CPU，大部分本科生队伍都是遵循传统的5级流水线，在此基础之上增加流水级、增加顺序双发射、提高频率等等。能力强的队伍或者往届学长很强的队伍可以依照已有的框架进行部分的微架构创新，基本就是这样。</p>



<p>LoongArch指令集恰好也是在2022年大踏步进入开源社区，Linux、QEMU、GCC等诸多重要开源软件都在2022年加入了LoongArch的支持。于是接下来讲一讲龙芯这个LoongArch指令集与MIPS有什么不同。</p>



<p>首先是最明显的（<s>大快人心</s>）的变化就是去除了<strong>延迟槽</strong>。延迟槽是MIPS设计时对那个年代普遍具有的5级流水微架构做的<strong>过度设计</strong>，这种与微架构绑定的设计显然没有跟上飞速发展的硬件设计水平，在2022年看来，延迟槽这个东西属于无用且严重增加设计复杂度的指令集设计。LoongArch没有沿用这个诟病已久的特性我觉得是理所当然的。</p>



<p>其次是删除了协处理器这个概念，特权资源与RISC-V类似使用特权寄存器（CSR）来提供支持，以及浮点运算也不在由协处理器完成。虽然参加龙芯杯不需要实现硬件浮点部件，且在大部分RISC的实现中，浮点部件因为面积巨大，逻辑复杂，通常也是由专门的一个深流水部件完成，与主流水线是解耦的关系，但是我觉得这个仍然是一个好的设计，同样是去除了MIPS中不必要的微架构绑定，为更复杂的设计提供了预留的空间。</p>



<p>再次是对MIPS中的立即数编码做了修改，由统一16位修改为长20/短12的分离的立即数格式。这样做的好处在RISC-V上就能得到充分的体现，常用指令使用短立即数增大了指令的编码空间，长跳、立即数加载等指令使用专门的20位立即数，避免了使用短立即数带来的代码体积增加的问题。</p>



<p>除了上面这些与MIPS相比的优点，LoongArch与RISC-V相比也有相当大的不同。首先是LoongArch设计之初就考虑了大量扩展，相比于RISC-V的极简主义极致模块化，每个扩展缓慢发展，LA的设计更加偏向于商用和实用主义，其中不乏大量的tradeoff和edge case，指令格式的类型也比RISC-V要多很多。</p>



<h2 class="wp-block-heading">队伍情况</h2>



<p>原本以为LoongArch这么先进的基础设施和先进的指令集，以往的强校都会将有实力的队伍投入LA挑战赛中，结果实际上虽然LA有实力强劲的队伍，也有拿出了复杂的乱序设计的队伍，但是传统强校依然基本将重心放在MIPS的团队赛。我猜测（盲猜，不对请大佬指正）是因为这些学校都有传承了数届的基础设施，MIPS的开发流程也能够做到十分先进高效，往年也有优秀的学长的设计可以参考，做到什么样的程度能拿到什么样的名次都十分清楚，因此不太愿意放弃成熟的基础设施和开发流程转而来新的赛道踩坑。</p>



<p>相比之下虽然我校也曾经在龙芯杯取得过一等奖第二名这样的成绩，然而我校却严重缺乏MIPS的基础设施和学长的经验传承，体现在以下几个方面：</p>



<ul class="wp-block-list"><li>没有差分测试框架。参加过多年的强校都有往届学长移植的差分测试框架，甚至有完善的CI自动化测试基础设施，这些基础设施我校一概没有。</li><li>没有学长成功启动过Linux。由于MIPS赛道的决赛门槛并不是支持操作系统，决赛评分中支持Linux也只是保证了一个高的起评分，因此在没有差分测试的情况下，我校的往届学长往往选择做出一个不错的性能，然后靠较为高级的外设和互动来拿到系统展示的分数。没有适配系统的经验对于想要启动Linux来说是非常致命的。</li><li>没有成熟的SoC和外设适配代码和流程。在操作系统上适配外设除了CPU以外SoC上也需要做很多的工作，包括地址空间分配，中断号分配，时钟分配等等。因此我校的系统展示基本仅限于裸机程序的展示，而且都没有使用中断，而采用轮询的方法避开SoC的工作。</li></ul>



<p>今年我们的队伍是临时组建的，也就是说组队前基本互不认识（起码我都不认识），所以并不是什么深谙嵌入式开发/熟悉CPU设计的人。我们所有人，包括我都是在组队完成以后才开始接触入门CPU设计的。</p>



<p>比赛结束以后我翻了一下团队赛的演示视频（是的，我也不知道为什么出现在b站上但不是官号）。2022年第六届龙芯杯了，就我个人的感觉而言，我觉得仍然没有队伍所做的工作能够超越哈利橙他们2019年第三届龙芯杯的工作。的确清华大学今年的参赛队伍设计了乱序的CPU性能能够超过nontrivials-mips，但是按照IPC的比例我觉得其实并没有很好的发挥出乱序处理器应有的能力，而他们的系统适配基本沿用2019年的工作。<s>这令我相信是神去了清华而不是清华培养出了神。</s></p>



<p>我们队伍今年的性能大约只有哈利橙他们的60%（因为没法直接对比），是因为流水cache出现了小概率bug，最后都没有调试出这个问题，因此只能提交主频低，IPC也不高的一个版本。如果流水dcache能够使用的话，我们的性能大约能够小幅超过他们。</p>



<h2 class="wp-block-heading">参赛过程</h2>



<p>以下是回顾参赛的过程，希望对我校或者其他参加龙芯杯的同学或者有志于从事CPU设计的同学有参考价值。</p>



<h3 class="wp-block-heading">语言选择</h3>



<p>我们队伍选择的开发语言是SystemVerilog，主要是考虑到我校的数字逻辑和组成原理都使用的是Verilog，而实际上2005年开始在IEEE的标准里Verilog其实就是SystemVerilog。相比于Verilog，SV主要的优点是有<strong>结构体</strong>和<strong>多维数组</strong>，在总线等信号很多但是较为固定的地方SV也有提供<strong>接口</strong>这一个概念供简化代码。</p>



<p>我校的其他队伍和龙芯杯参赛的其他队伍也都有使用一些其他的HDL，例如SpinalHDL和Chisel等。这些高级的硬件描述语言的工作方式通常是由编译器将高级语言编译成为Verilog然后再交由仿真或综合工具生成网表等。此类高级语言通常都能够解决Verilog系的接线和端口声明冗长的问题，有的语言还有更多的语法糖可以实现OOP等更加高级的代码精简方法。</p>



<p>对于参加龙芯杯使用的HDL的选择，其实并没有一个谁好谁坏，我把一些关键的区别总结在下面共有需要的人参考：</p>



<p>Verilog系语言的优势：</p>



<ul class="wp-block-list"><li>有完整原生的全套工具链支持，包括仿真、综合、实现、编辑&amp;代码补全等，虽然并不一定很好用但是一定是有的。</li><li>直接了当，生成的电路与代码描述的一致。</li><li>相当多开源项目/开源IP核都使用Verilog。</li></ul>



<p>Verilog系语言的劣势：</p>



<ul class="wp-block-list"><li>代码冗余，开发效率可能因此降低，开发者不能讲精力集中在功能设计上。</li><li>二义性语法多，如果出现未定义的语法，不同的工具可能行为不一致，非常影响开发。</li><li>测试testbench编写麻烦。</li></ul>



<p>Chisel类高级语言的优势：</p>



<ul class="wp-block-list"><li>代码精简，开发效率高。</li><li>生成的Verilog保证没有二义性。</li><li>可以使用高级语言编写测试样例，较为方便</li></ul>



<p>Chisel类高级语言的劣势：</p>



<ul class="wp-block-list"><li>通常较新，工具链支持可能不足。</li><li>可能存在编译器bug，而出现问题的时候由于生成的Verilog可读性较差，debug难度较大。</li><li>需要学习一门新的语言。</li></ul>



<p>当然有些优势/劣势并不一定符合你的实际情况，例如你熟悉java/scala语法，那可能Chisel等语言也很容易上手，所以对于语言的选择只要符合自己/自己队伍的情况就可以了，并没有必然的好坏。</p>



<h3 class="wp-block-heading">前期准备</h3>



<p>我校的龙芯杯宣传和备赛都开始得很早，在2021年秋季的数字逻辑设计实验课结束以后就开始了宣传和备赛，我猜测应该在大部分学校里面属于比较早的。我校2022年龙芯杯备赛的时候一开始来了11支队伍，30几个个人参赛，是的你没看错，我也觉得难以置信，而且往年也就是两支队伍+几个人。当然最后提交作品的大概就剩下3支mips+2两支la+8个个人左右（个人赛不太清楚）。</p>



<p>前期的话主要是以学习为主，切忌好高骛远，有的同学在开始动手之前先看了《超标量处理器设计》，然后打算先干一个乱序CPU，然后就没有然后了。我们队伍基本上到2022年3月都是在摸鱼/跟着《自己动手写CPU》抄代码，其他东西的准备也是几乎没有。直到我3月3号提交了ASC初赛的proposal，大家才开始干活（<s>才开始大力push队友</s>）。</p>



<p>4月初我们就分好了工，我负责分支预测，其他队友分别负责主流水线，DCache和总线。于是4月份我就在调研和复现，最终在4月底的时候完成了TAGE的RTL复现。五一假期的时候我们申请了一个教师做队伍内部交流（<s>首次面基</s>），那个时候DCache是说有bug还没有对接，axi已经有一个可用版本，主流水线的单发射版本已经可以进行功能测试了，双发射才开始改。</p>



<p>5月份我主要和主流水线的队友一起适配chiplab和修bug，<s>其他队友可能在摸鱼</s>。5月底的时候就基本完成了双发射CPU的代码编写，<s>这个大约就算是完成了前期的工作吧</s>。</p>



<p>接下来就是<strong>debug</strong>，<strong>debug</strong>和<strong>debug</strong>。</p>



<h3 class="wp-block-heading">工作量和难点</h3>



<p>对于参加LA赛道的工作量，我觉得最大的其实是适配系统。</p>



<p>虽然说支持Linux操作系统并不是两眼一抓瞎从头做起，官方的框架chiplab里面提供了一个十分完善的单发射五级流水的实例CPU，能够稳定启动Linux，因此理论上只要对着那个CPU增量开发就能够保证适配Linux。但是那个CPU充满了assign式的电路级别写法，十分晦涩难懂，所以我们选择了从头开始编写。然而对于完全没有CPU设计和系统适配经验的队伍来说，就算是有完整的实现可以参考，但是这个部分依然是工作量很大的部分。因为系统软件大量依赖特权资源，而这些特权资源其实并没有一个很好的仿真验证负载来测试，因此对于特权部分的调试我们队伍基本上只能依赖在仿真启动Linux，当然这个耗时久比较的久了。另外的是启动Linux操作系统有上亿条指令（没错，是真的上亿），这个对CPU的正确性要求很高，即便是有了6000万条的随机验证序列，chiplab的仿真SoC中也做了各种随机因素的引入，但是有的问题依然不能够在除了Linux以外的负载上复现。</p>



<p>其次就是DCache的一致性问题。LoongArch其实是一个软件维护一致性的ISA，相比于x86这种硬件维护的已经是大幅降低了硬件的设计难度，但是由于实际操作系统中访存指令的类型混杂，访存和一致性维护指令的混杂，使得DCache数据一致性的设计尤为重要但又十分难调试。我们队伍最终就是栽在了流水DCache的一致性维护上，导致Linux的启动并不稳定，有较低概率出现问题，只好提交一个旧的并不是最优的一个版本。</p>



<p>接下来才是CPU微架构的设计，或者其他的特性的设计。哪怕是设计一个乱序四发射的CPU，我相信除了访存部分，其他部分从设计到编写代码到调试完成，基本都可以在两个人月内完成。而访存部分的调试可能需要花同样多的时间。我们的队伍是以启动系统作为最高优先级，因此微架构上的设计可以说十分简陋和落后，仅仅是一个10级静态顺序双发射。</p>



<h3 class="wp-block-heading">队伍时间节点</h3>



<p>以下是我们队伍2022年参加龙芯杯的一些重要时间节点：</p>



<ul class="wp-block-list"><li>2021年12月：组队</li><li>2022年1月-3月：学习阶段，大家各自完成大部分的《自己动手写CPU》的内容</li><li>2022年4月：分工完成，我负责分支预测和前端，我完成TAGE的论文复现</li><li>2022年5月：完成主流水线并接入chiplab进行功能测试</li><li>2022年6月11日：首次仿真启动Linux，无Cache</li><li>2022年6月下旬：根据随机验证修复bug</li><li>2022年7月初：遇到Verilog二义性问题，花费全队一个星期debug</li><li>2022年7月12日：首次板上启动Linux，有ICache</li><li>2022年7月底：前端分支预测、写直通法dcache完成</li><li>2022年8月6日：前端freeze，首个可以提交的版本完成</li><li>2022年8月7-14日：流水写回dcache调试，未成功</li><li>2022年8月14日：提交旧版无bug版本</li><li>2022年8月18日：最终提交系统展示和文档ppt等</li></ul>



<h2 class="wp-block-heading">感想</h2>



<p>其实原本我校还有另一支队伍也参加了LA赛道，他们使用了Chisel作为开发语言，开始准备的时间比我们还要早很多，寒假结束就已经有一个五级流水的核了。而且他们的微结构比我们先进很多，也使用了记分板的乱序方法（没有重命名），但是他们最终没有调试出Linux，而无缘决赛。说到这个，不得不提我们最终的作品是一个静态10级流水的顺序双发射CPU，对于这个10级静态，我们队里的人和学长/老师都觉得十分浪费。访存所需的逻辑很多，导致基本上需要4-5级的流水来完成，但是作为一个静态的流水线，除了访存以外的指令在后面的访存流水中完全没有事情做，非常的浪费。而且我们的load to use问题解决的也不是很好，采取了保守的方式，而本可以采用更激进而高效的方法。但是其实我认为吧，LA这个赛道如果是第一年参赛，首要目标还是启动Linux吧，这也是我们队伍从一开始的原则。当然这导致我们的微架构没有充足的时间设计，只能草草沿用传统的静态长流水。</p>



<p>今年的龙芯杯也算是最终落下帷幕了，已经得知有乱序队伍今年Linux启动不稳定，打算明年继续了。</p>



<h2 class="wp-block-heading">成绩</h2>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2022/08/Screen-Shot-2022-08-21-at-10.18.55-1024x578.png" alt="" class="wp-image-1117" width="637" height="360" srcset="https://blog.eastonman.com/wp-content/uploads/2022/08/Screen-Shot-2022-08-21-at-10.18.55-1024x578.png 1024w, https://blog.eastonman.com/wp-content/uploads/2022/08/Screen-Shot-2022-08-21-at-10.18.55-300x169.png 300w, https://blog.eastonman.com/wp-content/uploads/2022/08/Screen-Shot-2022-08-21-at-10.18.55-768x433.png 768w, https://blog.eastonman.com/wp-content/uploads/2022/08/Screen-Shot-2022-08-21-at-10.18.55.png 1193w" sizes="(max-width: 637px) 100vw, 637px" /></figure>



<p>最终我们拿到了一等奖。对于这个成绩我还是十分满意的（<s>好像没有更满意的成绩了</s>）。其实也算有点意料之中吧，因为提交前看到LA群里的其他队伍的情况，很多队伍都还在调试Linux，估计是没有时间好好调性能了，我们虽然最终的流水DCache版本没有敢提交，但是前端是有仔细调优过的，性能也算中规中矩吧。</p>



<h2 class="wp-block-heading">后续</h2>



<p>项目代码整理完毕以后应该会开源出来。</p>



<p>接下来我也会撰写几篇文章描述我们的前端设计和TAGE预测器的详细复现内容，可以关注本博客或者订阅RSS。</p>The post <a href="https://blog.eastonman.com/blog/2022/08/nscscc2022/">NSCSCC2022 龙芯杯参赛总结</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2022/08/nscscc2022/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>全球大内网与Zerotier</title>
		<link>https://blog.eastonman.com/blog/2021/12/zerotier/</link>
					<comments>https://blog.eastonman.com/blog/2021/12/zerotier/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Mon, 06 Dec 2021 03:32:53 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[Zerotier]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=910</guid>

					<description><![CDATA[<p>预计阅读时间： 6 分钟 前段时间把笔记本换成了MacBook Pro（真香），为了数据安全，自然是需要把Ti [&#8230;]</p>
The post <a href="https://blog.eastonman.com/blog/2021/12/zerotier/">全球大内网与Zerotier</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 6 分钟</p>
<p>前段时间把笔记本换成了MacBook Pro（<s>真香</s>），为了数据安全，自然是需要把Time Machine利用起来。而Time Machine只能备份到本地的硬盘或者AFP或者SMB或者Time Capsule。显然我没有多余的硬盘，也没有rich到来一个Time Capsule，但是我手头上恰好有一个2T HDD的大盘鸡（<s>远在天边</s>），所以想把Time Machine的备份放到这个大盘鸡里。</p>



<p>那么现在问题来了，SMB这个玩意儿的安全问题相信大家也都有所耳闻，我实在是不想把它暴露在公网上，这就需要一个VPN。正好我前几天黑五搞了一些BGP Player友好的鸡，甚至还有公网的ASN号，因此就想干脆组一个全球大内网吧。</p>



<h2 class="wp-block-heading">为什么选择Zerotier</h2>



<p>其实我一开始的想法是Wireguard，因为这玩意儿大家都在吹，也在内核主线里，可以说相当的轻量，也很flexible。但是Wireguard的问题在于它是点对点的，也就是如果没有一个管理的途径的话，每条边都需要手动操作一次，非常麻烦。有的大佬用ansible解决，<s>然而我并不会这么复杂的ansible</s>。</p>



<p>我总结了一下我对于全球大内网最主要的需求：</p>



<ul class="wp-block-list"><li><strong>无中心节点</strong>，或者说流量不能由中心节点转发</li><li><strong>轻量高效</strong>（OpenVPN什么的就免了），因为有的节点之间可以跑到千兆，如果不够轻量的话会有CPU瓶颈。</li><li><strong>配置简易</strong>，超过Wireguard的就免了</li><li><strong>确保安全</strong>，必须加密传输，稍弱一些如aes128的也可以，但是不能接受明文传输，所以VXLAN和GRE等也不满足了。</li></ul>



<p>除此之外还有一些加分项：</p>



<ul class="wp-block-list"><li>multipath，我们的VPS服务商提供的网络接入质量不尽相同，如果有一种方式可以让两个线路较差的VPS通过一个线路较好的VPS进行转发其实可以得到更好的质量。不过目前除了iBGP+自定义cost似乎没有别的方式可以达到这样的效果。</li><li>中心化的路由配置，这一项Zerotier恰好可以满足，如果我们需要桥接别的“真正的”内网，这个功能还是很实用的。</li><li>跨平台客户端，虽然目前的方案并不需要这个功能，但是如果我们以后希望将大内网进一步扩大，可能会需要这个功能。</li></ul>



<h2 class="wp-block-heading">自建VS官方</h2>



<p><s>有这么多鸡不自建也是浪费</s></p>



<p>zerotier有network controller，planet，moon等很多概念/角色，我也没搞懂，有懂的可以指点一下。自建network controller的好处在于可以避免官方的同一个网络50个客户端限制。自建moon的好处在于对称nat的情况下可以有较好的转发质量。</p>



<p>由于我们不需要考虑nat，所以就只自建network controller。</p>



<h2 class="wp-block-heading">自建方案选型</h2>



<p>由于<s>好看是第一生产力</s>手动添加网络和批准加入十分繁琐，所以需要一个GUI在网络控制器上，而不是按照官方的API使用curl来管理网络。</p>



<p>ztncui（<a href="https://github.com/key-networks/ztncui" target="_blank" rel="noreferrer noopener" title="https://github.com/key-networks/ztncui">Github</a>和<a href="https://key-networks.com/ztncui/" target="_blank" rel="noreferrer noopener" title="https://key-networks.com/ztncui/">官网</a>）是一个基于zerotier-one API的前端，提供类似于官方my.zerotier.com的管理操作，可以添加网络，管理网络和路由等。</p>



<p>其他服务器自然是使用正常的zerotier-one作为节点加入。zerotier还提供有各个嵌入式平台和路由器平台的支持，可以说十分的跨平台了。</p>



<p>还有一点，这次我希望安装的服务器是一台buyvm/frantech的512MB内存的服务器，计划之后还要在上面跑<s>鸟</s>bird，如果要收全表的话，512MB的内存可谓是捉襟见肘，所以首先排除使用docker的安装方式，尽量使用overhead比较小的安装。</p>



<h2 class="wp-block-heading">安装过程</h2>



<h3 class="wp-block-heading">Zerotier</h3>



<p>zerotier在我使用的Debian系发行版上只需要一行命令，注意需要gpg已安装，未安装的话会提示需要安装。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl -s https://install.zerotier.com | sudo bash</pre>



<p>如果你是安全nerd，也可以使用gpg验证脚本以后再执行，仍然是一行。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl -s 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg' | gpg --import &amp;&amp; \
if z=$(curl -s 'https://install.zerotier.com/' | gpg); then echo "$z" | sudo bash; fi</pre>



<p>安装完成以后zerotier的源就会被加入apt中，以后只需要使用apt来管理和升级即可。</p>



<h3 class="wp-block-heading">Ztncui</h3>



<p>根据其<meta charset="utf-8"><a href="https://key-networks.com/ztncui/" target="_blank" rel="noreferrer noopener" title="https://key-networks.com/ztncui/">官网</a>的说明，是采用deb打包的方式安装，需要首先下载deb的安装包，然后再安装。</p>



<p>注意！此处具有实效性，建议按照官网安装最新的包！</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl -O https://s3-us-west-1.amazonaws.com/key-networks/deb/ztncui/1/x86_64/ztncui_0.7.1_amd64.deb
sudo apt-get install ./ztncui_0.7.1_amd64.deb</pre>



<p>由于ztncui是使用nodejs开发的，因此还需设置一下production mode</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sudo sh -c "echo 'NODE_ENV=production' >> /opt/key-networks/ztncui/.env"</pre>



<p>ztncui默认监听127.0.0.1:3000，可以使用以下命令转发到本地访问</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ssh -f user@network.controller.machine -L 3333:localhost:3000 -N</pre>



<p>也可以使用以下命令打开一个https端口，没有配置的话使用的是self-signed证书，如果需要配置证书的话可以参考<a href="https://key-networks.com/ztncui/#installation" target="_blank" rel="noreferrer noopener" title="https://key-networks.com/ztncui/#installation">官方教程</a>。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sudo sh -c "echo 'HTTPS_PORT=3443' > /opt/key-networks/ztncui/.env"</pre>



<p>最后重启ztncui是配置生效。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sudo systemctl restart ztncui</pre>



<h2 class="wp-block-heading">网络配置</h2>



<p>这部分的配置其实比较简单，按照提示来就可以了，此处放一些截图供调研方案和选型的读者参考。</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="529" height="255" src="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.12-AM.png" alt="" class="wp-image-926" srcset="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.12-AM.png 529w, https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.12-AM-300x145.png 300w" sizes="(max-width: 529px) 100vw, 529px" /><figcaption>创建网络</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="763" height="311" src="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.29-AM.png" alt="" class="wp-image-927" srcset="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.29-AM.png 763w, https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.29-AM-300x122.png 300w" sizes="(max-width: 763px) 100vw, 763px" /><figcaption>创建后结果</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="428" height="500" src="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.45-AM.png" alt="" class="wp-image-928" srcset="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.45-AM.png 428w, https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-11.21.45-AM-257x300.png 257w" sizes="(max-width: 428px) 100vw, 428px" /><figcaption>Easy setup中添加cidr</figcaption></figure></div>



<h2 class="wp-block-heading">最终效果</h2>



<p><s>辣鸡nodejs果然占内存</s> 安装完成以后大概内存占用是111MB，ztncui自己占了60MB，相对来说还可以接受。</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="940" height="139" src="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.35.04-AM.png" alt="" class="wp-image-917" srcset="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.35.04-AM.png 940w, https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.35.04-AM-300x44.png 300w, https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.35.04-AM-768x114.png 768w" sizes="(max-width: 940px) 100vw, 940px" /><figcaption>mtr</figcaption></figure></div>



<p>可以看到，一天的丢包率只有0.1%，我认为是可以接受的。</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="653" height="263" src="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.36.31-AM.png" alt="" class="wp-image-918" srcset="https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.36.31-AM.png 653w, https://blog.eastonman.com/wp-content/uploads/2021/12/Screen-Shot-2021-12-06-at-10.36.31-AM-300x121.png 300w" sizes="(max-width: 653px) 100vw, 653px" /><figcaption>iperf3</figcaption></figure></div>



<p>我的两台vps之间也能跑到比较高的速率。</p>The post <a href="https://blog.eastonman.com/blog/2021/12/zerotier/">全球大内网与Zerotier</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/12/zerotier/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>Bash History的使用技巧</title>
		<link>https://blog.eastonman.com/blog/2021/06/bash-history-tricks/</link>
					<comments>https://blog.eastonman.com/blog/2021/06/bash-history-tricks/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Thu, 10 Jun 2021 14:51:35 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[Bash]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=792</guid>

					<description><![CDATA[<p>Bash是常见的shell环境，大多数Linux发行版也都带有Bash，因此高效地利用history可以节省手动输入重复命令的时间。本文分享一下几个我自己使用的技巧和配置。</p>
The post <a href="https://blog.eastonman.com/blog/2021/06/bash-history-tricks/">Bash History的使用技巧</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 3 分钟</p>
<p>Bash是常见的shell环境，大多数Linux发行版也都带有Bash，因此高效地利用history可以节省手动输入重复命令的时间。本文分享一下几个我自己使用的技巧和配置。</p>



<h3 class="wp-block-heading">搜索</h3>



<p>这个技巧大家应该多少都知道，就是在输入界面按<code>Ctrl-R</code>就可以进行搜索了，返回的结果是最近一个匹配的命令，然后按回车就可以执行。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="526" height="220" src="https://blog.eastonman.com/wp-content/uploads/2021/06/image.png" alt="" class="wp-image-794" srcset="https://blog.eastonman.com/wp-content/uploads/2021/06/image.png 526w, https://blog.eastonman.com/wp-content/uploads/2021/06/image-300x125.png 300w" sizes="(max-width: 526px) 100vw, 526px" /><figcaption>Ctrl-R</figcaption></figure></div>



<p>但是有的时候搜索结果只是与我想要的很相近，但又不完全一样，我希望能够在执行之前对命令做小的修改，那怎么办?其实此时按Esc就可以退出并且编辑。</p>



<p>还有一个方式是利用<code>:p</code>，<code>:p</code>在Bash中表示打印但不执行，因此如果我们想要查看某个前缀的命令，就可以使用<code>!prefix:p</code>来打印。</p>



<h3 class="wp-block-heading">HISTIGNORE</h3>



<p>HISTIGNORE是一个环境变量，它指示Bash什么样的命令不要加入history中，例如这样的一个变量。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export HISTIGNORE='pwd:exit:fg:bg:top:clear:history:ls:uptime:df'</pre>



<p>就可以把无意义的交互命令从history中去除了。如果你经常使用<code>ll</code>或者<code>la</code>这样的简写，那么把他们加入HISTIGNORE中或许是一个不错的选择。</p>



<h3 class="wp-block-heading">HISTCONTROL</h3>



<p>HISTCONTROL也是一个环境变量，它有几个选择</p>



<ul class="wp-block-list"><li>ignorespace：以空格开头的所有命令都不会加入history中</li><li>ignoredups：重复的命令不会加入history中</li><li>ignoreboth：打开以上的两个特性</li></ul>



<p>通常一些发行版已经带有ignoredups的选项，如果你想要避免某些特定的命令计入history中，那么可以打开ignorespace选项。</p>



<h3 class="wp-block-heading">并行的窗口之间的history问题</h3>



<p>这个问题相信很多同学也会有，在远程管理服务器的时候，通常为了某些监视任务或者耗时长的任务不阻塞人的操作，会使用各种窗口复用工具，比如<code>tmux</code>或者<code>screen</code>，那么由于Bash在关闭的时候才会将history写入<code>.bash_history</code>文件，那么大多数时候当我们关闭这些窗口以后就只有最后一个关闭的窗口的History被记录了下来。</p>



<p>我们可以使用</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">shopt -s histappend</pre>



<p>来指示Bash讲history附加到原本的history上而不是覆盖。</p>



<h3 class="wp-block-heading">使用!来执行history中的命令</h3>



<p>!可以用来执行命令，比如!10就是执行history中的第10条命令。当然也可以执行最近的第几条命令，比如!-3就是倒数第三条命令。</p>



<p>这里还可以结合<code>:p</code>使用，以避免直接执行带来的风险。</p>



<p>如果你一定要避免所有的风险，每次都希望检查命令再执行，而又不想输入<code>:p</code>，那么Bash还有一个选项可以做到这一点：<code>shopt -s histverify</code>。这个选项会使得所有用!执行的命令都需要按Enter键确认才会执行。</p>



<h3 class="wp-block-heading">使用上一个命令的参数</h3>



<p>!$和!*分别会将上一个命令的最后一个参数和所有参数填补到这个位置，例如</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ mv list.txt items.txt
$ vim !$
vim items.txt
$ cp !$ shopping.txt
cp items.txt shopping.txt</pre>



<p></p>The post <a href="https://blog.eastonman.com/blog/2021/06/bash-history-tricks/">Bash History的使用技巧</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/06/bash-history-tricks/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>现代处理器结构</title>
		<link>https://blog.eastonman.com/blog/2021/05/modern-processor/</link>
					<comments>https://blog.eastonman.com/blog/2021/05/modern-processor/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Mon, 17 May 2021 15:10:04 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[计算机系统]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[体系结构]]></category>
		<category><![CDATA[处理器]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=639</guid>

					<description><![CDATA[<p>一个简短的、直接的现代处理器微架构设计介绍。</p>
The post <a href="https://blog.eastonman.com/blog/2021/05/modern-processor/">现代处理器结构</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 36 分钟</p>
<p>一个简短的、直接的现代处理器微架构设计介绍。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>当今的机器实在是非常原始，它们只能理解区区几个简单的指令，比如“向左”、“向右”和“生产汽车”。</p><p><em>Today&#8217;s robots are very primitive, capable of understanding only a</em> <em>few simple instructions such as &#8216;go left&#8217;, &#8216;go right&#8217; and &#8216;build car&#8217;.</em></p><cite><a href="https://www.azquotes.com/quote/1403947" target="_blank" rel="noreferrer noopener">John Thomas Sladek</a></cite></blockquote>



<p><strong>警告：</strong>本文旨在以非正式和风趣的语言讲述严肃的科学。</p>



<p><strong>警告2：</strong>长文！预计阅读时间36分钟。</p>



<p>本文主要向计算机专业的低年级学生和对现代处理器结构感兴趣的读者介绍有关处理器微架构的一些概念。具体来说，有以下几个方面：</p>



<ul class="wp-block-list"><li>流水线（超标量执行、乱序执行、超长字指令、分支预测）</li><li>多核和超线程（同步超线程 SMT）</li><li>SIMD指令集（SSE、AVS、NEON、SVE）</li><li>缓存和缓存机制</li></ul>



<p>听起来内容很深奥，<strong>但是，不要害怕！</strong>这篇文章将带你快速地了解这些看似只有处理器设计从业者或者是体系结构专家才能了解的东西。也许你很快就可以和你的同学/朋友吹牛了<img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f923.png" alt="🤣" class="wp-smiley" style="height: 1em; max-height: 1em;" />。</p>



<h2 class="wp-block-heading">超10G！</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>超10G！全人类感谢你！</p><cite>——某up</cite></blockquote>



<p>主频越高，CPU性能越好，这似乎是很多人的误区（不包括以上引用的up主），但是，从上古时期开始，CPU的性能和主频就没有直接关系。那么这个刻板印象是从哪里来的呢？<img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f914.png" alt="🤔" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>让我们来看看一些上古（上世纪90年代末）的处理器数据&#8230;</p>



<figure id="megahertztable" class="wp-block-table is-style-stripes"><table><tbody><tr><td><strong>主频</strong></td><td><strong>型号</strong></td><td><strong>SPECint95</strong></td><td><strong>SPECfp95</strong></td></tr><tr><td>195 MHz</td><td>MIPS R10000</td><td>11.0</td><td>17.0</td></tr><tr><td>400 MHz</td><td>Alpha 21164</td><td>12.3</td><td>17.2</td></tr><tr><td>300 MHz</td><td>UltraSPARC</td><td>12.1</td><td>15.5</td></tr><tr><td>300 MHz</td><td>Pentium II</td><td>11.6</td><td>8.8</td></tr><tr><td>300 MHz</td><td>PowerPC G3</td><td>14.8</td><td>11.4</td></tr><tr><td>135 MHz</td><td>POWER2</td><td>6.2</td><td>17.6</td></tr></tbody></table><figcaption>1997年的处理器性能</figcaption></figure>



<p>SPEC是一个当年常用的性能测试工具，乔布斯在宣布苹果的Macbook由IBM PowerPC平台转向Intel的酷睿平台的时候就在发布会上展示了SPEC的性能提升。</p>



<p>从表中可以看到，为什么300MHz的处理器有这么不同的性能差异？为什么低主频的CPU反而吊打高主频的？</p>



<p>什么？你说这都是上古的数据？那就来一个最近的：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="903" height="340" src="https://blog.eastonman.com/wp-content/uploads/2021/05/image-1.png" alt="" class="wp-image-651" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/image-1.png 903w, https://blog.eastonman.com/wp-content/uploads/2021/05/image-1-300x113.png 300w, https://blog.eastonman.com/wp-content/uploads/2021/05/image-1-768x289.png 768w" sizes="(max-width: 903px) 100vw, 903px" /></figure>



<p>超到一半人类感谢你（5GHz）的Intel i9-9900K居然被M1吊打？</p>



<p>是的，你没有看错，这就说明显然除了主频以外还有一些什么东西，那就是——</p>



<h2 class="wp-block-heading">流水线和指令级并行</h2>



<p>指令在处理器中是一个接一个的执行的，对吗？不完全对。这样的说法可能是直观的，但是并不是事实，实际上，从80年代开始，CPU就不再是完全顺序执行每个指令了。现代处理器可以同时执行不同指令的不同阶段，甚至有的处理器也可以完全同时地执行多个指令。</p>



<p>让我们来看一看一个简单的四级流水线是怎么构成的。指令被分成四个部分：<strong>取指、译码、执行和写回</strong>。</p>



<p>如果CPU完全顺序执行，那么每条指令需要花费4个周期才能执行完毕，IPC=0.25（Instruction per cycle）。当然，古老一点的时期更喜欢使用CPI，因为当时的处理器普遍不能做到每周期执行一条指令。但是现在时代变了，你能接触到的任何一个桌面级处理器都可以在一个周期内执行一条、两条甚至是三条指令。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="384" height="145" src="https://blog.eastonman.com/wp-content/uploads/2021/05/sequential2.png" alt="" class="wp-image-654" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/sequential2.png 384w, https://blog.eastonman.com/wp-content/uploads/2021/05/sequential2-300x113.png 300w" sizes="(max-width: 384px) 100vw, 384px" /><figcaption>顺序执行的处理器</figcaption></figure></div>



<p>正如你所看到的，实际上CPU内负责运算的组件（ALU）十分的悠闲，甚至只有25%的时间在干活。什么？怎么压榨ALU？我看你很有资本家的天赋嘛&#8230;</p>



<p>好吧，现代处理器确实有手段压榨这些ALU（对，现代处理器也不止一个ALU）。一个很符合直觉的想法就是既然大部分的阶段CPU都不是完全占用的，那么将这些阶段重叠起来就好了。确实，现代处理器就是这么干的。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="384" height="145" src="https://blog.eastonman.com/wp-content/uploads/2021/05/pipelined2.png" alt="" class="wp-image-660" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/pipelined2.png 384w, https://blog.eastonman.com/wp-content/uploads/2021/05/pipelined2-300x113.png 300w" sizes="(max-width: 384px) 100vw, 384px" /><figcaption>流水线执行的处理器</figcaption></figure></div>



<p>现在我们的处理器大多数时候一个周期可以执行一条指令了，看起来不错！这已经是在没有增加主频的情况下达到四倍的加速了。</p>



<p>从硬件的角度来看，每级流水线都是由该级的逻辑模块构成的，CPU时钟就像一个水泵，每次把信号（或者也可以说是数据）从一级泵到下一级，就像这样：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="380" height="96" src="https://blog.eastonman.com/wp-content/uploads/2021/05/pipelinedmicroarch2.png" alt="" class="wp-image-661" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/pipelinedmicroarch2.png 380w, https://blog.eastonman.com/wp-content/uploads/2021/05/pipelinedmicroarch2-300x76.png 300w" sizes="(max-width: 380px) 100vw, 380px" /><figcaption>流水线微架构</figcaption></figure></div>



<p>事实上，现代处理器除了以上这样简单的结构，首先还有很多额外的ALU，比如整数乘法、加法、位运算、浮点数的各种运算等等，几乎每种常用的运算都有至少一个ALU。其次，如果前一条指令的结果就是下一条指令的操作数，那么为什么还要把数据写回寄存器呢？因此就出现了Bypass（前递）通路，用于在这种情况下直接将数据重新送到运算器的输入端口。综合起来，详细一点的流水线微架构应该长这样：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="390" height="92" src="https://blog.eastonman.com/wp-content/uploads/2021/05/pipelinedfunctionalunits2.png" alt="" class="wp-image-665" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/pipelinedfunctionalunits2.png 390w, https://blog.eastonman.com/wp-content/uploads/2021/05/pipelinedfunctionalunits2-300x71.png 300w" sizes="(max-width: 390px) 100vw, 390px" /><figcaption>详细的流水线微架构</figcaption></figure></div>



<h2 class="wp-block-heading">更深的流水线——超级流水线！</h2>



<p>自从CPU主频由于某种原因（某种神秘力量？<img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f608.png" alt="😈" class="wp-smiley" style="height: 1em; max-height: 1em;" />）很多年没有大的进步以来（对的，超频榜第一还是AMD的推土机架构CPU），流水线的设计几乎成为了CPU厂商的竞赛主场。加深的流水线首先可以继续增大实际的IPC（理论上限仍是1），其次可以避免流水线对时序的影响。这与晶体管的特性有关，感兴趣的读者可以上网搜一搜多级流水线结构为什么会影响时序和最终综合出的主频。</p>



<p>在2000-2010年间，这种竞赛达到了最高峰，那时候的处理器甚至可以有高达31级的流水线。但是超深的流水线带来的是结构上的复杂和显著增大的动态调度模块设计难度，因此，从那以后就没有再出现过使用这么多级流水线的CPU了。作为对比，目前（2021年）的处理器多半视应用场景的不同采用10-20级不等的流水线。</p>



<p>x86和其它CISC处理器通常有着更深的流水线，因为他们在取指和译码阶段有数倍的任务要做，所以通常使用更深的流水线来避免这一阶段带来的性能损耗。</p>



<h2 class="wp-block-heading">多发射——超标量处理器</h2>



<p>既然整数的运算器和浮点数的运算器以及其它的的一些ALU互相之间都是没有依赖的，自己做自己的事情，那为什么不进一步压榨它们，让他们尽可能地一起忙起来呢？这就出现了多发射和超标量处理器。多发射的意思是处理器每个周期可以“发射”多于一条的指令，比如浮点运算和整数运算的指令就可以同时执行且互不干扰。为了完成这一点，取指和译码阶段的逻辑必须加强，这就出现了一个叫做<strong>调度器</strong>或者<strong>分发器</strong>的结构，就像这样：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="437" height="335" src="https://blog.eastonman.com/wp-content/uploads/2021/05/superscalarmicroarch2.png" alt="" class="wp-image-685" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/superscalarmicroarch2.png 437w, https://blog.eastonman.com/wp-content/uploads/2021/05/superscalarmicroarch2-300x230.png 300w" sizes="(max-width: 437px) 100vw, 437px" /><figcaption>超标量处理器微架构</figcaption></figure></div>



<p>或者我们来看一张实际的Intel Skylake架构的调度器，图中红圈的就是负责每周期“发射”指令的调度器。</p>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2021/05/image-2.png" alt="" class="wp-image-684" width="-127" height="-87" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/image-2.png 792w, https://blog.eastonman.com/wp-content/uploads/2021/05/image-2-300x207.png 300w, https://blog.eastonman.com/wp-content/uploads/2021/05/image-2-768x529.png 768w" sizes="(max-width: 792px) 100vw, 792px" /><figcaption>Skylake 调度器</figcaption></figure></div>



<p>当然，现在不同的运算有了不同的“数据通路”，经过的运算器也不同。因为不同的运算器内部可能也分不同的执行阶段，于是不同的指令也就有了不同的流水线深度：简单的指令执行得快一些，复杂的指令执行得慢一些，这样可以降低简单指令的<strong>延迟</strong>（我们很快就会涉及到）。某些指令（比如除法）可能相当耗时，可能需要数十个周期才能返回，因此在编译器设计中，这些因素就变得格外重要了。有兴趣的读者可以思考<strong>梅森素数</strong>在这里的妙用。</p>



<p>超标量处理器中指令流可能是这个样子的：</p>



<figure class="wp-block-gallery alignwide columns-2 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img loading="lazy" decoding="async" width="384" height="145" src="https://blog.eastonman.com/wp-content/uploads/2021/05/superscalar2.png" alt="" data-id="690" data-full-url="https://blog.eastonman.com/wp-content/uploads/2021/05/superscalar2.png" data-link="https://blog.eastonman.com/?attachment_id=690" class="wp-image-690" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/superscalar2.png 384w, https://blog.eastonman.com/wp-content/uploads/2021/05/superscalar2-300x113.png 300w" sizes="(max-width: 384px) 100vw, 384px" /></figure></li><li class="blocks-gallery-item"><figure><img loading="lazy" decoding="async" width="384" height="145" src="https://blog.eastonman.com/wp-content/uploads/2021/05/superpipelinedsuperscalar2.png" alt="" data-id="689" data-full-url="https://blog.eastonman.com/wp-content/uploads/2021/05/superpipelinedsuperscalar2.png" data-link="https://blog.eastonman.com/?attachment_id=689" class="wp-image-689" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/superpipelinedsuperscalar2.png 384w, https://blog.eastonman.com/wp-content/uploads/2021/05/superpipelinedsuperscalar2-300x113.png 300w" sizes="(max-width: 384px) 100vw, 384px" /></figure></li></ul></figure>



<p>现代处理器一般都有相当多的发射端口，比如上面提到的Intel Skylake是八发射的结构，苹果的M1也是八发射的，ARM最新发布的N1则是16发射的处理器。</p>



<h2 class="wp-block-heading">显式并行——超长指令集</h2>



<p>当兼容性不成问题的时候（很不幸，很少有这种时候），我们可以设计一种指令集，显式地指出某些指令是可以被并行执行的，这样就可以避免在译码时进行繁复的依赖检验。这样理论上可以使处理器的硬件设计变得更加简单、小巧，也更容易取得更高的主频。</p>



<p>这种类型的指令集中，“指令”实际上是“一组子指令”，这使得它们拥有非常多的指令，进而每个指令都很长，例如128bits，这就是<strong>超长指令集（VLIW）</strong>这个名字的来源。</p>



<p>超长指令集处理器的指令流和超标量处理器的指令流十分的类似，只是省去了繁杂的取指和译码阶段，像这样：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="384" height="145" src="https://blog.eastonman.com/wp-content/uploads/2021/05/vliw2.png" alt="" class="wp-image-697" srcset="https://blog.eastonman.com/wp-content/uploads/2021/05/vliw2.png 384w, https://blog.eastonman.com/wp-content/uploads/2021/05/vliw2-300x113.png 300w" sizes="(max-width: 384px) 100vw, 384px" /><figcaption>VLIW指令流</figcaption></figure></div>



<p>除了硬件结构，超长指令集处理器和超标量处理器十分的相似，尤其是从编译器的角度来看（我们很快也会谈到）。</p>



<p>但是，超长指令集处理器通常被设计成<strong>不检查依赖</strong>的，这就使得它们必须依赖编译器的魔法才能保证结果的正确，而且，如果发生了缓存缺失，那它们不得不整个处理器都停下来，而不是仅仅停止遇到缓存缺失问题的那一条指令。编译器会在指令之间插入“nops”（no operations）——即空指令，以保证有数据依赖的指令能够正确地执行。这无疑增加了编译器的设计难度和编译所需的时间，但是这同时节省了宝贵的处理器片上资源，通常也能有略好的性能。</p>



<p>现在仍在生产的现代处理器中<strong>并没有</strong>采用VLIW指令集的处理器。Intel曾经大力推行过的IA-64架构就是一个超长指令集（VLIW）架构，由此设计的“Itanium”系列处理器在当时也被认为是x86的继承者，但是由于市场对这个新架构并不感冒，所以最终这个系列没有发展下去。现代硬件加速最火热的方向是GPU，其实GPU也可以看作是一种VLIW架构的的处理器，只不过它将VLIW架构更进一步，使用“核函数”代替指令，大大增加了这种体系结构的可扩展性，有兴趣的读者也可以了解相关方面的内容。</p>



<h2 class="wp-block-heading">数据依赖和延迟</h2>



<p>我们在流水线和多发射这条路上能走多远？既然多发射和多级流水线这么好，那为什么不做出50级流水线、30发射的处理器？我们来讨论以下两条指令：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">a = b * c;
d = a + 1;</pre>



<p>第二条指令<strong>依赖于</strong>第一条指令——处理器在完成前一条指令之前无法执行下一条指令。这是一个很严重的问题，这样一来，多发射就没有用武之地了，因为无论你制造出了多少发射的处理器，这两条指令还是只能顺序地执行（除去取指等部分）。有关依赖和消除的问题我们会在后面讨论。</p>



<p>如果第一条指令是一个简单的加法指令，那么加法器在执行完毕后可以通过Bypass通路（前递）将数据传回ALU的输入端口并继续计算，这样流水线才可以正常工作。但是很不幸，第一条指令是一个需要多周期才能完成的乘法（目前的大多数CPU没有使用单周期乘法，因为复杂的逻辑通常会损害主频），这样的话，处理器为了等待第一条指令完成就不得不往流水线中加入若干“气泡”也就是类似于“nops”的指令来保证运算的正确性。</p>



<p>一条指令到达运算器的输入端口和执行结果可用之间需要耗费的CPU周期称为<strong>指令的延迟</strong>。流水线越深，指令的延迟就越高，所以更深的流水线如果无法有效地填满，那么结果只能是很高的指令延迟而无益于处理器的性能。</p>



<p>从编译器的角度（考虑了Bypass，硬件工程师口中的延迟通常不包括Bypass），现代处理器的指令延迟通常是：整数乘加和位操作1周期，浮点数乘加2-6周期不等，sincos这种复杂指令10+周期，最后是可能长达30-50周期的除法。</p>



<p>访存操作的延迟也是一个很麻烦的问题，因为它们通常是每条指令最开始执行的步骤，这使得它们造成的延迟很难用别的方式补偿。除此以外，他们的延迟也很难预测，因为延迟很大程度上取决于缓存是否命中，而缓存是动态调度的（我们很快也会讲到）。</p>



<h2 class="wp-block-heading">分支和分支预测</h2>



<p>另外一个流水线的重要问题就是分支，我们来看一看接下来的一段程序：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (a > 7) {
    b = c;
} else {
    b = d;
}</pre>



<p>编译成的汇编程序将会是这样：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="asm" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">    cmp a, 7    ; a > 7 ?
    ble L1
    mov c, b    ; b = c
    br L2
L1: mov d, b    ; b = d
L2: ...</pre>



<p>现在想象一个流水线处理器来执行这一段程序。当处理器执行到第二行，也就是第二行的跳转命令到达处理器的执行器的时候，它肯定已经把后面的所有指令都提前从内存中取出存并完成了译码工作了。但是，究竟跳转的是哪一条指令？是3，4行还是第5行？在跳转命令到达执行器之前我们并不知道应该跳转到哪里。在一个深流水线的处理器中，似乎不得不停下来等待这个跳转命令，再重新往流水线中填入新的指令。这当然是不可接受的，程序中，尤其是循环时，分支跳转的命令占比很大，如果每次都等待这条命令的完成，那么我们的流水线就不得不经常地暂停，而我们通过流水线取得的性能提升也将不复存在。</p>



<p><strong>于是现代处理器会做出猜测</strong>。什么？处理器竟然靠猜，我还以为发达的处理器设计行业能给出更好的解决方案呢！先不要着急，实际上程序中分支的跳转是有规律的，现代处理器分支预测的准确度通常能达到99%以上（虽然分支预测也是Intel的spectre和meltdown漏洞的来源<img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f47f.png" alt="👿" class="wp-smiley" style="height: 1em; max-height: 1em;" />）。</p>



<p><strong>处理器会沿着预测的分支执行下去</strong>，这样一来，我们的处理器就可以保持流水线和运算器的占用，并高速地执行下去。当然，执行的结果还不能作为最终的结果，只有在分支跳转命令的结果出来以后，预测正确的结果才会被写回（commit或retire）。那猜错了怎么办？那处理器也没有好的办法，只能重新从另一个分支开始进入流水线，在高度流水化的现代处理器里，分支预测错误的代价（<strong>分支预测的错误惩罚</strong>）是相当高昂的，通常会达到数十个CPU周期。</p>



<p>这里的关键在于，<strong>处理器如何做出预测</strong>。通常而言，分支预测分为静态和动态两种。</p>



<p><strong>静态分支预测即处理器做出的猜测与运行时的状态无关</strong>，而对跳转的优化由编译器完成。静态预测通常有一律跳或者往后跳预测不跳，往前跳转则预测跳。后者通常效果更好，原因是循环中一般会有大量向前的跳转指令。</p>



<p><strong>动态分支预测则是根据跳转指令的历史决定是否跳转</strong>。一个最简单的动态分支预测器就是<strong>2位饱和计数器</strong>，它是一个四个状态的状态机，特点是只有连续两次预测错误才会更改预测方向。它已经能在大部分场合下取得90%以上的预测正确率。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2021/05/Branch_prediction_2bit_saturating_counter-dia.svg" alt="" class="wp-image-722"/><figcaption>2位饱和计数器 Author: Afog CC BY-SA 3.0</figcaption></figure></div>



<p>这种预测器在交替出现跳和不跳的分支指令时表现不佳，于是人们又发明了n级自适应分支预测器，它的原理与2位饱和计数器类似，不过它能够记住过去n次的历史，在重复的跳转模式中表现优异。</p>



<p>不幸的是，分支预测是各个CPU厂商的核心竞争力之一，大多数优秀的分支预测技术也是重要的商业机密，于是在这个方面并没有太多可以深入的。Cloudflare最近发布了一篇<a href="https://blog.cloudflare.com/branch-predictor/" target="_blank" rel="noreferrer noopener" title="https://blog.cloudflare.com/branch-predictor/">博文</a>深入测试了x86和ARM的M1上分支预测器的特征，有兴趣的读者可以看看。</p>



<h2 class="wp-block-heading">去除分支语句</h2>



<p>由于分支这实在是处理器不喜欢的东西，于是人们便想要尽量减少分支语句的使用。而以下这种情况很常见，在求取最大最小值或者是条件赋值的时候经常被使用（第1，2行）：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="asm" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">cmp a, 7       ; a > 7 ?
mov c, b       ; b = c
cmovle d, b    ; if le, then b = d</pre>



<p>于是人们就设计出了第3行这种指令。这样的指令是在特定条件下将d的值赋给b，而并不引入分支，只需要在条件不满足的时候不进行写回（commit/retire）就可以了。这种指令被称为<strong>条件转移指令</strong>，编译器中经常使用这种trick来避免进行跳转。</p>



<p>我们古老的x86架构一开始并不支持条件转移指令，MIPS、SPARC也不例外，而Alpha架构从设计之初就考虑了这类指令（RISC-V这样的新指令集当然也有）。ARM则是第一个采用全可预测指令的指令集，这一点很有趣，因为早期的ARM处理器通常采用很浅的流水线，分支预测的惩罚很小。</p>



<h2 class="wp-block-heading">指令调度、寄存器重命名和乱序执行</h2>



<p>如果分支和长延迟的指令会带来流水线气泡，那么能不能把这些气泡占据的处理器时间用来干有用的事情呢？为了达到这个目的，就需要引入<strong>乱序执行</strong>。乱序执行允许处理器将部分指令的顺序打乱，在执行长延迟指令的同时执行一些别的指令。</p>



<p>历史上有两种方式来达到乱序执行的目的：软件的和硬件的。</p>



<p>软件的途径很好理解，就是通过编译器与体系结构的强耦合，在编译阶段就生成好无相互依赖，易于处理器调度的指令。在编译阶段进行指令重排又被称为<strong>静态指令调度</strong>，优点是软件实现可以更灵活（众所周知，软件什么都能干），通常软件也可以有足够的存储空间来分析整个程序，因此可以获得更优的指令排布。当然缺点也是显而易见的，由于编译器需要深入地了解体系结构相关的信息，如指令延迟和分支预测惩罚等，对可移植性造成了很大的困难。因此现代处理器更加常用的是硬件方式。</p>



<p>硬件方式主要是通过<strong>寄存器重命名</strong>来消除读—读和写—写假依赖。寄存器重命名就是对不同指令调用的相同寄存器使用不同的物理硬件存储，在写回阶段再对这些指令和寄存器进行排序，这样这些假依赖就不再是产生流水线气泡的原因了。注意，写—读依赖是真正的数据依赖，虽然像前递这样的技术可以降低延迟，但是并没有能够解决这种依赖的办法。现代处理器中也并非仅仅只有如16个通用寄存器和32个浮点寄存器等等，通常都有成百上千的物理寄存器在CPU的片上。寄存器重命名的算法最有名的便是Tomasulo算法，有兴趣的读者可以搜索一下。</p>



<p>硬件方式的优点在于降低了编译器的体系结构耦合度，提高了软件编写的便捷性，通常硬件乱序执行的效果也不必软件的差。而缺点在于依赖分析和寄存器重命名都需要耗费宝贵的片上空间和电力，但对于性能的提升却没有相应的大。因此，在一些更加关注低功耗和成本的CPU中，会采用顺序执行，如ARM的低功耗产品线，Intel Atom等。</p>



<h2 class="wp-block-heading">多核和超线程</h2>



<p>我们之前讨论了各种指令集并行的方法，而很多时候它们的效果并不是很好，因为相当一部分的程序没有提供细粒度的并行。因此，制造更“宽”更“深”的处理器效果相当有限。</p>



<p>但是CPU的设计者又想了，如果本程序中没有足够并行的没有相互依赖的指令，那么不同的程序之间肯定是没有数据依赖的（指令级数据依赖），那么在同一个物理核心上同时运行两个线程，互相填补流水线的空缺，岂不美哉？这就叫做<strong>同步多线程（SMT）</strong>，它提供了线程级的并行化。这种技术对于CPU以外的世界来说是透明的，就仿佛真的CPU数量多了一倍似的，因此现在人们也常说虚拟核心。</p>



<p>从硬件角度来说，同步多线程的实现需要将所有与运行状态有关的结构数量都翻倍，比如寄存器，PC计数器，MMU和TLB等等。幸运的是，这些结构并不是CPU的主要部分，最复杂的译码和分发器，运算器和缓存都是在两个线程之间共享的。</p>



<p>当然，真实的性能不可能翻倍，理论上限还是取决于运算器的数量，同步多线程只是能够将运算器更好地利用而已。因此在例如游戏画面生成这样地并行度本来就很高地任务中，SMT几乎没有任何地效果，反而因为偶尔地线程切换而带来一定地性能损失。</p>



<p>SMT处理器的指令流看起来大概是这样的：</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2021/05/smt2.svg" alt="" class="wp-image-769"/><figcaption>SMT处理器的指令流</figcaption></figure></div>



<p>太好了！现在我们有了填满哪些流水线气泡的方法了，而且绝无任何风险。所以，<strong>30发射的处理器我们来啦</strong>！对吗？不幸的是，不对。</p>



<p>虽然IBM曾在它的产品中使用过8线程的核心，但是很快我们就会看到，现代处理器的瓶颈早已不单单是CPU本身了，访存延迟和带宽都成为了更加迫切需要解决的问题。而同时使用8个MMU，8个PC，8个TLB怎么看也不是一个缓存友好的做法。因此，现在已经很少听到有多于一个核心两个线程的处理器了。</p>



<h2 class="wp-block-heading">数据并行——SIMD指令集</h2>



<p>除了指令级并行和超线程，在现代处理器中还有一种并行化的设计——数据并行化。数据并行化的思想是将同一条指令不同数据进行并行化，而不是对不同的指令进行并行。所以使用数据并行化的指令集通常又称为<strong>SIMD指令集</strong>（单指令多数据），也有称为<strong>向量指令集</strong>的。</p>



<p>在超级计算机和高性能计算领域，SIMD指令集被大量的使用，因为通常科学计算会处理极多的数据而对于每个数据的操作并不复杂，而且基本没有相互依赖性。在现代的个人计算机中，SIMD指令集也大量的存在，哪怕是最为廉价的手机中，SIMD指令集也有它的身影。</p>



<p>SIMD指令集的工作原理就像下图所示的那样：</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://cdn.arstechnica.net/wp-content/uploads/archive/cpu/1q00/simd/figure6.gif" alt=""/><figcaption>SIMD指令原理</figcaption></figure></div>



<p>从硬件的角度来说，实现这样的并行化并不难，这就像每次都执行同一个指令的超标量处理器，CPU设计厂商唯一需要做的就是增大寄存器的容量而已。Intel在过去20年正在不断地增大可以并行的向量长度，从SSE的128bits到AVX512的512bits。而ARM从ARMv8a开始便从NEON的128bits飞跃到了SVE的2048bits长度，甚至还支持可变长度。</p>



<p>现代的x86-64处理器都支持SSE指令集，所以现在的编译器如果编译64位平台的目标文件，会自动的将SSE指令集加入用于优化。由于SIMD指令集发展迅速，不少指令的延迟甚至和传统的标量命令不相上下，而且SSE指令集也拥有操作单个操作数的指令，现代编译器在默认情况下对于单个浮点数的操作也会使用SSE指令集而不是使用传统的x87浮点指令。另外，几乎所有的体系结构都拥有自己的SIMD指令集。</p>



<p>像渲染画面或者科学计算这种简单而重复的任务很适合SIMD指令集，事实上，GPU的工作原理也与SIMD类似。但不幸的是，在大多数普通（没有经过特别的思考而写出的）代码中，SIMD指令集并不能被很好的应用。现代编译器全部都有不同程度的循环自动向量化（使用SIMD指令集），但是当程序的编写者没有很好地考虑数据的依赖性和内存布局（马上就会谈到）时，编译器往往不能对代码进行什么优化。而幸运的是，通常通过简单的改动，就可以编译器明白某些循环是可以被优化的，进而大幅的提升程序的运行速度。</p>



<h2 class="wp-block-heading">内存和内存墙</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>现代处理器实在是太快了，以至于它们大多数时候都在等待内存响应，而不是干正事。</p><cite>——佚名（忘记出处了）</cite></blockquote>



<p>自从计算机发明以来，处理器的发展速度远远超过存储的发展速度，以下是一个对比图：</p>



<div class="wp-block-image"><figure class="aligncenter"><img decoding="async" src="https://blog.royalsloth.eu/posts/2021/compiler-will-optimize-that-away/cpuMemoryPerformance_min.png" alt="Performance of processors and memory through the years 1980-2010"/><figcaption>处理器和内存速度对比</figcaption></figure></div>



<p>对于现代处理器来说，内存访问非常的昂贵。</p>



<ul class="wp-block-list"><li>在1980年，CPU访问一次内存通常只需要一个周期。</li><li>在2021年，CPU访问内存大约需要300-500个周期。</li></ul>



<p>当我们考虑到，我们在CPU上使用了那么多种手段来压榨运算器，使IPC能够突破1，这样来看，内存就更慢了。以下是一个表格，展示了如果处理器周期看作是1秒，访问其它的存储器需要的时间。</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><td>事件</td><td>延迟</td><td>等效延迟</td></tr><tr><td>CPU周期</td><td>0.2ns</td><td>1s</td></tr><tr><td>L1缓存访问</td><td>0.9ns</td><td>4s</td></tr><tr><td>L2缓存访问</td><td>3ns</td><td>15s</td></tr><tr><td>L3缓存访问</td><td>10ns</td><td>50s</td></tr><tr><td>内存访问</td><td>100ns</td><td>8分钟</td></tr><tr><td>固态硬盘访问</td><td>10-100us</td><td>15-150小时</td></tr><tr><td>机械硬盘访问</td><td>1-10ms</td><td>2-18月</td></tr></tbody></table><figcaption>等效延迟表</figcaption></figure>



<p>可以看到，现代CPU实在是太快了，程序编写者现在要比过去花费更多的精力来使他们的程序能够充分利用CPU的性能，而不是卡在内存操作上。</p>



<p>为了解决这个严重的问题，处理器设计者们也想出了办法，也就是上面的表格中已经出现了的缓存。80年代的CPU由于没有内存墙的问题，所以基本都没有设计缓存。而现代CPU通常而言都有高达三级的缓存（某些低功耗和移动端的CPU只有两级），理解这些缓存是怎么样工作的有利于程序设计者写出更快的程序。</p>



<h2 class="wp-block-heading">缓存</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Cache这个词读音和Cash（现金）一样，而不是kay-sh、ca-shay或者Cake！</p><cite>我</cite></blockquote>



<p>现代处理器为了解决内存墙，使用多级的缓存来避免内存延迟的影响。一个典型的缓存结构是这样的：</p>



<figure class="wp-block-table is-style-stripes"><table><tbody><tr><th>等级</th><th>大小</th><th>延迟</th><th>物理位置</th></tr><tr><td>L1 cache</td><td>32 KB</td><td>4 cycles</td><td>每个核心内部</td></tr><tr><td>L2 cache</td><td>256 KB</td><td>12 cycles</td><td>每个die或每个核心</td></tr><tr><td>L3 cache</td><td>6 MB</td><td>~21 cycles</td><td>整个处理器共享或每die共享</td></tr><tr><td>RAM</td><td>4+ GB</td><td>~117 cycles</td><td>主板上的内存条上</td></tr></tbody></table><figcaption>缓存等级</figcaption></figure>



<p>令人高兴的是，现代处理器的缓存机制出奇的有效，L1缓存的命中率在大多数时候高达90%，这说明在大多数情况下内存访问的代价仅仅是几个周期而已。</p>



<p>缓存能够取得这么好的效果主要是因为程序具有很好的<strong>局部性</strong>。分为空间局部性和时间局部性。<strong>时间局部性</strong>说的是当程序访问一块内存时，很有可能接下来连续访问这一块内存。<strong>空间局部性</strong>是说程序访问一块内存，那么它很可能也许要访问附近的内存。为了利用好这样的局部性，内存中的数据是一块一块地从内存条上复制到缓存中的，这些快被称为<strong>缓存行</strong>。</p>



<p>从硬件的角度来说，缓存的工作原理和键值对表很类似。Key就是内存的地址，而Value则是对应的数据。事实上Key并不一定是完整的地址，通常是地址的高位一部分，而低位被用来索引缓存本身。用物理地址和虚拟地址来作为Key都是可行的，也各有好坏（就像所有的事情一样）。使用虚拟地址的缺点是进程的上下文切换需要刷新缓存，这非常昂贵。使用物理地址的缺点则是每次查缓存都需要先查页表。因此现代处理器通常采用虚拟地址作为缓存索引，而使用物理地址作为缓存行的标记。这样的方法又被称作“<strong>虚拟索引——物理标记</strong>”缓存。</p>



<h2 class="wp-block-heading">缓存冲突和关联度</h2>



<p>理想状态下，缓存应当保存最近最常使用的数据，但是对于CPU上的硬件缓存来说，有效维护使用状态的算法不能满足严格的延迟要求（例如Linux内核页缓存使用的LRU，有兴趣可以看我前面的文章<a href="https://blog.eastonman.com/blog/2021/04/linux-multi-lru/" title="https://blog.eastonman.com/blog/2021/04/linux-multi-lru/">Linux内核页面置换算法</a>），也难以用硬件实现，所以通常处理器使用简单的方法：<strong>每一个缓存行直接对应内存的几个位置</strong>。由于对应的几个位置不太可能同时访问，因此缓存是有效的。</p>



<p>这样的做法非常快速（本来缓存的设计目的就是这样的），但是当程序的确不断地来回访问同一个缓存行对应的不同位置的时候，缓存控制单元不得不反复从内存中装载数据，非常耗时，这被称为<strong>缓存冲突</strong>。解决办法就是不限制每一个内存区域只对应一个缓存行，而是对应几个，这个几个就被称作是<strong>缓存关联度</strong>。</p>



<p>当然，最快的方法是每个内存区域对应一个缓存行，这被叫做<strong>直接映射缓存</strong>，而使用4个关联度的缓存被称作<strong>4通道关联缓存</strong>。内存可以装载到任意一个缓存行的缓存叫做<strong>全关联缓存</strong>。使用关联的缓存带来的好处是大大减少了缓存冲突而保持查询延迟在一个合理的范围内。这也是现代处理器通常使用的方法。</p>



<h2 class="wp-block-heading">致谢</h2>



<p><a href="http://www.lighterra.com/papers/modernmicroprocessors/">Modern Microprocessors: A 90-Minute Guide!</a> 2016 By <a href="http://www.lighterra.com/jason/">Jason Robert Carey Patterson</a></p>



<p><a href="https://blog.royalsloth.eu/posts/the-compiler-will-optimize-that-away/" target="_blank" rel="noreferrer noopener" title="https://blog.royalsloth.eu/posts/the-compiler-will-optimize-that-away/">The compiler will optimize that away</a> 2021 By <a target="_blank" href="https://www.royalsloth.eu/" rel="noreferrer noopener">RoyalSloth</a></p>



<p><a href="http://www.anandtech.com/show/9582/intel-skylake-mobile-desktop-launch-architecture-analysis">The Intel Skylake Mobile and Desktop Launch, with Architecture Analysis</a> 2015 By&nbsp;<a href="https://www.anandtech.com" target="_blank" rel="noreferrer noopener" title="https://www.anandtech.com">AnandTech</a></p>



<p><a href="https://en.wikichip.org/wiki/intel/microarchitectures/skylake_(client)" target="_blank" rel="noreferrer noopener" title="https://en.wikichip.org/wiki/intel/microarchitectures/skylake_(client)">Skylake(Client) Microarchitecture</a> 2020 By <a href="https://en.wikichip.org" target="_blank" rel="noreferrer noopener" title="https://en.wikichip.org">WikiChip</a></p>



<h2 class="wp-block-heading">深入阅读</h2>



<ul class="wp-block-list"><li>《深入理解计算机系统》（CSAPP)</li><li>《计算机体系结构：一种量化方法》</li><li><a href="https://www.bilibili.com/video/BV1Mo4y1Z7jb" target="_blank" rel="noreferrer noopener">吹牛还是真牛？苹果M1全网最硬核评测（上）</a> By 极客湾@bilibili.com</li></ul>



<p></p>The post <a href="https://blog.eastonman.com/blog/2021/05/modern-processor/">现代处理器结构</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/05/modern-processor/feed/</wfw:commentRss>
			<slash:comments>11</slash:comments>
		
		
			</item>
		<item>
		<title>Git: Failed sending HTTP2 data解决办法</title>
		<link>https://blog.eastonman.com/blog/2021/04/git-failed-sending-http2-data/</link>
					<comments>https://blog.eastonman.com/blog/2021/04/git-failed-sending-http2-data/#comments</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Sun, 25 Apr 2021 12:47:30 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[Git]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=556</guid>

					<description><![CDATA[<p>预计阅读时间： 5 分钟 最近一段时间发现手中的Debian机器在Git Clone的时候出现奇怪的错误，经过 [&#8230;]</p>
The post <a href="https://blog.eastonman.com/blog/2021/04/git-failed-sending-http2-data/">Git: Failed sending HTTP2 data解决办法</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 5 分钟</p>
<p>最近一段时间发现手中的Debian机器在Git Clone的时候出现奇怪的错误，经过一番Google，最终发现是libcurl3-gnutls这个库的bug。</p>



<p></p>



<p>2021-10-01 Update: Buster-backports仍未修复此问题，但是Bullseye已经合入了新的版本，修复了此问题。</p>



<p>2021-06-19 Update: 修复补丁仍未合入backports中，问题的原因似乎是libcurl-guntls的TLSv1.3实现有问题。</p>



<p>2021-08-03 Update: Curl 7.76.1修复了此问题，但尚未合并进入Buster backports, Bullseye也未更新。</p>



<h2 class="wp-block-heading">现象</h2>



<p>在使用https协议进行git clone的时候，出现HTTP2报错</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">xxxxx$> git clone https://github.com/TechMinerApps/portier
Cloning into 'portier'...
fatal: unable to access 'https://github.com/TechMinerApps/portier/': Failed sending HTTP2 data</pre>



<p>起初我以为和GitHub即将禁止Basic Auth的https操作有关，但是使用<code>GIT_TRACE2=2 GIT_CURL_VERBOSE=1</code>进行调试的时候，发现是curl中的问题。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">20:35:22.968833 http.c:756              == Info: Couldn't find host github.com in the .netrc file; using defaults
20:35:22.973819 http.c:756              == Info:   Trying 192.30.255.113:443...
20:35:23.144791 http.c:756              == Info: Connected to github.com (192.30.255.113) port 443 (#0)
20:35:23.168789 http.c:756              == Info: found 411 certificates in /etc/ssl/certs
20:35:23.169022 http.c:756              == Info: ALPN, offering h2
20:35:23.169092 http.c:756              == Info: ALPN, offering http/1.1
20:35:23.341023 http.c:756              == Info: SSL connection using TLS1.3 / ECDHE_RSA_AES_128_GCM_SHA256
20:35:23.341674 http.c:756              == Info:         server certificate verification OK
20:35:23.341682 http.c:756              == Info:         server certificate status verification SKIPPED
20:35:23.341750 http.c:756              == Info:         common name: github.com (matched)
20:35:23.341754 http.c:756              == Info:         server certificate expiration date OK
20:35:23.341757 http.c:756              == Info:         server certificate activation date OK
20:35:23.341763 http.c:756              == Info:         certificate public key: EC/ECDSA
20:35:23.341766 http.c:756              == Info:         certificate version: #3
20:35:23.341786 http.c:756              == Info:         subject: C=US,ST=California,L=San Francisco,O=GitHub\, Inc.,CN=github.com
20:35:23.341791 http.c:756              == Info:         start date: Thu, 25 Mar 2021 00:00:00 GMT
20:35:23.341795 http.c:756              == Info:         expire date: Wed, 30 Mar 2022 23:59:59 GMT
20:35:23.341807 http.c:756              == Info:         issuer: C=US,O=DigiCert\, Inc.,CN=DigiCert High Assurance TLS Hybrid ECC SHA256 2020 CA1
20:35:23.341818 http.c:756              == Info: ALPN, server accepted to use h2
20:35:23.341851 http.c:756              == Info: Using HTTP2, server supports multi-use
20:35:23.341853 http.c:756              == Info: Connection state changed (HTTP/2 confirmed)
20:35:23.341858 http.c:756              == Info: Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
20:35:23.341866 http.c:756              == Info: Failed sending HTTP2 data
20:35:23.341873 http.c:756              == Info: Connection #0 to host github.com left intact</pre>



<p>随即Google了一番后找到了Debian Bug Tracker中的一个提交 <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=987187" target="_blank" rel="noreferrer noopener" title="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=987187">libcurl3-gnutls from debian backports breaks git http operations</a></p>



<p>原文如下，完全一模一样。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Package: libcurl3-gnutls
Version: 7.74.0-1.2~bpo10+1
Severity: important
|X-Debbugs-CC: |debian-backports@lists.debian.org

After upgrading the Debian system from "backports" any git http operations (clone/fetch ...) has stopped working, bringing libcurl3-gnutls to the previous version (7.74.0-1.2~bpo10+1) fix the problem:

Output of git command
> git clone https://github.com/git/git
Cloning into 'git'...
fatal: unable to access 'https://github.com/git/git/': Failed sending HTTP2 data
>

-- apt-cache policy libcurl3-gnutls
libcurl3-gnutls:
  Installato: 7.74.0-1.2~bpo10+1
  Candidato:  7.74.0-1.2~bpo10+1
  Tabella versione:
 *** 7.74.0-1.2~bpo10+1 100
        100 https://deb.debian.org/debian buster-backports/main amd64 Packages
        100 /var/lib/dpkg/status
     7.64.0-4+deb10u2 500
        500 https://deb.debian.org/debian-security buster/updates/main amd64 Packages
     7.64.0-4+deb10u1 500
        500 https://deb.debian.org/debian buster/main amd64 Packages

-- Extra detailed informations:
- Output of GIT_CURL_VERBOSE=1 GIT_TRACE2=1 git clone https://github.com/git/git
13:03:36.251941 common-main.c:48                  version 2.29.2
13:03:36.251960 common-main.c:49                  start git clone https://github.com/git/git
13:03:36.251981 git.c:445                         cmd_name clone (clone)
13:03:36.252185 repository.c:130                  worktree /tmp/gh/git
Clone in 'git' in corso...
13:03:36.253276 run-command.c:735                 child_start[0] git remote-https origin https://github.com/git/git
13:03:36.254468 common-main.c:48                  version 2.29.2
13:03:36.254485 common-main.c:49                  start /usr/lib/git-core/git remote-https origin https://github.com/git/git
13:03:36.254572 git.c:723                         cmd_name _run_dashed_ (clone/_run_dashed_)
13:03:36.254586 run-command.c:735                 child_start[0] git-remote-https origin https://github.com/git/git
13:03:36.259050 common-main.c:48                  version 2.29.2
13:03:36.259065 common-main.c:49                  start /usr/lib/git-core/git-remote-https origin https://github.com/git/git
13:03:36.259138 repository.c:130                  worktree /tmp/gh
13:03:36.259176 remote-curl.c:1482                cmd_name remote-curl (clone/_run_dashed_/remote-curl)
13:03:36.259581 http.c:756              == Info: Couldn't find host github.com in the .netrc file; using defaults
13:03:36.469244 http.c:756              == Info:   Trying 140.82.121.3:443...
13:03:36.520476 http.c:756              == Info: Connected to github.com (140.82.121.3) port 443 (#0)
13:03:36.544248 http.c:756              == Info: found 381 certificates in /etc/ssl/certs
13:03:36.544365 http.c:756              == Info: ALPN, offering h2
13:03:36.544371 http.c:756              == Info: ALPN, offering http/1.1
13:03:36.595957 http.c:756              == Info: SSL connection using TLS1.3 / ECDHE_RSA_AES_128_GCM_SHA256
13:03:36.596671 http.c:756              == Info: 	 server certificate verification OK
13:03:36.596680 http.c:756              == Info: 	 server certificate status verification SKIPPED
13:03:36.596778 http.c:756              == Info: 	 common name: github.com (matched)
13:03:36.596786 http.c:756              == Info: 	 server certificate expiration date OK
13:03:36.596790 http.c:756              == Info: 	 server certificate activation date OK
13:03:36.596799 http.c:756              == Info: 	 certificate public key: EC/ECDSA
13:03:36.596804 http.c:756              == Info: 	 certificate version: #3
13:03:36.596838 http.c:756              == Info: 	 subject: C=US,ST=California,L=San Francisco,O=GitHub\, Inc.,CN=github.com
13:03:36.596854 http.c:756              == Info: 	 start date: Thu, 25 Mar 2021 00:00:00 GMT
13:03:36.596868 http.c:756              == Info: 	 expire date: Wed, 30 Mar 2022 23:59:59 GMT
13:03:36.596884 http.c:756              == Info: 	 issuer: C=US,O=DigiCert\, Inc.,CN=DigiCert High Assurance TLS Hybrid ECC SHA256 2020 CA1
13:03:36.596914 http.c:756              == Info: ALPN, server accepted to use h2
13:03:36.596955 http.c:756              == Info: Using HTTP2, server supports multi-use
13:03:36.596960 http.c:756              == Info: Connection state changed (HTTP/2 confirmed)
13:03:36.596974 http.c:756              == Info: Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
13:03:36.596984 http.c:756              == Info: Failed sending HTTP2 data
13:03:36.596993 http.c:756              == Info: Connection #0 to host github.com left intact
13:03:36.597168 usage.c:64                        error impossibile accedere a 'https://github.com/git/git/': Failed sending HTTP2 data
fatal: impossibile accedere a 'https://github.com/git/git/': Failed sending HTTP2 data
13:03:36.597194 usage.c:68                        exit elapsed:0.338490 code:128
13:03:36.597203 trace2/tr2_tgt_normal.c:123       atexit elapsed:0.338501 code:128
13:03:36.598095 run-command.c:990                 child_exit[0] pid:179509 code:128 elapsed:0.343497
13:03:36.598114 git.c:745                         exit elapsed:0.343919 code:128
13:03:36.598155 trace2/tr2_tgt_normal.c:123       atexit elapsed:0.343959 code:128
13:03:36.598389 transport-helper.c:581            exit elapsed:0.346748 code:128
13:03:36.598639 trace2/tr2_tgt_normal.c:123       atexit elapsed:0.347003 code:128</pre>



<h2 class="wp-block-heading">解决方法</h2>



<p>既然是Debian官方源中的问题，那除了等官方解决以外，就只能降级这个库了。</p>



<p>降级到stable，也就是buster的命令如下。APT会自动降级有依赖关系的库，如libcurl4</p>



<p>2021-10-01 Update：Debian 11发布后Buster不再是Stable，因此在Debian 10遇到此问题需要降级到buster或者oldstable，推荐升级至Debian 11解决此问题。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Downgrade command
sudo apt reinstall libcurl3-gnutls/buster
</pre>



<p></p>The post <a href="https://blog.eastonman.com/blog/2021/04/git-failed-sending-http2-data/">Git: Failed sending HTTP2 data解决办法</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/04/git-failed-sending-http2-data/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>Linux内核页面置换算法</title>
		<link>https://blog.eastonman.com/blog/2021/04/linux-multi-lru/</link>
					<comments>https://blog.eastonman.com/blog/2021/04/linux-multi-lru/#respond</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Sat, 03 Apr 2021 12:23:02 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Linux内核]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=523</guid>

					<description><![CDATA[<p>预计阅读时间： 9 分钟 最近来自Google的Yu Zhao向Linux内核提交了一个Patch，修改了内核 [&#8230;]</p>
The post <a href="https://blog.eastonman.com/blog/2021/04/linux-multi-lru/">Linux内核页面置换算法</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 9 分钟</p>
<p>最近来自Google的Yu Zhao向Linux内核提交了一个Patch，修改了内核内存管理模块中的页面置换算法，提出了多级LRU，本文带你了解页面置换算法和多级LRU的优势。</p>



<h2 class="wp-block-heading">什么是页和页面置换</h2>



<p>我们知道，在几乎所有的现代操作系统和处理器上都使用了<strong>内存分页机制</strong>。某些嵌入式操作系统和特殊情况下，处理器资源很紧张，不需要考虑进程间安全问题，甚至没有线程这一概念时，内存分页也就没有必要了。内存分页是为了<strong>虚拟内存机制</strong>而提出的，大多数现代操作系统上为了进程间的隔离，每个进程拥有自己独立的地址空间，这就是虚拟内存。虚拟内存需要与物理内存有一个映射，但是问题来了，这个映射表是需要占用内存空间的，尤其是64位处理器上，需要映射的地址空间非常庞大，如果对地址做一一映射，映射表将会比物理内存还要大。这时就需要对内存进行<strong>分页映射</strong>，即将内存分为一定大小，如4KB大小的页，这些页面与物理内存中的4KB大小的页面一一对应，这就是内存分页机制。但是页表仍然很大，现代体系结构中一般使用<strong>多级页表</strong>来大幅减少页表所占用的空间，例如Linux中的四级页表：</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="701" height="351" src="https://blog.eastonman.com/wp-content/uploads/2021/04/e043a1db694a5145a434eecd98fe1d0b1603800172716.png" alt="" class="wp-image-526" srcset="https://blog.eastonman.com/wp-content/uploads/2021/04/e043a1db694a5145a434eecd98fe1d0b1603800172716.png 701w, https://blog.eastonman.com/wp-content/uploads/2021/04/e043a1db694a5145a434eecd98fe1d0b1603800172716-300x150.png 300w" sizes="(max-width: 701px) 100vw, 701px" /><figcaption class="wp-element-caption">Linux四级页表</figcaption></figure>



<p>其中PGD、PUD等分别是一、二、三、四级页表项，Offset是页内部的地址。Linux在最新的Intel Xeon处理器上甚至使用了五级页表，具体处理器支持就不赘述了。值得注意的是，<strong>页表需要硬件支持，也就是CPU的内存管理单元（MMU）的支持</strong>。以下是一个Intel Xeon Gold 6230的MMU支持，可以看到40 bits physical，也就值它的MMU支持40位的硬件寻址，48 bits physical也就是目前Linux四级页表所支持的虚拟地址空间大小。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="687" height="161" src="https://blog.eastonman.com/wp-content/uploads/2021/04/image.png" alt="" class="wp-image-527" srcset="https://blog.eastonman.com/wp-content/uploads/2021/04/image.png 687w, https://blog.eastonman.com/wp-content/uploads/2021/04/image-300x70.png 300w" sizes="(max-width: 687px) 100vw, 687px" /></figure>



<p>那页面置换又是什么？</p>



<p>页面置换也来自一个几乎所有的现代操作系统都拥有的功能——<strong>交换机制。</strong>当物理内存接近满的时候，操作系统为了使整个系统仍然可用，会将一部分不常使用的页面移到磁盘上，为更经常使用的页面腾出空间。当然，这样的做的代价是会降低性能，这个机制也不是万能的，当活跃使用的内存也借近物理内存大小的时候，系统依然会变得几乎不可用，不过至少这样的机制使得系统有机会自己恢复，而不是不得不由OOM Killer杀死一些进程。这个交换机制显然需要一个优秀的算法来判断哪一部分页面是“陈旧的”，这就引出了这篇文章的主题——<strong>页面置换算法。</strong></p>



<h2 class="wp-block-heading">页面置换算法</h2>



<p>操作系统发展了50年，页面置换算法也在不断地改进，我们先来盘点一下曾经出现过的页面置换算法有哪一些。</p>



<ul class="wp-block-list">
<li><strong><s>最优算法</s>：</strong>想得真美，甚至关于“最优”定义也取决于实际场景。</li>



<li><strong>NRU（最近未使用）算法：</strong>一个简易的算法，维护一个链表，每次淘汰末端的页，每次读取时将页面移至表头。是可以接受的算法，实现简易，性能损耗小。</li>



<li><strong>FIFO：</strong>维护一个队列，每次直接清理队列头的页面。几乎无性能开销，但是效果不好。</li>



<li><strong>Second Chance FIFO：</strong>维护一个循环队列，每次清理访问标志位为0的页面，每次访问后将标志位置为1，这样只要周期内页面有访问，就会免遭清理，除非所有的页都为1。由于使用循环队列，内存操作较多，故一般使用下面一个算法。</li>



<li><strong>时钟算法：</strong>指针和环形链表/队列实现SCFIFO，消除了内存操作，也是较为常用的算法，效果较好，比较NRU有较大改善。</li>



<li><strong>LRU（最近最少使用）算法：</strong>对于最优算法在大多数情况下很好的近似，效果优异，但是较难实现，目前的算法要不就是时间复杂度较高，要不就是空间复杂度较高。大多数时候还需要硬件支持。</li>



<li><strong>NFU算法：</strong>由软件模拟的LRU，使用的频度Freq由系统时钟中断处理程序统计，效果较好，但是有一定的性能开销，且没有实现“最近”。</li>



<li><strong>老化算法：</strong>在NFU基础上加入频度老化，实现了“最近”，越久的使用次数权重越低。</li>
</ul>



<p>Linux内核现在使用的页面置换算法是<strong>两级的软件LRU</strong>，也就是分为active和inactive类型的两个链表，并实现软件LRU（NFU）算法，由于基于硬件的LRU在当今的体系结构中无法实现，下文中我们把这一种算法简称为LRU。</p>



<h2 class="wp-block-heading">两级LRU的问题</h2>



<h3 class="wp-block-heading">TL;DR</h3>



<p>现行的算法CPU开销太大，而且经常做出错误的决策。</p>



<h3 class="wp-block-heading">粒度</h3>



<p>Active/Inactive这两种分类在现在的硬件环境下，实在是一个相当粗的粒度。要知道，Linux统治的服务器领域中，内存上T的并不少见，甚至是很常见。Intel近几年推广的傲腾内存更是对内存调度提出了更高的要求。这个活动/非活动的分类并不是一个好的分类方法，像文件存取这样的情境下，经常会有周期性的内存操作，而这时页面会在两个表中来回移动，并不能很好表现这个页面是否是活跃的。而许多正在执行的可执行文件的代码段常常被移除，仅仅是因为它们在相当短的一段时间内没有被使用。这导致了在安卓和ChromeOS上交互进程的反应延迟。</p>



<h3 class="wp-block-heading">倒排页表扫描的性能问题</h3>



<p>由于LRU算法需要周期性地根据倒排页表（rmap）以更新页面的访问计数器，但是，rmap拥有相当复杂的数据结构，局部性很差，导致在扫描rmap的时候CPU缓存命中率很不理想。现代CPU的缓存命中率对执行速度由极大的影响，缓存命中率低下可以使CPU的执行速度下降到峰值的10%甚至更低。</p>



<h2 class="wp-block-heading">多级LRU</h2>



<p>Yu Zhao最近提交的Patch中提出了Multigenerational LRU算法，旨在解决现在内核使用的两级LRU的问题。这个多级LRU借鉴了老化算法的思路，按照<strong>页面的生成（分配）时间将LRU表分为若干Generation</strong>。在LRU页面扫面的时候，使用增量的方式扫描，根据周期内访问过的页面对<strong>页表</strong>进行扫描，除非这段时间内访问的内存分布非常稀疏，通常<strong>页表相对于倒排页表有更好的局部性</strong>，进而可以提升CPU的缓存命中率。</p>



<p>在邮件列表中Yu Zhao说明了他使用更改后的算法在几个模拟场景中的情况：</p>



<ul class="wp-block-list">
<li>Android：减少了18%的low memory kill，进而减少了16%的冷启动。</li>



<li>Borg（Google的云资源编排系统）：活跃内存占用降低了。</li>



<li>ChromeOS：非活跃页面减少了96%的内存占用，减少了59%的OOM Kill。</li>
</ul>



<h2 class="wp-block-heading">结语</h2>



<p>Linux内核页面置换算法的修改影响非常巨大，哪怕只是“微小”的调整。因为Linux的使用场景并不仅限于常用的几种情况，因此这个Patch需要经过相当长的一段讨论和各个开发者的测试才可能最终被合并进入主线。虽然大部分的内核开发者还没有发表对此的意见，但已有部分人对这个新算法持有积极的态度和较好的评价，所以我也认为这个Patch将会最终进入Linux内核主线。</p>



<p>[2023-01-07] Update: MLRU 已在 Linux 6.2 合并进入主线内核</p>



<h2 class="wp-block-heading">深入阅读</h2>



<ul class="wp-block-list">
<li><a href="https://lwn.net/SubscriberLink/851184/01351eb745a6405d/" title="https://lwn.net/SubscriberLink/851184/01351eb745a6405d/" target="_blank" rel="noreferrer noopener">The multi-generational LRU</a></li>



<li><a href="https://lwn.net/ml/linux-kernel/20210313075747.3781593-1-yuzhao@google.com/" target="_blank" rel="noreferrer noopener" title="https://lwn.net/ml/linux-kernel/20210313075747.3781593-1-yuzhao@google.com/">[PATCH v1 00/14] Multigenerational LRU</a></li>



<li>Long-term SLOs for reclaimed cloud computing resources    <a href="https://research.google/pubs/pub43017/" target="_blank" rel="noreferrer noopener">https://research.google/pubs/pub43017/</a> </li>



<li>Profiling a warehouse-scale computer    <a href="https://research.google/pubs/pub44271/" target="_blank" rel="noreferrer noopener">https://research.google/pubs/pub44271/</a> </li>



<li>Evaluation of NUMA-Aware Scheduling in Warehouse-Scale Clusters    <a href="https://research.google/pubs/pub48329/" target="_blank" rel="noreferrer noopener">https://research.google/pubs/pub48329/</a> </li>



<li>Software-defined far memory in warehouse-scale computers    <a href="https://research.google/pubs/pub48551/" target="_blank" rel="noreferrer noopener">https://research.google/pubs/pub48551/</a> </li>



<li>Borg: the Next Generation    <a href="https://research.google/pubs/pub49065/" target="_blank" rel="noreferrer noopener">https://research.google/pubs/pub49065/</a></li>
</ul>



<p></p>The post <a href="https://blog.eastonman.com/blog/2021/04/linux-multi-lru/">Linux内核页面置换算法</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/04/linux-multi-lru/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Sigstore项目是什么</title>
		<link>https://blog.eastonman.com/blog/2021/03/sigstore/</link>
					<comments>https://blog.eastonman.com/blog/2021/03/sigstore/#respond</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Fri, 12 Mar 2021 13:41:43 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[Linux Foundation]]></category>
		<category><![CDATA[Trust]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=509</guid>

					<description><![CDATA[<p>Sigstore是Linux基金会最新的项目，它是基于Certificate Transparency Log的软件签名新模式。Sigstore解决了传统的PGP签名所带来的私钥保管问题，它有出色的开放性和扩展性，被誉为软件签名的Let's Encrypt。</p>
The post <a href="https://blog.eastonman.com/blog/2021/03/sigstore/">Sigstore项目是什么</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 8 分钟</p>
<p>Sigstore是Linux基金会最新的项目，它是基于Certificate Transparency Log的软件签名新模式。Sigstore解决了传统的PGP签名所带来的私钥保管问题，它有出色的开放性和扩展性，被誉为软件签名的Let&#8217;s Encrypt。</p>



<h2 class="wp-block-heading">TL;DR</h2>



<p>简要地说，Sigstore项目实现的是通过Certificate Transparency Log记录每个签名的信息，终端用户需要使用的时候查询Log并验证。由于公钥被记录在Log中，因此每次签名可以使用不同的密钥。由此大大降低了私钥泄露的风险和保管私钥的痛苦。</p>



<h2 class="wp-block-heading">我们为什么需要Sigstore</h2>



<h3 class="wp-block-heading">开源供应链的脆弱性</h3>



<amp-fit-text layout="fixed-height" min-font-size="6" max-font-size="72" height="80"><p>从仍在继续发酵的SolarWinds攻击事件，到刚刚兴起的包管理器重名包攻击，可以看到在越来越多的厂商采用开源解决方案的同时，安全问题也在变得日益严重。开源供应链的这几个问题愈发突出：</p></amp-fit-text>



<ul class="wp-block-list"><li><strong>工具链长：</strong>攻击面广泛，任何一个环节都有可能导致问题，比如SolarWinds事件，带毒的VSCode造成了很多供应链上游的软件包出现了问题。</li><li><strong>公开透明：</strong>公开透明的CI、公开的源码和公开的管理机制给了有心之人更多的机会。</li><li><strong>不可控性：</strong>开源工具链中的相当多环节都处于社区或者其它公司的控制中，不受末端用户和公司的控制。</li></ul>



<p>正如Luke Hinds在视频会议中所展示的那样，即便是大型公司的供应链中也存在有相当多的安全风险。</p>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2021/03/image-1024x578.png" alt="" class="wp-image-512" width="586" height="331" srcset="https://blog.eastonman.com/wp-content/uploads/2021/03/image-1024x578.png 1024w, https://blog.eastonman.com/wp-content/uploads/2021/03/image-300x169.png 300w, https://blog.eastonman.com/wp-content/uploads/2021/03/image-768x434.png 768w, https://blog.eastonman.com/wp-content/uploads/2021/03/image.png 1144w" sizes="(max-width: 586px) 100vw, 586px" /><figcaption>Supply Chain</figcaption></figure></div>



<h3 class="wp-block-heading">传统CA方式的问题</h3>



<p>传统上，公司使用CA体系来进行软件签名。例如，一个希望软件能在Windows 10 上不报风险提示地运行，那么这名开发者/开发公司将不得不花费一定的钱款向DigiCert、Symantec或者类似的CA购买用于签名的证书（注意，Let&#8217;s Encrypt颁发的证书仅能用于域名验证，不能用于软件签名）来对他们的软件进行签名。当然这个体系的缺陷很明显：<strong>中心化</strong>。我们依赖于对CA的绝对信任来保证我们所运行的程序是未经篡改的，如果遇到WoSign之类的CA那么这一体系的安全性便荡然无存。</p>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2021/03/image-1-1024x580.png" alt="" class="wp-image-515" width="562" height="318" srcset="https://blog.eastonman.com/wp-content/uploads/2021/03/image-1-1024x580.png 1024w, https://blog.eastonman.com/wp-content/uploads/2021/03/image-1-300x170.png 300w, https://blog.eastonman.com/wp-content/uploads/2021/03/image-1-768x435.png 768w, https://blog.eastonman.com/wp-content/uploads/2021/03/image-1.png 1152w" sizes="(max-width: 562px) 100vw, 562px" /><figcaption>CA Architecture</figcaption></figure></div>



<h3 class="wp-block-heading">PGP是最佳实践吗？</h3>



<p>相当多的生态系统和包管理器等深度地使用PGP作为它们的信任体系基础，迄今为止，它确实在相当多的情况下表现得很不错。但是PGP信任体系也有它的问题：</p>



<ul class="wp-block-list"><li><strong>私钥保管：</strong>私钥显然不能够被保存在人类的大脑里，那把它存放在何处就成了问题。如果仅仅保存一份，那么如果这一份由于某种原因（物理损坏，OVH机房燃起来了等等）丢失了，那么重建信任将会是一个噩梦。如果存放多了又会带来问题，万一私钥泄露了，并遭到利用，那么撤回这些遭受污染的版本也是一个噩梦。</li><li><strong>多开发者：</strong>下面的截图展示了Node.js开发者使用中和曾经使用的GPG公钥，这么多的核心开发者使用每个人自己的密钥进行发布，终端用户如果想要验证签名，就不得不导入者数十个密钥并且信任它们，这给验证带来了麻烦。</li><li><strong>公钥分发：</strong>大多数的项目使用简单的HTTP或HTTPS的方式进行公钥的分发。而这些Web服务器实在是很容易遭受黑客的攻击，如果这些公钥遭到替换，那么安全也就无从谈起了。</li><li><strong>信任体系的建立：</strong>PGP的信任体系是基于去中心化的人脉关系，通常人们在进行互相签名信任之前会在现实世界中认识或者见面，这无疑给信任体系的建立带来了麻烦，尤其是在新冠肺炎全球大流行的当下。</li></ul>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://blog.eastonman.com/wp-content/uploads/2021/03/image-2.png" alt="" class="wp-image-516" width="388" height="353" srcset="https://blog.eastonman.com/wp-content/uploads/2021/03/image-2.png 550w, https://blog.eastonman.com/wp-content/uploads/2021/03/image-2-300x273.png 300w" sizes="(max-width: 388px) 100vw, 388px" /><figcaption>Node.js GPG</figcaption></figure></div>



<h2 class="wp-block-heading">Sigstore解决了什么</h2>



<h3 class="wp-block-heading">私钥保管问题</h3>



<p>我们在开头曾经提到过，Sigstore是使用CT Log作为信任源（Trust Root）的，由于在Log中包括了公钥信息，开发者在每次签名的时候不需要相同的密钥，每次使用的密钥对在签名过后，公钥被上传至Log，私钥则被销毁，这样一来开发者就无需小心翼翼地保管自己的密钥了。由于Log的增长性，已添加的Log可以按照不可更改（Immutable）的数据来进行保管，相对于现行的GPG公钥方式来说，提供了更好的安全性。</p>



<h3 class="wp-block-heading">使用的简便性</h3>



<p>由于传统软件签名方式的复杂性，很多的开发者并不会去对自己发布的软件进行签名，绝大多数的用户更加不会对软件进行验证。那么如何才能让大家都参与到这一信任体系的建设中呢？</p>



<p>其关键在于<strong>便捷性</strong>。哪怕一套体系再完美，如果缺乏便捷性，那么很少会有人问津。有一个很好假设可以说明这个事情，如果微软宣布开发了一个绝对安全的新系统SecureOS，我们假设它真的如微软所说绝对的安全，但是它不兼容Windows软件，没有GUI，甚至不能在x86-64体系的处理器上运行，那么会有多少企业为这个新操作系统买单呢？答案可想而知。</p>



<p>Sigstore使用几个方式来保证便捷性和域现有体系的兼容性，以确保迁移成本最小化。</p>



<p><strong>第一，Sigstore使用Web PKI + OpenID Connect等身份信息提供者进行身份的验证。</strong>未来还可能添加新的验证方式。这无疑节省了巨大的成本，这使得企业可以几乎无痛地迁移到Sigstore所主导的新体系。这个身份验证也完全可以不使用这一套系统，只要能够确定开发者的身份，GPG、x509、MiniSign都是支持的。</p>



<p><strong>第二，Sigstore在签名这一环节使用x509标准。</strong>这使得Sigstore甚至可以与Let&#8217;s Encrypt合作提供免费的签名凭据。而且，x509的基础设施几乎遍地都是，这相比与MiniSign这种解决方案无疑要简便的多。</p>



<p><strong>第三，终端客户验证使用CT Log的形式。</strong>这使得客户不再需要导入信任的证书或者由操作系统或者浏览器等维护一个CA列表。任何出现在CT Log上的记录都是真实的，任何人都可以验证它的有效性。还可以使用不同的验证策略进一步加强安全性，例如要求同一个版本至少要由三位维护者共同签名。</p>



<h2 class="wp-block-heading">结语</h2>



<p>我也曾经有过对我所开发的软件进行签名的想法，但是被高昂（学生狗买不起）的证书价格劝退了。作为一名开发者，我由衷地希望Sigstore这个项目能够发展起来，毕竟有Linux Foundation做支持，这个体系推广开来以后，应该可以节省很多这方面的成本。</p>



<h2 class="wp-block-heading">参考</h2>



<ul class="wp-block-list"><li><a href="https://www.youtube.com/watch?v=3LKHKpcH2x8" target="_blank" rel="noreferrer noopener nofollow" title="CommCommunity Central: Connecting with sigstoreunity Central: Connecting with sigstore">Community Central: Connecting with sigstore</a> By RedHat Community</li><li><a href="https://sigstore.dev/what_is_sigstore/" target="_blank" rel="noreferrer noopener nofollow" title="What is Sigstore">What is Sigstore</a></li><li><a href="https://sigstore.dev" target="_blank" rel="noreferrer noopener nofollow">Sigstore Homepage</a></li></ul>



<p></p>The post <a href="https://blog.eastonman.com/blog/2021/03/sigstore/">Sigstore项目是什么</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/03/sigstore/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
