深入理解计算机系统------优化程序性能(2)

1、理解现代处理器
在代码级上,看上去是一次执行一条指令,每条指令都包括从寄存器或内存取值,执行一个操作,并把结果存回到一个寄存器或内存位置。在实际的处理器中,是同时执行多条指令的,这个现象称为指令级并行。多条指令并行地执行,同时又呈现出一种简单的顺序执行的表象。

延迟界限:下一条指令执行之前,这条指令必须结束,当代码中的数据相关限制了处理器利用指令集并行的能力时,延迟界限能够限制程序的性能。
吞吐量界限:刻画了处理器原始单元的原始计算能力,这个界限是程序的终极限制。

2、可以通过以下的方法优化程序的性能,提高并行度
(1)循环展开
循环展开能够从两方面改变程序的性能:首先,它减少了不直接有助于程序结果的操作数量,例如循环索引计算和条件分支。第二,它提供了一些方法,可以进一步变化代码,减少整个计算关键路径上的操作数量。

例如:



void psum1(float p[], long n)
{
    long i;
    float acc = 1;
    for (i = 0; i < n; i++)
    {
        acc = acc * p[i] ;
    }
}


void psum2( float p[], long n)
{
    long i;
    float acc = 1;
    for (i = 0; i < n-1; i+=2)
    {
        acc = (acc * p[i]) * p[i + 1];
    }
    if (i < n)
    {
        acc = acc * p[i];
    }
}

函数psum2()采用“2x1”循环展开,一个循环每次处理数组的两个元素,也就是每次迭代,循环索引i加2,在一次迭代中,对数组元素i和i+1使用合并运算;循环结束条件改为n-1;最后判断是否加完(为奇数个时没有加完所有元素)。
以此类推,按照“kx1”循环展开时,上限设为n-k+1,每次 i += k。

(2)多个累计变量(提高并行性)
虽然循环展开减少了迭代次数,但是展开后的两个乘法仍然是限制因素,他们相互相关,不能并行,因而增加多个累计变量,提高程序的并行性。

void psum3(float p[], long n)
{
    long i;
    float acc0 = 1;
    float acc1 = 1;
    float acc = 1;
    for (i = 0; i < n - 1; i += 2)
    {
        acc0 = acc0 * p[i];
        acc1 = acc1 * p[i + 1];
    }
    if (i < n)
    {
        acc0 = acc0 * p[i];
    }
    acc = acc0 * acc1;
}

通过增加中间变量,使两个乘法运算能够同时运行(并行),从而提高了程序的性能

(3)重新结合变换

void psum2( float p[], long n)
{
    long i;
    float acc = 1;
    for (i = 0; i < n-1; i+=2)
    {
        acc = (acc * p[i]) * p[i + 1];
    }
    if (i < n)
    {
        acc = acc * p[i];
    }
}

void psum4( float p[], long n)
{
    long i;
    float acc = 1;
    for (i = 1; i < n-1; i+=2)
    {
        acc = acc * (p[i] * p[i + 1]);//不需要前一次迭代的累计值就可以执行
    }
    if (i < n)
    {
        acc = acc * p[i];
    }
}

函数psum4看上去跟psum2没有什么区别,但是性能却优化了接近一倍;因为,当将后面 p[i] * p[i + 1] 结合时,可以在上一轮计算acc时同时计算,而不用等待acc的计算结果,所以关键路径上由两个乘法减少为只有一个乘法。

(4)考虑局部性原理的优化
局部性原理
空间局部性:被引用过一次的内存位置很可能在不久的将来再被多次引用。
时间局部性:一个内存位置被引用了一次,在不久的将来很可能引用附近的位置。

有良好的局部性的程序比局部性差的程序运行的更快。

void clear1(point *p, int n)
{
    int i, j;
    for (j = 0; j < 3; j++)
    {
        for (i = 0; i < n; i++)
        {
            p[i].vel[j] = 0;
        }
        for (i = 0; i < n; i++)
        {
            p[i].acc[j] = 0;
        }
    }
}

void clear2(point *p, int n)
{
    int i, j;
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < 3; j++)
        {
            p[i].vel[j] = 0;
            p[i].acc[j] = 0;
        }
    }
}

void clear3(point *p, int n)
{
    int i, j;
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < 3; j++)
        {
            p[i].vel[j] = 0;
        }
        for (j = 0; j < 3; j++)
        {
            p[i].acc[j] = 0;
        }
    }
}

对于上述的三个函数,它们的空间局部性的顺序是:clear1() < clear()2 < clear3()
虽然三个函数实现的功能相同,但是程序的性能却不同,作为程序员,了解机器内部的运行以及一些原理,有利于更好地优化程序。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于LoRA对ChatGLM进行微调实验python源码+训练好的模型+项目说明.zip 使用LoRA对ChatGLM进行微调。整体的结构非常简单,构造好相应格式的数据后就可以开始训练。 训练好的实体识别LoRA权重已经位于checkpoint下。 # 依赖 linux操作系统为Ubantu,GPU为A40-48G显存。 ```python mpi4py transformers==4.28.1 peft==0.3.0 icetk deepspeed==0.9.2 accelerate cpm_kernels sentencepiece==0.1.99 peft=0.3.0 torch=2.0.0 ``` # 说明 ## 目录结构 ```python --checkpoint:保存模型 ----msra:数据集名称 --------train_deepspeed ------------adapter_model ----------------adapter_config.json ----------------adapter_model.bin ----------------train_args.json --------train_trainer ------------adapter_model ----------------adapter_config.json ----------------adapter_model.bin ----------------train_args.json --model_hub:预训练模型 ----chatglm-6b:预训练模型位置 --data:数据 ----msra:数据集名称 --------instruct_data:指令数据 ------------dev.txt ------------train.txt --------ori_data:原始数据 --chat_ner.py:闲聊 --train_deepspeed.py:使用原生deepspeed训练 --train_trainer.py: 使用transformers的Trainer进行训练 --test.py:测试训练好的模型 --predict.py:预测 --test_chatglm_6b.py:测试原始的chatglm-6b --process.py:处理数据为instruct_data --dataset.py:加载数据为相应的格式 --deepspeed.json:deepspeed配置文件,用于trasnformers的Trainer --config_utils.py:用于用字典定义配置,并接收命令行参数 ``` chatglm-6b下面数据是这样,除权重外已经放在model_hub/chatglm-6b下: 数据格式 这里我们以命名实体识别任务为例,数据在data/msra下,其中ori_data为原始数据,instruct_data为处理后的数据,数据格式为一条一个样本,具体是: ```python {"instruct": "你现在是一个实体识别模型,你需要提取文本里面的人名、地名、机构名,如果存在结果,返回'实体_实体类型',不同实体间用\n分隔。如果没有结果,回答'没有'。", "query": "文本:因有关日寇在京掠夺文物详情,藏界较为重视,也是我们收藏北京史料中的要件之一。", "answer": "日_地名\n京_地名\n北京_地名"} ``` 可以按照自己的任务自行构建。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值