魔法代码 - 缓存对齐 C#版

代码


我们先看下面一段代码,非常的简单。

using System.Diagnostics;

namespace CacheConsistency
{
    internal class Program
    {
        class Test
        {
            public long X { get; set; }
            //public long x1, x2, x3, x4, x5, x6, x7;
        }

        static void Main(string[] args)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();
            stopwatch.Start();

            long count = 100000000;            
            List<Test> list = new List<Test>();//业务数据项集合
            for (int i = 0; i < 4; i++)
            {
                list.Add(new Test());
            }

            Parallel.ForEach(list, test =>
            {
                for (int i = 0; i < count; i++)
                {
                    test.X = i;
                }
                Console.WriteLine(test.X);
            });

            stopwatch.Stop();
            Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds);
        }
    }
}

上面的代码中Main方法主要是定义一个Test实例集合,然后并行执行为集合的Test实例的属性X赋值这样一个过程。

运行这个控制台程序,多运行几次并记一下执行时间,我取我其中一次执行的结果如下图:

现在让我们施展“魔法”:取消public long x1, x2, x3, x4, x5, x6, x7;这行代码的注释。是的,我们仅仅是定义这些变量,没有修改其它任何地方的代码,现在我们执行看看会发生什么,我取其中一次的执行结果如下图:

神奇的事情发生了——执行时间缩短了5倍,为什么?怎么做到的?

揭秘


  • 缓存对齐

上面代码实现的效果就是因为缓存对齐,定义7个long类型的变量使得有被使用的变量X一起被放到同一缓存行,这样就避免变量X被缓存到不同的CPU内核。

缓存行

在解释上面的原理之前,我们需要先了解一些概念:

  • 内存的空间局部性

    在一个具有良好空间局部性的程序中,如果一个内存位置被引用了一次,那么程序很可能在不远的未来引用附近的一个内存位置。

  • 缓存行

    计算机将数据从主存读入CPU高速缓存时,如果一个变量一个变量的同步效率比较低,而且根据内存的空间局部性原理:这个变量相邻的变量将来很可能也要被使用,所以CPU从主存读取数据时是读取一个数据块,这个数据块大小一般是64个字节。

  • CPU的缓存一致性同步

    CPU的L1、L2缓存是每个CPU内核独享的,当从主存读取缓存行到不同的CPU内核时,如果读取的是相同的缓存行,当其中一个CPU的内核修改了缓存行的数据,就必须把修改的数据同步到另一个内核的缓存,从而保证数据的一致。

OK,以上的理论知识比较枯燥,我们结合代码来画一些图:

前后代码的缓存行对比

  • 取消注释代码前

  • 取消注释代码后

    实际的应用场景


...

posted @ 2022-10-21 16:29  boby,peng  阅读(116)  评论(0)    收藏  举报