ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【计算机基础】程序的局部性简介

2021-04-25 16:01:13  阅读:215  来源: 互联网

标签:计算机 int 简介 sum Linux 数组 time 局部性


  • 什么是局部性?

  • 局部性分类

  • 局部性有什么作用?

  • 局部性举例

    • 数据引用的局部性

    • 取指令的局部性

  • 结论

  • 完整代码

什么是局部性?

  程序倾向于使用它们最近使用的地址接近或相等的数据和指令。

局部性分类

  局部性主要分为时间局部性和空间局部性。

  时间局部性:最近使用过的数据和指令在不久的将来可能再次被使用。具体如下图所示。

图片时间局部性

  空间局部性:某个地址或者某个地址附近的数据和指令可能在不久的将来再次被引用。具体如下图所示。

图片空间局部性

局部性有什么作用?

  在现代计算机的软硬件中,处处体现着局部性原理。在硬件上,计算机通过引入称为高速缓存来保存最近被使用的指令和数据。在软件上,操作系统用主存来缓存磁盘文件系统中最近被使用的磁盘块。在应用程序的设计中,Web浏览器将最近被引用的文档放在本地磁盘上,利用的就是时间局部性。作为程序员应该理解局部性原理,一般来说,有良好局部性的程序比局部性差的程序运行得更快。

局部性举例

数据引用的局部性

  看下下面两个函数。都是计算数组a的和。唯一的区别在于行列的访问先后顺序不同。那么这两个程序运行起来会有什么差别呢?我们测试下。

/**
 * @Description: 行优先方式求二维数组a的和
 * @Param:       整型数组a
 * @Return:      数组a的和sum
 * @Author:      嵌入式与Linux那些事
 */
int SumArrRow(int aarr[ROW][COL])
{
    //1393.166487us
    int i,j,sum = 0;
    for(i = 0;i < ROW;i++)
        for(j = 0;j < COL;j++)
        sum +=a[i][j];
    printf("%d\r\n",sum);
    return sum;
}
/**
 * @Description: 列优先方式求二维数组a的和
 * @Param:       整型数组a
 * @Return:      数组a的和sum
 * @Author:      嵌入式与Linux那些事
 */
int SumArrCol(int a[ROW][COL])
{
    //2083.776579us
    int i,j,sum = 0;
    for(j = 0;j < COL;j++)
        for(i = 0;i < ROW;i++)
        sum +=a[i][j];
    printf("%d\r\n",sum);
    return sum;
}

  运算的数组为2 * 3大小。测试结果如下。

15
SumArrRow run_time:1039.612803us
15
SumArrCol run_time:2011.529889us

  SumArrCol函数的运行时间是SumArrRow运行时间的将近2倍。原因是什么呢?

  首先我们要知道数组在内存中是以行优先的方式存储的。SumArrRow函数在for循环中访问a的顺序如下。

地址       0     4      8      12    16    20
内容       a00   a01    a02    a10   a11   a12
访问顺序    1     2      3     4      5      6

  而SumArrRow函数中,双重嵌套循环按照行优先顺序读数组的元素。也就是,内层循环读第一行的元素,然后读第二行,依此类推。元素被访问的步长为1。和数组在内存中的存储方式是一样的,因此具有很好的空间局部性。

  SumArrCol函数和SumArrRow函数,唯一的区别是我们交换了i和j的循环。这样交换循环对它的局部性有何影响?因为它按照列顺序来扫描数组,而不是按照行顺序。因为C数组在内存中是按照行顺序来存放的,元素被访问的步长为COL。所以其空间局部性较差。

  SumArrCol函数在内存中的存放方式如下所示。

地址       0     4      8      12    16    20
内容       a00   a01    a02    a10   a11   a12
访问顺序    1     3      5      2     4      6

  下面再看一个时间局部性的例子。

/**
 * @Description: 求一维数组a的和
 * @Param:       整型数组a
 * @Return:      数组a的和sum
 * @Author:      嵌入式与Linux那些事
 */
int SumArr(int a[MAX])
{
    int i,sum = 0;
    for(i = 0;i < MAX;i++)
        sum +=a[MAX];
    printf("%d\r\n",sum);
    return sum;
}

  SumArr单函数,它对一个向量的元素求和。这个程序有良好的局部性吗?要回答这个问题,我们来看看每个变量的引用模式。

地址       0     4      8      12    16    20
内容       a0    a1     a2     a3    a4    a5
访问顺序    1     2      3      4     5      6

  在这个例子中,变量sum在每次循环迭代中被引用一次,因此,对于sum来说,有好的时间局部性。另一方面,因为sum是标量,对于sum来说,没有空间局部性。

  数组a的元素是被顺序读取的,一个接一个,按照它们存储在内存中的顺序(为了方便,我们假设数组是从地址0开始的)。因此,对于数组a,函数有很好的空间局部性,但是时间局部性很差,因为每个数组元素只被访问一次。对于循坏体中的每个变量,这个函数要么有好的空间局部性,要么有好的时间局部性,所以我们可以断定 SumArr函数有良好的局部性。

取指令的局部性

  因为程序的指令是放在内存中的,程序运行时,CPU必须取出这些指令。SumArr中for循环体中的指令是按照连续的内存顺序执行的。因此具有很好的空间局部性。而且,循环体又被执行很多次,所以也有很好的时间局部性。

取指令的局部性和数据引用的局部性的区别在于,在程序运行时,指令是不可修改的。程序只能对指令读。

结论

上面我们介绍了局部性的概念,并给出了程序示例。现将以上内容总结如下。

  • 重复引用相同变量的程序有良好的时间局部性。
  • 对于具有步长为k的引用模式的程序,步长越小,空间局部性越好。在内存中以大步长跳来跳去的程序空间局部性会很差。
  • 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好

完整代码

/*
 * @Description: 【计算机基础】程序的局部性简介
 * @Version: V1.0
 * @Autor: 嵌入式与Linux那些事
 * @Date: 2020-12-21 21:40:12
 * @LastEditors: 嵌入式与Linux那些事
 * @LastEditTime: 2020-12-23 23:11:58
 */
#include <stdio.h>
#include <time.h>
#include <Windows.h>

#define ROW  2
#define COL  3
/**
 * @Description: 行优先方式求数组a的和
 * @Param:       整型数组a
 * @Return:      数组a的和sum
 * @Author:      嵌入式与Linux那些事
 */
int SumArrRow(int aarr[ROW][COL])
{
    //1393.166487us
    int i,j,sum = 0;
    for(i = 0;i < ROW;i++)
        for(j = 0;j < COL;j++)
        sum +=a[i][j];
    printf("%d\r\n",sum);
    return sum;
}
/**
 * @Description: 列优先方式求数组a的和
 * @Param:       整型数组a
 * @Return:      数组a的和sum
 * @Author:      嵌入式与Linux那些事
 */
int SumArrCol(int a[ROW][COL])
{
    //2083.776579us
    int i,j,sum = 0;
    for(j = 0;j < COL;j++)
        for(i = 0;i < ROW;i++)
        sum +=a[i][j];
    printf("%d\r\n",sum);
    return sum;
}
/**
 * @Description: 求一维数组a的和
 * @Param:       整型数组a
 * @Return:      数组a的和sum
 * @Author:      嵌入式与Linux那些事
 */
int SumArr(int arr[MAX])
{
    int i,sum = 0;
    for(i = 0;i < MAX;i++)
        sum +=a[MAX];
    printf("%d\r\n",sum);
    return sum;
}
int main()
{
    int a[ROW][COL] = {0,1,2,3,4,5};
    double run_time;
    LARGE_INTEGER time_start; //开始时间
    LARGE_INTEGER time_over; //结束时间
    double dqFreq;  //计时器频率
    LARGE_INTEGER f; //计时器频率
    QueryPerformanceFrequency(&f);
    dqFreq=(double)f.QuadPart;

    QueryPerformanceCounter(&time_start); //计时开始
    SumArrRow(a);
    QueryPerformanceCounter(&time_over); //计时结束
    
    run_time=1000000*(time_over.QuadPart-time_start.QuadPart)/dqFreq;//乘以1000000把单位由秒化为微秒,精度为1000 000/(cpu主频)微秒
    printf("\nSumArrRow run_time:%fus\n",run_time);
    return 0;
}


标签:计算机,int,简介,sum,Linux,数组,time,局部性
来源: https://blog.51cto.com/u_11934066/2731807

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有