内存访问模式的影响:行主序与列主序的性能分析

内存访问模式的影响:行主序与列主序的性能分析

两种方式执行的对比

我们平时默认就是使用的第一种方式遍历数组。可能很少思考为什么使用第一种不用第二种,今天来试着比较一下两种方式的差距。

// 在第一种方式中,内层循环首先访问nums[i][0],然后nums[i][1],依此类推。这是一种连续的内存访问模式,称为行主序(row-major  order)或按行存储(row-wise storage)。    
public static void ergodicOne(int[][] nums){
        for (int i = 0;i<nums.length;i++){
            for (int j = 0; j<nums[0].length;j++){
                int a = nums[i][j];
            }
        }
    }
// 在第二种方式中,内层循环首先访问nums[0][i],然后nums[1][i],依此类推。这是一种间断的内存访问模式,称为列主序column-major order)或按列存储(column-wise storage)
    public static void ergodicTwo(int[][] nums){
        for (int i = 0;i<nums.length;i++){
            for (int j = 0; j<nums[0].length;j++){
                int a = nums[j][i];
            }
        }
    }

传入大小 100x100 的数组 遍历10万次

    public static long count1 (int[][] nums){
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
            ergodicOne(nums);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    public static long count2 (int[][] nums){
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
            ergodicTwo(nums);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    public static int[][] create (){
        int[][] ints = new int[100][100];
        for (int i = 0;i<ints.length;i++){
            for (int j = 0; j<ints[0].length;j++){
                ints[i][j] = i+j;
            }
        }
        return ints;
    }
    public static void main(String[] args) throws  Exception{
        int[][] nums = create();
        long t1 = count1(nums);
        long t2 = count2(nums);
        System.out.println("第一种方式耗时:"+ t1); // 456
        System.out.println("第二种方式耗时:"+ t2); // 668
    }

在这里插入图片描述

可以看出第一种方式和第二种方式遍历数组有很大的差距,这是为什么呢?

分析

cpu内存层级:

在这里插入图片描述

  • L1 Cache 通常会分为「数据缓存」和「指令缓存」,这意味着数据和指令在 L1 Cache 这一层是分开缓存的
  • 程序执行时,会先将内存中的数据加载到共享的 L3 Cache 中,再加载到每个核心独有的 L2 Cache,最后进入到最快的 L1 Cache,之后才会被 CPU 读取。

越靠近 CPU 核心的缓存其访问速度越快,CPU 访问 L1 Cache 只需要 2~4 个时钟周期,访问 L2 Cache 大约 10~20 个时钟周期,访问 L3 Cache 大约 20~60 个时钟周期,而访问内存速度大概在 200~300 个 时钟周期之间。如下表格:

在这里插入图片描述

在这两种方式中,第一种方式(ergodicOne)执行更快的原因是与内存访问模式和CPU缓存有关。让我们更详细地解释一下:

CPU缓存

  • 现代计算机系统通常具有多级缓存(L1、L2、L3缓存等)。这些缓存是用来存储从主内存中读取的数据,以加速CPU对数据的访问。
  • CPU缓存以缓存行(cache line)为单位进行数据加载。每个缓存行通常包含多个相邻的字节或整数。当CPU访问一个特定的内存地址时,它通常会将整个缓存行加载到缓存中。
  • 在行主序访问中,连续的数组元素通常位于相邻的内存地址中,因此它们更有可能在同一个缓存行中。这意味着在行主序访问中,CPU更容易从缓存中命中数据,而不必频繁地从主内存中加载新数据。
  • 在列主序访问中,由于内存访问模式不连续,CPU可能需要更频繁地从内存中加载新的缓存行,导致更多的缓存未命中(cache misses)。

因此,第一种方式(ergodicOne)的行主序访问模式更有可能充分利用CPU缓存,减少了缓存未命中的情况,从而使代码执行更快。总之,在编写需要频繁访问多维数组的代码时,可以通过优化内存访问模式来提高性能。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值