【genius_platform软件平台开发】第八十一讲:ARM Neon指令集一(ARM NEON Intrinsics, SIMD运算, 优化心得)

news2024/11/19 3:40:24

1. ARM Neon Intrinsics 编程

1.入门:基本能上手写Intrinsics
1.1 Neon介绍、简明案例与编程惯例
1.2 如何检索Intrinsics
1.3 优化效果案例
1.4 如何在Android应用Neon
2. 进阶:注意细节处理,学习常用算子的实现
2.1 与Neon相关的ARM体系结构
2.2 对非整数倍元素个数(leftovers)的处理技巧
2.3 算子源码学习(ncnn库,AI方向)
2.4 算子源码学习(Nvidia carotene库,图像处理方向 )
3. 学个通透:了解原理
3.1 SIMD加速原理
3.2 了解硬件决定的速度极限:Software Optimization Guide
3.3 反汇编分析生成代码质量
4. 其他:相关的研讨会视频、库、文档等
ncnn是腾讯开源,nihui维护的AI推理引擎。由于Neon实现往往跟循环展开等技巧一起使用,代码往往比较长。可以先阅读普通实现的代码实现了解顶层逻辑,再阅读Neon实现的代码。例如,我们希望学习全连接层(innerproduct)的Neon实现,其普通实现的位置在ncnn/src/layer/innerproduct.cpp,对应的Neon加速实现的位置在ncnn/src/layer/arm/innerproduct_arm.cpp。

在这里插入图片描述

2. ARMv8 中的 SIMD 运算

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SIMD

什么是SIMD呢?就是一条指令处理多个数据,可以算作是一种并行计算。比如我们要做一个4维向量的加法,用一般的指令完成必须使用4次加法指令才行,而用SIMD指令可能只需要一次加法,而且花费的时间和一般指令做一次加法的时间相同。很显然,SIMD可以大大提高一些计算密集型任务的执行效率。这种SIMD指令功能,主流的体系结构一般都用一组特殊的指令子集给予支持,比如x86的SSE,还比如本文讲的ARM的NEON。

NEON

NEON是ARM下的一个SIMD指令集合。可实现64位/128位的并行计算。64位/128位并行怎么理解呢?举例说,在128位并行的情况下,如果是8位整数,可以并行进行16对整数的加法;如果是16位整数,就可以并行进行8对整数的加法;以此类推。

指令集合自然也离不开寄存器。NEON寄存器分两种。一种寄存器以D开头,共32个,每个64位;另一种寄存器以Q开头,共16个,每个128位。Q0与D0,D1重合(共用128比特),Q1与D2,D3重合,以此类推。因此用D寄存器可并行8个8位整数加法,而用Q寄存器可并行16个8位整数加法。

NEON intrinsics

如果直接用汇编写NEON固然可以,但是coding的效率不会很高。C编译器支持将NEON指令封装成内置函数供程序员直接使用,这样一来无疑会大大提高开发效率和代码可维护性。

同时,执行效率也并不会降低很多,因为使用NEON intrinsics时,虽然像是在调用各种结构体和函数,但将生成的代码反汇编后可以发现,其实没有调用函数,只是在使用NEON寄存器和指令罢了。

即便目的是写汇编代码,使用intrinsics也有好处。比如先用intrinsics写好代码编译后在反汇编,在此基础上进行优化,可能比较省力。

数据类型

<基本类型>x<lane个数>x<向量个数>_t,向量个数如果省略表示只有一个。如int8x8_t,uint8x8x3_t。

基本类型int8,int16,int32,int64,uint8,uint16,uint32,uint64,float16,float32

lane个数表示并行处理的基本类型数据的个数。

对于多个向量的类型实际上是结构体

typedef struct {
uint8x8_t val[3];
} uint8x8x3_t;

指令命名

<指令名>[后缀]_<数据基本类型简写>

其中后缀如果没有,表示64位并行;如果后缀是q,表示128位并行。

如果后缀是l,表示长指令,输出数据的基本类型位数是输入的2倍;如果后缀是n,表示窄指令,输出数据的基本类型位数是输入的一半。

数据基本类型简写:s8,s16,s32,s64,u8,u16,u32,u64,f16,f32

例如:

vadd_u16:两个uint16x4相加为一个uint16x4

vaddq_u16:两个uint16x8相加为一个uint16x8

vaddl_u16:两个uint8x8相加为一个uint16x8

指令分类说明

算术和位运算指令
vadd,vsub,vmul,vand,vorr,vshl,vshr等。

但是NEON不直接提供除法和开平方指令,而是提供了对于倒数1/x和开方的倒数1/x0.5的近似指令。这样一来除法a/b可以表示为a*(1/b),开方a0.5可以表示为a*(1/a^0.5)。

示例:

//近似求倒数
inline static float32x4_t vrecp(float32x4_t v) {
    float32x4_t r = vrecpeq_f32(v);        //求得初始估计值
    r = vmulq_f32(vrecpsq_f32(v, r), r);    //逼近
    r = vmulq_f32(vrecpsq_f32(v, r), r);    //再次逼近
    return r;
}
//近似求开方
inline float32x4_t vsqrt(float32x4_t v) {
    float32x4_t r = vrsqrteq_f32(v);        //求得开方倒数的初始估计值
    r = vmulq_f32(vrsqrtsq_f32(v, r), r);    //逼近
    return vmulq_f32(v, r);                //通过乘法转为开方
}

数据移动指令

实际编程中经常要在不同NEON数据类型间转移数据,有时还要按lane来get/set向量值,NEON intrinsics也提供了这类操作。

vdup[后缀]n<数据基本类型简写>:用同一个标量值初始化一个向量全部的lane;

vset[后缀]lane<数据基本类型简写>:对指定的一个lane进行设置

vget[后缀]lane<数据基本类型简写>:获取指定的一个lane的值

vmov[后缀]_<数据基本类型简写>:数据间移动

访存指令

NEON访存指令可以将内存读到NEON数据类型中去,或者将NEON数据类型写进内存。可以支持一次读写多向量数据类型。

vld<向量数>[后缀]_<数据基本类型简写>:读内存

vst<向量数>[后缀]_<数据基本类型简写>:写内存

例如,vld1_u8从内存读取一个uint8x8_t数据,vst3q_u8写入一个u8x16x3_t数据。

需要注意的是,默认情况下对多个向量数据的读写使用了interleave模式,可以理解为向多向量数据读入或从其写出时外层按照lane循环,内层再按照向量循环。

例如将一个16像素的RGB图片解析成R,G,B三个plane的时候,可以写如下代码:

void split(uint8_t *rgb, uint8_t *r, uint8_t *g, uint8_t *b) {
    uint8x16x3_t v = vld3q_u8(rgb);
    vst1q_u8(r, v.val[0]);
    vst1q_u8(g, v.val[1]);
    vst1q_u8(b, v.val[2]);
}

条件指令

如同非SIMD程序需要分支语句一样,NEON程序有时候需要对一个向量的各个lane的值的情况来判断另一个向量对应的lane如何进行处理。

vce[后缀]_<数据基本类型简写>:v[n] = v1[n] == v2[n] ? 全0 : 全1

vcle[后缀]_<数据基本类型简写>:v[n] = v1[n] <= v2[n] ? 全0 : 全1

vclt[后缀]_<数据基本类型简写>:v[n] = v1[n] < v2[n] ? 全0 : 全1

vcge[后缀]_<数据基本类型简写>:v[n] = v1[n] >= v2[n] ? 全0 : 全1

vcgt[后缀]_<数据基本类型简写>:v[n] = v1[n] > v2[n] ? 全0 : 全1

得出的结果结合位运算即可实现条件判断。

注意事项

NEON intrinsics的注意事项同时也是NEON汇编的注意事项。

处理数组时要注意数组元素个数不能被NEON向量lane个数整除的情况,多出的元素应补齐或者通过非SIMD方式处理。

NEON不是万能的,比如把地址放在向量里让内存同时读写就办不到。设计算法时应尽量避免这种情况。

对cache友好仍然是最重要的。有时一个算法看上去似乎访存次数和计算次数都比另一个算法少,但是由于其访存方式对cache不友好,导致其运行效率不如后者。

示例

  • 4x4 矩阵乘法
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>

#if __aarch64__
#include <arm_neon.h>
#endif

static void dump(uint16_t **x)
{
    int i, j;
    uint16_t *xx = (uint16_t *)x;

    printf("%s:\n", __func__);

    for(i = 0; i < 4; i++) {
        for(j = 0; j < 4; j++) {
            printf("%3d ", *(xx + (i << 2) + j));
        }

        printf("\n");
    }
}

static void matrix_mul_c(uint16_t aa[][4], uint16_t bb[][4], uint16_t cc[][4])
{
    int i = 0, j = 0;

    printf("===> func: %s, line: %d\n", __func__, __LINE__);

    for(i = 0; i < 4; i++) {
        for(j = 0; j < 4; j++) {
            cc[i][j] = aa[i][j] * bb[i][j];
        }
    }

}

#if __aarch64__
static void matrix_mul_neon(uint16_t **aa, uint16_t **bb, uint16_t **cc)
{
    printf("===> func: %s, line: %d\n", __func__, __LINE__);
#if 1
    uint16_t (*a)[4] = (uint16_t (*)[4])aa;
    uint16_t (*b)[4] = (uint16_t (*)[4])bb;
    uint16_t (*c)[4] = (uint16_t (*)[4])cc;

    printf("aaaaaaaa\n");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    uint16x4_t _cc0;
    uint16x4_t _cc1;
    uint16x4_t _cc2;
    uint16x4_t _cc3;

    uint16x4_t _aa0 = vld1_u16((uint16_t*)a[0]);
    uint16x4_t _aa1 = vld1_u16((uint16_t*)a[1]);
    uint16x4_t _aa2 = vld1_u16((uint16_t*)a[2]);
    uint16x4_t _aa3 = vld1_u16((uint16_t*)a[3]);

    uint16x4_t _bb0 = vld1_u16((uint16_t*)b[0]);
    uint16x4_t _bb1 = vld1_u16((uint16_t*)b[1]);
    uint16x4_t _bb2 = vld1_u16((uint16_t*)b[2]);
    uint16x4_t _bb3 = vld1_u16((uint16_t*)b[3]);

    _cc0 = vmul_u16(_aa0, _bb0);
    _cc1 = vmul_u16(_aa1, _bb1);
    _cc2 = vmul_u16(_aa2, _bb2);
    _cc3 = vmul_u16(_aa3, _bb3);

    vst1_u16((uint16_t*)c[0], _cc0);
    vst1_u16((uint16_t*)c[1], _cc1);
    vst1_u16((uint16_t*)c[2], _cc2);
    vst1_u16((uint16_t*)c[3], _cc3);
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
#else
    printf("bbbbbbbb\n");
    int i = 0;
    uint16x4_t _aa[4], _bb[4], _cc[4];
    uint16_t *a = (uint16_t*)aa;
    uint16_t *b = (uint16_t*)bb;
    uint16_t *c = (uint16_t*)cc;

    for(i = 0; i < 4; i++) {
        _aa[i] = vld1_u16(a + (i << 2));
        _bb[i] = vld1_u16(b + (i << 2));
        _cc[i] = vmul_u16(_aa[i], _bb[i]);
        vst1_u16(c + (i << 2), _cc[i]);
    }

#endif
}

static void matrix_mul_asm(uint16_t **aa, uint16_t **bb, uint16_t **cc)
{
    printf("===> func: %s, line: %d\n", __func__, __LINE__);

    uint16_t *a = (uint16_t*)aa;
    uint16_t *b = (uint16_t*)bb;
    uint16_t *c = (uint16_t*)cc;

#if 0
    asm volatile(
        "ldr d3, [%0, #0]           \n\t"
        "ldr d2, [%0, #8]           \n\t"
        "ldr d1, [%0, #16]          \n\t"
        "ldr d0, [%0, #24]          \n\t"

        "ldr d7, [%1, #0]           \n\t"
        "ldr d6, [%1, #8]           \n\t"
        "ldr d5, [%1, #16]          \n\t"
        "ldr d4, [%1, #24]          \n\t"

        "mul v3.4h, v3.4h, v7.4h    \n\t"
        "mul v2.4h, v2.4h, v6.4h    \n\t"
        "mul v1.4h, v1.4h, v5.4h    \n\t"
        "mul v0.4h, v0.4h, v4.4h    \n\t"

        //"add v3.4h, v3.4h, v7.4h    \n\t"
        //"add v2.4h, v2.4h, v6.4h    \n\t"
        //"add v1.4h, v1.4h, v5.4h    \n\t"
        //"add v0.4h, v0.4h, v4.4h    \n\t"

        "str d3, [%2,#0]            \n\t"
        "str d2, [%2,#8]            \n\t"
        "str d1, [%2,#16]           \n\t"
        "str d0, [%2,#24]           \n\t"

        : "+r"(a),   //%0
          "+r"(b),   //%1
          "+r"(c)    //%2
        :
        : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"
    );
#else
    // test, OK
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm volatile(
        //"ld4 {v0.4h, v1.4h, v2.4h, v3.4h}, [%0] \n\t"
        "ld4 {v0.4h-v3.4h}, [%0]                \n\t"
        "ld4 {v4.4h, v5.4h, v6.4h, v7.4h}, [%1] \n\t"

        "mul v3.4h, v3.4h, v7.4h                \n\t"
        "mul v2.4h, v2.4h, v6.4h                \n\t"
        "mul v1.4h, v1.4h, v5.4h                \n\t"
        "mul v0.4h, v0.4h, v4.4h                \n\t"

        "st4 {v0.4h, v1.4h, v2.4h, v3.4h}, [%2] \n\t"

        : "+r"(a),   //%0
          "+r"(b),   //%1
          "+r"(c)    //%2
        :
        : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
    );
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
#endif
}
#endif

int main(int argc, const char *argv[])
{
    uint16_t aa[4][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {3, 6, 8, 1},
        {2, 6, 7, 1}
    };

    uint16_t bb[4][4] = {
        {1, 3, 5, 7},
        {2, 4, 6, 8},
        {2, 5, 7, 9},
        {5, 2, 7, 1}
    };

    uint16_t cc[4][4] = {0};
    int i, j;
    struct timeval tv;
    long long start_us = 0, end_us = 0;

    dump((uint16_t **)aa);
    dump((uint16_t **)bb);
    dump((uint16_t **)cc);

    /* ******** C **********/
    gettimeofday(&tv, NULL);
    start_us = tv.tv_sec + tv.tv_usec;

    matrix_mul_c(aa, bb, cc);

    gettimeofday(&tv, NULL);
    end_us = tv.tv_sec + tv.tv_usec;
    printf("aa[][]*bb[][] C time %lld us\n", end_us - start_us);
    dump((uint16_t **)cc);

#if __aarch64__
    /* ******** NEON **********/
    memset(cc, 0, sizeof(uint16_t) * 4 * 4);
    gettimeofday(&tv, NULL);
    start_us = tv.tv_sec + tv.tv_usec;

    matrix_mul_neon((uint16_t **)aa, (uint16_t **)bb, (uint16_t **)cc);

    gettimeofday(&tv, NULL);
    end_us = tv.tv_sec + tv.tv_usec;
    printf("aa[][]*bb[][] neon time %lld us\n", end_us - start_us);
    dump((uint16_t **)cc);

    /* ******** asm **********/
    memset(cc, 0, sizeof(uint16_t) * 4 * 4);
    gettimeofday(&tv, NULL);
    start_us = tv.tv_sec + tv.tv_usec;

    matrix_mul_asm((uint16_t **)aa, (uint16_t **)bb, (uint16_t **)cc);

    gettimeofday(&tv, NULL);
    end_us = tv.tv_sec + tv.tv_usec;
    printf("aa[][]*bb[][] asm time %lld us\n", end_us - start_us);
    dump((uint16_t **)cc);
#endif

    return 0;
}

aarch64-linux-gcc -O3  matrix_4x4_mul.c
gcc –march=armv8-a [input file] -o [output file]

8x8 矩阵乘法

static void matrix_mul_asm(uint16_t **aa, uint16_t **bb, uint16_t **cc)
{
    printf("===> func: %s, line: %d\n", __func__, __LINE__);

    uint16_t *a = (uint16_t*)aa;
    uint16_t *b = (uint16_t*)bb;
    uint16_t *c = (uint16_t*)cc;

    asm volatile(
        "ld4 {v0.8h, v1.8h, v2.8h, v3.8h}, [%0]     \n\t"
        "ld4 {v8.8h, v9.8h, v10.8h, v11.8h}, [%1]   \n\t"

        "mul v0.8h, v0.8h, v8.8h                    \n\t"
        "mul v1.8h, v1.8h, v9.8h                    \n\t"
        "mul v2.8h, v2.8h, v10.8h                   \n\t"
        "mul v3.8h, v3.8h, v11.8h                   \n\t"

        "st4 {v0.8h, v1.8h, v2.8h, v3.8h}, [%2]     \n\t"


        "add x1, %0, #64                            \n\t"
        "add x2, %1, #64                            \n\t"
        "add x3, %2, #64                            \n\t"

        //"ld4 {v4.8h-v7.8h}, [x1]                    \n\t"
        "ld4 {v4.8h, v5.8h, v6.8h, v7.8h}, [x1]     \n\t"
        "ld4 {v12.8h, v13.8h, v14.8h, v15.8h}, [x2] \n\t"

        "mul v4.8h, v4.8h, v12.8h                   \n\t"
        "mul v5.8h, v5.8h, v13.8h                   \n\t"
        "mul v6.8h, v6.8h, v14.8h                   \n\t"
        "mul v7.8h, v7.8h, v15.8h                   \n\t"

        "st4 {v4.8h, v5.8h, v6.8h, v7.8h}, [x3]     \n\t"

        : "+r"(a),   //%0
          "+r"(b),   //%1
          "+r"(c)    //%2
        :
        : "cc", "memory", "x1", "x2", "x3", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
            "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15"
      );
}

3. NEON编程, 优化心得及内联汇编使用心得

Very thanks to Orchid (Orchid Blog).

NEON intrinsics

提供了一个连接NEON操作的C函数接口,编译器会自动生成相关的NEON指令,支持ARMv7-A或ARMv8-A平台。

所有的intrinsics函数都在GNU官方说明文档。

一个简单的例子:

//add for int array. assumed that count is multiple of 4  
#include<arm_neon.h>  
// C version void add_int_c(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i++)
    dst[i] = src1[i] + src2[i];
  }
}
 
// NEON version void add_float_neon1(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i += 4)
  {
    int32x4_t in1, in2, out;
    in1 = vld1q_s32(src1);
    src1 += 4;
    in2 = vld1q_s32(src2);
    src2 += 4;
    out = vaddq_s32(in1, in2);
    vst1q_s32(dst, out);
    dst += 4;
  }
}

代码中的vld1q_s32会被编译器转换成vld1.32 {d0, d1}, [r0]指令,同理vaddq_s32和vst1q_s32被转换成vadd.i32 q0, q0, q0,vst1.32 {d0, d1}, [r0]。若不清楚指令意义,请参见ARM® Compiler armasm User Guide - Chapter 12 NEON and VFP Instructions。

在这里插入图片描述
在这里插入图片描述

参考
ARMv8 Neon Programming
Introducing NEON
Coding for NEON - Part 1: Load and Stores
ARM® Cortex®-A72 MPCore Processor Technical Reference Manual

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/11255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

寻 友 软 件

寻友软件项目技术技术功能部署Redis部署RocketMQJWT&#xff08;Json Web Token&#xff09;虹软人脸识别部署MongoDB&#xff08;尽量不用docker部署mongo&#xff09;部署Nginx过滤器及拦截器加缓存编码流程DOC接口文档bug技术 技术 前端&#xff1a; flutterandroid环信S…

分销微信小程序介绍_分销小程序有什么作用呢

不同的微商城系统对于分销功能的支持会有不要的叫法&#xff0c;一般来说主要有两种&#xff0c;一种是基于商品分享的分销方式&#xff0c;通过分享链接识别客户从属关系&#xff0c;订单完成&#xff0c;结算佣金&#xff1b;另一种分销商可以建立并独立运营一个分销店铺&…

【JavaSE】关于多态那些事儿

目录 1. 多态 1.1 多态的概念 1.2 多态实现条件 1.3 向上转型 1.3.1 直接赋值 1.3.2 方法传参 1.3.3 方法返回 1.3.4 向上转型的优缺点 1.4 重写 1.4.1 重写的条件 1.4.2 重写注意事项 1.4.3 重载与重写的区别 1.5 通过父类的引用&#xff0c;调用这个父类和子类重…

CSS篇十六——盒子模型之边框

目录一、CSS盒子模型1.1 盒子模型组成1.2 边框&#xff08;border&#xff09;1.2.1 语法格式1.2.2 边框样式 border-style1.2.3 代码示例1.3 表格的细线边框1.3.1 语法格式、代码示例及结果一、CSS盒子模型 网页布局过程&#xff1a; 1.先准备好相关的网页元素&#xff0c;网…

My sql的深度剖析

一.数据库的创建、删除、使用 数据库的创建&#xff1a;create database 数据库名 数据库的删除&#xff1a;drop database 数据库名&#xff1b; 数据库的使用&#xff1a;use数据名&#xff1b; 所有数据库的查看&#xff1a;show databases; 建立数据时如何指定字符集…

在Java中计算Levenshtein莱文斯坦(相似度)编辑距离

在本教程中&#xff0c;我们将研究 Levenshtein 距离算法&#xff0c;该算法也称为编辑距离算法&#xff0c;用于比较单词的相似性。 什么是列文施泰因距离 Levenshtein距离算法由俄罗斯科学家Vladimir Levenshtein创建。 Levenshtein 距离算法通过计算将一个字符串转换为另…

基于单片机的贪吃蛇设计

1 绪论 1.1 设计目的 在21世纪的今天&#xff0c;人们的生活开始变得更加丰富多彩。在繁忙的工作之余&#xff0c;娱乐成为人们生活不可或缺的一份子&#xff0c;而游戏作为近年来逐渐兴起的一种娱乐方式&#xff0c;已经越来越受到人们的青睐。在工作学习之余&#…

dreamweaver网页设计作业制作 学生NBA篮球网页 WEB静态网页作业模板 大学生校园篮球网页代码 dw个人网页作业成品

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

第五章:双指针与离散化的映射

第五章&#xff1a;双指针、离散化、二进制运算与区间合并一、双指针1、什么是双指针&#xff1f;2、双指针的模板3、双指针例题&#xff08;1&#xff09;思路&#xff1a;&#xff08;2&#xff09;解答&#xff1a;C版&#xff1a;C版&#xff1a;二、离散化1、什么是离散化…

java面试强基(3)

重载和重写的区别? 重载 发生在同一个类中&#xff0c;方法名必须相同&#xff0c;参数类型不同、个数不同、顺序不同&#xff0c;方法返回值和访问修饰符可以不同。 重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。 重写 重写发生在运行期&#xff0c;…

go语言基本环境搭建

下载地址 Go官网下载地址&#xff1a;https://studygolang.com/dl 一、下载对应电脑得安装包 二、下载完成点击安装下一步&#xff08;选择目录尽量简单&#xff09; 三、是否安装成功 四、环境变量 GOROOT和GOPATH都是环境变量&#xff0c;其中GOROOT是我们安装go开发包的路…

【计算机毕业设计】Springboot医疗管理系统源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 医疗服务系统&#xff0c;主要的模块包括查看管理员&#xff1b;首页、个人中心…

tomcat官网下载配置全部详细步骤(包含各种报错解决办法)

前言&#xff1a; 名字由来&#xff1a;翻译是野猫&#xff0c;tomcat的作者初衷是希望这个软件可以自力更生&#xff0c;自给自足。不依赖其他插件&#xff0c;独立达到提供web服务的效果 1.tocat和java的关系&#xff1f; tomcat是用Java语言编写的&#xff0c;需要运行在…

大三,请问现在自学Java还来得及吗?

前言 如果还在为入门Java晚而发愁时间够不够&#xff0c;首先你是准备自学&#xff0c;那么我们可以看看现在网络上一些比较热门的Java全体系的学习需要化多长时间&#xff0c;先拿B站上做的比较好的黑马教程和尚硅谷举例&#xff1a; 2022黑马程序员Java学习路线图​www.bili…

耗时半月,终于把牛客网软件测试面试八股文,整理成了文档资料.....

一、面试基础题 简述测试流程: 1、阅读相关技术文档&#xff08;如产品PRD、UI设计、产品流程图等&#xff09;。 2、参加需求评审会议。 3、根据最终确定的需求文档编写测试计划。 4、编写测试用例&#xff08;等价类划分法、边界值分析法等&#xff09;。 5、用例评审(…

飞象星球落地重庆云阳86所学校,县乡4万学生迎来素质课堂

猜生字笔画顺序、学习硬笔书法&#xff1b;跟随老师认识情绪、写下心里话……自从重庆云阳县86所中小学引入飞象星球双师素质课堂&#xff0c;4万多名县城和乡村孩子的课后素质课堂一下子变得丰富多彩起来。 图&#xff1a;洞鹿小学双河村校上双师素质书法课 云阳县地处三峡库…

代码随想录算法训练营第三十六天| LeetCode435. 无重叠区间、LeetCode763. 划分字母区间、LeetCode56. 合并区间

一、LeetCode435. 无重叠区间 1&#xff1a;题目描述&#xff08;435. 无重叠区间&#xff09; 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 2&#xff1a;解题思路 class …

MySQL面试问题汇总(2022)

一、MySQL架构 锁 什么是锁&#xff1f; 当多个连接并发地存取MySQL数据时&#xff0c;在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据&#xff0c;破坏数据库的一致性。 加锁是实现数据库并发控制的一个非常重要的…

BFV同态加密方案初步学习

BFV是把Bra12的LWE版本推到了RLWE版本&#xff0c;Bra12也可以叫做BFV。 经典的RLWE的公钥加密算法回顾 对比以前的Regev的LWE公钥加密方案&#xff0c;其实几乎只是把明文空间换了&#xff0c;也就是在最大比特编码的时候把2换成t&#xff0c;即&#xff0c;Δ⌊q/t⌋\Delta…

web网页设计期末课程大作业——香格里拉旅游网页设计 5页 HTML+CSS+JavaScript

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…