C#代码如何影响CPU缓存速度?
<h1 id="cpu与ram的隔阂">CPU与RAM的隔阂</h1><p>CPU与RAM是两个独立的硬件,并非集成在一起。所以他们两个之间一定会存在<code>一个连接的桥梁</code>,这个桥梁的名字叫做内存总线。</p>
<p>内存总线由三部分组成:</p>
<ol>
<li>
<p>地址总线(Address Bus)<br>
用于传输内存地址,也就是我们经常看到的0X77F84FAB这种类似的内存地址,一根总线代表一个电信号,一个电信号能传递<code>高电平/低电平</code>两种信息,用二进制表示就是1/0。因此总线数量的多少决定了可以传递内存地址的大小,比如你有32根总线,就代表你总线宽度32。2^32=4294967296,等于4GB内存,这就是32位操作系统只支持4G内存的由来。</p>
</li>
<li>
<p>数据总线(Data Bus)<br>
用于传输数据,原理同上,一根总线代表1bit传输段位,64根总线就是 64bit=8byte。一次性可以传输8byte单位的数据。</p>
</li>
<li>
<p>控制总线(Control Bus)<br>
用户传输控制信号,比如一根用来"读信号输出"的开关,一根"写信号输出"的开关。一根"时钟信号"的开关</p>
</li>
</ol>
<blockquote>
<p>更详细可以参考此文:https://www.cnblogs.com/lmy5215006/p/18469027</p>
</blockquote>
<h1 id="cpu-cache">CPU Cache</h1>
<p>可以看到,假设CPU要读取1kb的数据,你的数据总线总线只有64根,1024/8=128,你需要来回倒腾128次,才能读取完毕,这一来二去就加大了内存之间的延迟,为了优化此性能瓶颈,<br>
<code>CPU除了寄存器外临时存储数据,还内置了Cache来临时存储数据与指令。</code></p>
<blockquote>
<p>游戏神U 9800x3d就是依靠96MB的"巨大L3缓存",来降低了内存延迟,从而在游戏场景默秒全Intel</p>
</blockquote>
<table>
<thead>
<tr>
<th>特性</th>
<th>L1缓存</th>
<th>L2缓存</th>
<th>L3缓存</th>
</tr>
</thead>
<tbody>
<tr>
<td>速度</td>
<td>1-2纳秒</td>
<td>约10纳秒</td>
<td>约30纳秒</td>
</tr>
<tr>
<td>容量</td>
<td>几十KB到几百KB</td>
<td>几百KB到几MB</td>
<td>几MB到几十MB</td>
</tr>
<tr>
<td>位置</td>
<td>集成在 CPU 核心内部</td>
<td>每个核心独立拥有(位于核心附近)</td>
<td>多核共享(位于 CPU 芯片内)</td>
</tr>
<tr>
<td>缓存一致性</td>
<td>每个核心独立,与L3主从同步</td>
<td>每个核心独立,与L1/L3主从同步</td>
<td>多核共享</td>
</tr>
<tr>
<td>场景</td>
<td>需要立即执行的指令与高频访问的数据</td>
<td>稍低频但重复访问的数据</td>
<td>跨核心共享数据、大吞吐量计算</td>
</tr>
</tbody>
</table>
<p><img src="https://img2024.cnblogs.com/blog/1084317/202506/1084317-20250607143416819-1968724507.png"></p>
<blockquote>
<p>what is CacheLine?<br>
CacheLine是CPU缓存中最小数据单元,当CPU从内存中读取数据时,会一次性加载64byte的数据,而不是只加载特定数据,即使只想读取1bit数据,也会加载64byte数据。这么做是因为,大多数情况下,数据都是顺序读取的,因此提前加载数据有利于减少延迟。</p>
</blockquote>
<h2 id="眼见为实">眼见为实</h2>
<p>使用Coreinfo 来观察CPU</p>
<p><img src="https://img2024.cnblogs.com/blog/1084317/202506/1084317-20250608090705516-1285492300.png"></p>
<h1 id="c代码如何影响cpu缓存速度">C#代码如何影响CPU缓存速度?</h1>
<pre><code>internal class Program
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Start();
Rows();
sw.Stop();
Console.WriteLine($"逐行赋值执行时间:{sw.ElapsedMilliseconds}");
sw.Restart();
Columns();
sw.Stop();
Console.WriteLine($"逐列赋值执行时间:{sw.ElapsedMilliseconds}");
}
static void Rows()
{
int[,] tab = new int;
for (int i = 0; i < 5000; i++)
{
for (int j = 0; j < 5000; j++)
{
tab = 1;//逐行赋值,能成功利用到Cacheline提前加载的数据
}
}
}
static void Columns()
{
{
int[,] tab = new int;
for (int i = 0; i < 5000; i++)
{
for (int j = 0; j < 5000; j++)
{
tab = 1;//逐列赋值,无法利用Cacheline提前加载的数据,只能丢弃重新读取。
}
}
}
}
}
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/1084317/202506/1084317-20250607145428534-459340711.png"></p>
<blockquote>
<p>可以看到,非线性的数据检索带来了严重的性能问题,应当尽量避免对内存的非顺序访问。</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/lmy5215006/p/18916344
頁:
[1]