CUDA 从入门到放弃(一)

news2024/11/25 2:41:39

CUDA从入门到放弃系列包含内容

  1. 异构并行计算
  2. CUDA编程模型
  3. CUDA执行模型
  4. CUDA内存
  5. CUDA流和并发
  6. CUDA指令级原语
  7. GPU加速库
  8. 多GPU编程

本文你将了解到

  1. 异构并行计算
  2. CUDA编程模型

温馨提示: 本文可能引发C/C++零基础的读者不适,请谨慎观看.


一、聊聊异构并行计算

异构并行计算的本质是把任务分发给不同架构的硬件计算单元(比方说CPU、GPU、FPGA等),让他们各司其职。

同步工作。如同平时工作,把业务中不同类型的任务分给不同的计算资源运行。

从软件的角度来讲
异构并行计算框架是让软件开发人员高效地开发异构并行的程序。充分使用计算平台资源。

从硬件角度来讲
一方面,多种不同类型的计算单元通过很多其它时钟频率和内核数量提高计算能力

另一方面,各种计算单元通过技术优化(如GPU从底层架构支持通用计算,通过分支预測、原子运算、动态并行、统一寻址、NIC直接訪问显存等能力)提高运行效率。

异构计算(Heterogeneous Computing) 在80年代中期就已产生。其定义更加宽泛。

异构计算主要是指使用不同类型指令集和体系架构的计算单元组成系统的计算方式。

常见的计算单元类别包含CPU、GPU等协处理器、DSP、ASIC、FPGA等。

一个异构计算平台往往包含使用不同指令集架构(ISA)的处理器。

在HPC异构并行计算架构应用技术中,通常分为通用架构并行专用架构并行

通用架构并行分为:

  • 同构多核并行(X86 CPU多核并行计算和非X86 CPU多核并行计算)
  • 异构众核并行(CPU+GPU异构协同计算和CPU+MIC异构协同计算)

专用架构并行: 主要是指CPU+FPGA异构协同计算

从更广义的角度来讲,不同计算平台的各个层次上都存在异构现象

除硬件层的指令集、互联、内存层次之外。软件层中应用二进制接口、API、语言特性底层实现等的不同。

对于上层应用和服务而言,都是异构的。

异构并行计算框架有个很重要的特征就是可以帮助开发人员屏蔽底层硬件差异,能让软件平台自适应未来硬件的演进。

概括来说,理想的异构计算具有例如以下的一些要素

  • 它所使用的计算资源具有多种类型的计算能力。如SIMD、MIMD、向量、标量、专用等
  • 它须要识别计算任务中各子任务的并行性需求类型。
  • 它须要使具有不同计算类型的计算资源能相互协调运行。
  • 它既要开发应用问题中的并行性,更要开发应用问题中的异构性
  • 它追求的终于目标是使计算任务的运行具有最短时间。

异构计算技术是一种使计算任务的并行性类型(代码类型)与机器能有效支持的计算类型(即机器能力)最匹配、最能充分利用各种计算资源的并行和分布计算技术。

异构计算处理过程本质上可分为三个阶段:

  1. 并行性检測阶段。并行性检測不是异构计算特有的。同构计算也须要经历这一阶段。可用并行和分布计算中的常规方法加以处理。
  2. 并行性特征(类型)析取阶段。并行性特征析取阶段是异构计算特有的,这一阶段的主要工作是预计应用中每一个任务的计算类型參数,包含映射及对任务间通信代价的考虑。
  3. 任务映射和调度阶段,也称为资源分配阶段。主要确定每一个任务(或子任务)应该映射到哪台机器上运行以及何时开始运行。

从用户来看,上述的异步计算处理过程可用两种方法来实现:

第一种是用户指导法,即由用户用显式的编译器命令指导编译器完毕相应用代码类型分析及有关任务的分解等工作,这是一种显式开发异构性和并行性方法,较易于实现。

但对用户有一定要求,需将异构计算思想融入用户程序中。

这是当前主流採用的方法,我们所知的CUDA(Computing Unified Device Architecture) 、OpenCL都是採用用户指导法。

还有一种是编译器指导法,需将异构思想融入编译器中,然后由具有“异构智能”的编译器自己主动完毕应用代码类型分析、任务分解、任务映射及调度等工作,即实现自己主动异构计算。

这是一种隐式开发异构性和并行性方法,是异构计算追求的终极目标。但难度很大,对编译器要求很高。

NVIDIA已经開始部分尝试编译器指导法。当然,基于CUDA Core的同构特征,也可以觉得其做的是同构自己主动调度。

异构计算按提供计算类型多样性的形式来看。

可分为系统异构计算SHC (System Heterogeneous Computing) 和网络异构计算NHC (Network Heterogeneous Computing) 两大类。

SHC以单机多处理器形式提供多种计算类型

NHC则以网络连接的多计算机形式提供多种计算类型

用于HPC的计算系统(如IBM RoadRunner等)属于NHC;当前热门的CUDA、OpenCL都属于SHC的范畴。

深度学习应用领域应用对异构并行计算领域很关注


二、聊聊CUDA

CUDA平台不是单单指软件或者硬件,而是建立在Nvidia GPU上的一整套平台,并扩展出多语言支持
在这里插入图片描述

CUDA C 是标准ANSI C语言的扩展,扩展出一些语法和关键字来编写设备端代码,而且CUDA库本身提供了大量API来操作设备完成计算。

对于API也有两种不同的层次,一种相对底层,CUDA驱动API,一种相对交高层,CUDA运行时API

在这里插入图片描述
驱动API是低级的API,使用相对困难,运行时API是高级API使用简单,其实现基于驱动API。

两种API是互斥的,两者之间的函数不可以混合调用,只能用其中的一个库。

一个CUDA应用通常可以分解为两部分:CPU 主机端代码、GPU 设备端代码

CUDA nvcc编译器会自动分离你代码里面的不同部分

如下图中主机代码用C写成,使用本地的C语言编译器编译,设备端代码,也就是核函数

用CUDA C编写,通过nvcc编译,链接阶段,在内核程序调用或者明显的GPU设备操作时,添加运行时库。

在这里插入图片描述
nvcc 是从LLVM开源编译系统为基础开发的。

在这里插入图片描述
使用cuda打印Hello World试一试

/*
*hello_world.cu
*/
#include<stdio.h>
__global__ void hello_world(void)
{
  printf("GPU: Hello world!\n");
}
int main(int argc,char **argv)
{
  printf("CPU: Hello world!\n");
  hello_world<<<1,10>>>();
  cudaDeviceReset();//if no this line ,it can not output hello world from gpu
  return 0;
}
  • __global__ :告诉编译器这个是个可以在设备上执行的核函数
  • hello_world<<<1,10>>>();:C语言中没有’<<<>>>’是对设备进行配置的参数(CUDA扩展出来的部分)
  • cudaDeviceReset();: 隐式同步:CPU等待GPU运行完成后,退出主线程

三、聊聊CUDA编程模型

CUDA是一种通用的并行计算平台和编程模型,是在C语言基础上扩展的。

CUDA编程模型假设系统是由一个主机(CPU)和一个设备(GPU)组成的,而且各自拥有独立的内存。

对于用户,需要做的就是编写运行在主机和设备上的代码,并且根据代码的需要为主机和设备分配内存空间以及拷贝数据。

运行在设备上的代码,我们一般称之为核函数(Kernel),核函数将会由大量硬件线程并行执行。

一个典型的CUDA程序是按这样的步骤执行的:

  1. 把数据从CPU内存拷贝到GPU内存。
  2. 调用核函数对存储在GPU内存中的数据进行操作的。
  3. 将数据从GPU内存传送回CPU内存。

CUDA编程模型有两个特色功能

(1)通过一种层次结构来组织线程

一般CPU一个核只支持一到两个硬件线程,而GPU往往在硬件层面上就支持同时成百上千个并发线程。

不过这也要求我们在GPU编程中更加高效地管理这些线程,以达到更高的运行效率。

在CUDA编程中,线程是通过线程网格(Grid)、线程块(Block)、线程束(Warp)、线程(Thread)这几个层次进行管理的

(2)通过层次结构来组织内存的访问

为了达到更高的效率,在CUDA编程中我们需要格外关注内存的使用。

与CPU编程不同,GPU中的各级缓存以及各种内存是可以软件控制的,在编程时我们可以手动指定变量存储的位置。

具体而言,这些内存包括寄存器、共享内存、常量内存、全局内存等。

这就造成了CUDA编程中有很多内存使用的小技巧,比如我们要尽量使用寄存器,尽量将数据声明为局部变量。

而当存在着数据的重复利用时,可以把数据存放在共享内存里。

而对于全局内存,我们需要注意用一种合理的方式来进行数据的合并访问,以尽量减少设备对内存子系统再次发出访问操作的次数。

CUDA的线程管理

首先我们需要了解线程是如何组织的,下面这幅图比较清晰地表示出了线程的组织结构。

当核函数在主机端启动时,其执行会移动到设备上,此时设备中会产生大量的线程并且每个线程都执行由核函数指定的语句。
在这里插入图片描述
由一个内核启动所产生的所有线程统称一个网格(Grid),同一网格中的所有线程共享相同的全局内存空间。

向下一级,一个网格由多个线程块(Block)构成。

再下一级,一个线程块由一组线程(Thread)构成。

线程网格和线程块从逻辑上代表了一个核函数的线程层次结构,这种组织方式可以帮助我们有效地利用资源,优化性能。

CUDA编程中,我们可以组织三维的线程网格和线程块,具体如何组织,一般是和我们需要处理的数据有关。

上面这个示意图展示的是一个包含二维线程块的二维线程网格。

如果使用了合适的线程网格和线程块大小来正确地组织线程,内核的性能可以得到大大地提高。

通常给定一个需求,我们会有多种选择来实现核函数,并且我们会有多种不同的配置来执行该核函数。

而学习如何组织线程就是其中的重点之一,后面我们通过编程例子来学习具体如何合理组织。

下面有一段代码,块的索引和维度

/*
*1_check_dimension
*/
#include <cuda_runtime.h>
#include <stdio.h>
__global__ void checkIndex(void)
{
  printf("threadIdx:(%d,%d,%d) blockIdx:(%d,%d,%d) blockDim:(%d,%d,%d)\
  gridDim(%d,%d,%d)\n",threadIdx.x,threadIdx.y,threadIdx.z,
  blockIdx.x,blockIdx.y,blockIdx.z,blockDim.x,blockDim.y,blockDim.z,
  gridDim.x,gridDim.y,gridDim.z);
}
int main(int argc,char **argv)
{
  int nElem=6;
  dim3 block(3);
  dim3 grid((nElem+block.x-1)/block.x);
  printf("grid.x %d grid.y %d grid.z %d\n",grid.x,grid.y,grid.z);
  printf("block.x %d block.y %d block.z %d\n",block.x,block.y,block.z);
  checkIndex<<<grid,block>>>();
  cudaDeviceReset();
  return 0;
}

检查网格和块的大小

/*
*2_grid_block
*/
#include <cuda_runtime.h>
#include <stdio.h>
int main(int argc,char ** argv)
{
  int nElem=1024;
  dim3 block(1024);
  dim3 grid((nElem-1)/block.x+1);
  printf("grid.x %d block.x %d\n",grid.x,block.x);

  block.x=512;
  grid.x=(nElem-1)/block.x+1;
  printf("grid.x %d block.x %d\n",grid.x,block.x);

  block.x=256;
  grid.x=(nElem-1)/block.x+1;
  printf("grid.x %d block.x %d\n",grid.x,block.x);

  block.x=128;
  grid.x=(nElem-1)/block.x+1;
  printf("grid.x %d block.x %d\n",grid.x,block.x);

  cudaDeviceReset();
  return 0;
}

CUDA的内存管理

CUDA编程另一个显著的特点就是解释了内存层次结构,每一个GPU设备都会有用于不同用途的存储类型。

在这里插入图片描述
对于上图:我们只关注寄存器(Registers)、共享内存(Shared Memory)和全局内存(Global Memory)。

寄存器是GPU上运行速度最快的内存空间,通常其带宽为8TB/s左右,延迟为1个时钟周期。

核函数中声明的一个没有其他修饰符的自变量,通常就存储在寄存器中。

最快速也最受偏爱的存储器就是设备中的寄存器,属于具有重要价值有极度缺乏的资源。

共享内存是GPU上可受用户控制的一级缓存。

共享内存类似于CPU的缓存,不过与CPU的缓存不同,GPU的共享内存可以有CUDA内核直接编程控制。

由于共享内存是片上内存,所以与全局内存相比,它具有更高的带宽与更低的延迟,通常其带宽为1.5TB/s左右,延迟为1~32个时钟周期。

对于共享内存的使用,主要考虑数据的重用性。

当存在着数据的重复利用时,使用共享内存是比较合适的。

如果数据不被重用,则直接将数据从全局内存或常量内存读入寄存器即可。

全局内存是GPU中最大、延迟最高并且最常使用的内存。

全局内存类似于CPU的系统内存。在编程中对全局内存访问的优化以最大化程度提高全局内存的数据吞吐量是十分重要的。

这里我们来个例子,两个向量的加法

/*
* https://github.com/Tony-Tan/CUDA_Freshman
* 3_sum_arrays
*/
#include <cuda_runtime.h>
#include <stdio.h>
#include "freshman.h"


void sumArrays(float * a,float * b,float * res,const int size)
{
  for(int i=0;i<size;i+=4)
  {
    res[i]=a[i]+b[i];
    res[i+1]=a[i+1]+b[i+1];
    res[i+2]=a[i+2]+b[i+2];
    res[i+3]=a[i+3]+b[i+3];
  }
}
__global__ void sumArraysGPU(float*a,float*b,float*res)
{
  int i=threadIdx.x;
  res[i]=a[i]+b[i];
}
int main(int argc,char **argv)
{
  int dev = 0;
  cudaSetDevice(dev);

  int nElem=32;
  printf("Vector size:%d\n",nElem);
  int nByte=sizeof(float)*nElem;
  float *a_h=(float*)malloc(nByte);
  float *b_h=(float*)malloc(nByte);
  float *res_h=(float*)malloc(nByte);
  float *res_from_gpu_h=(float*)malloc(nByte);
  memset(res_h,0,nByte);
  memset(res_from_gpu_h,0,nByte);

  float *a_d,*b_d,*res_d;
  CHECK(cudaMalloc((float**)&a_d,nByte));
  CHECK(cudaMalloc((float**)&b_d,nByte));
  CHECK(cudaMalloc((float**)&res_d,nByte));

  initialData(a_h,nElem);
  initialData(b_h,nElem);

  CHECK(cudaMemcpy(a_d,a_h,nByte,cudaMemcpyHostToDevice));
  CHECK(cudaMemcpy(b_d,b_h,nByte,cudaMemcpyHostToDevice));

  dim3 block(nElem);
  dim3 grid(nElem/block.x);
  sumArraysGPU<<<grid,block>>>(a_d,b_d,res_d);
  printf("Execution configuration<<<%d,%d>>>\n",block.x,grid.x);

  CHECK(cudaMemcpy(res_from_gpu_h,res_d,nByte,cudaMemcpyDeviceToHost));
  sumArrays(a_h,b_h,res_h,nElem);

  checkResult(res_h,res_from_gpu_h,nElem);
  cudaFree(a_d);
  cudaFree(b_d);
  cudaFree(res_d);

  free(a_h);
  free(b_h);
  free(res_h);
  free(res_from_gpu_h);

  return 0;
}

使用CUDA核函数

核函数就是在CUDA模型上诸多线程中运行的那段串行代码,这段代码在设备上运行,用NVCC编译

产生的机器码是GPU的机器码,所以我们写CUDA程序就是写核函数

第一步我们要确保核函数能正确的运行产生正切的结果

第二步优化CUDA程序的部分,无论是优化算法,还是调整内存结构,线程结构都是要调整核函数内的代码,来完成这些优化的。

启动核函数,通过的以下的ANSI C 扩展出的CUDA C指令:

kernel_name<<<grid,block>>>(argument list);

其标准C的原型就是C语言函数调用

function_name(argument list);

通过指定grid和block的维度,我们可以配置:内核中线程的数目、内核中使用的线程布局

可以使用dim3类型的grid维度和block维度配置内核,也可以使用int类型的变量,或者常量直接初始化:

kernel_name<<<4,8>>>(argument list);

指令的线程布局是:
在这里插入图片描述

核函数是同时复制到多个线程执行的

多个计算执行在一个数据,肯定是浪费时间,所以为了让多线程按照我们的意愿对应到不同的数据,就要给线程一个唯一的标识

由于设备内存是线性的(基本市面上的内存硬件都是线性形式存储数据的)我们观察上图,可以用threadIdx.x 和blockIdx.x 来组合获得对应的线程的唯一标识

改变核函数的配置,产生运行出结果一样,但效率不同的代码:

kernel_name<<<1,32>>>(argument list); // 一个块

kernel_name<<<32,1>>>(argument list); // 32个块

上述代码如果没有特殊结构在核函数中,执行结果应该一致,但是有些效率会一直比较低。

上面这些是启动部分,当主机启动了核函数,控制权马上回到主机,而不是主机等待设备完成核函数的运行

想要主机等待设备端执行可以用下面这个指令:

cudaError_t cudaDeviceSynchronize(void);

这是一个显示的方法,对应的也有隐式方法,隐式方法就是不明确

说明主机要等待设备端,而是设备端不执行完,主机没办法进行,比如内存拷贝函数:

cudaError_t cudaMemcpy(void* dst,const void * src, size_t count,cudaMemcpyKind kind);

当核函数启动后的下一条指令就是从设备复制数据回主机端,那么主机端必须要等待设备端计算完成。

编写CUDA核函数

核函数也是一个函数,但是声明核函数有一个比较模板化的方法

__global__ void kernel_name(argument list);

注意:声明和定义是不同的,这点CUDA与C语言是一致的

在C语言函数前没有的限定符global ,CUDA C中还有一些其他我们在C中没有的限定符,如下:

  • global:设备端执行,可以从主机调用也可以从计算能力3以上的设备调用,必须有一个void的返回类型
  • device: 设备端执行, 设备端调用
  • host: 主机端执行,主机调用,可以省略

这里有个特殊的情况就是有些函数可以同时定义为 device 和 host

这种函数可以同时被设备和主机端的代码调用,主机端代码调用函数很正常

设备端调用函数与C语言一致,但是要声明成设备端代码,告诉nvcc编译成设备机器码,

同时声明主机端设备端函数,那么就要告诉编译器,生成两份不同设备的机器码。

Kernel核函数编写有以下限制

  1. 只能访问设备内存
  2. 必须有void返回类型
  3. 不支持可变数量的参数
  4. 不支持静态变量
  5. 显示异步行为

并行程序中经常的一种现象:把串行代码并行化时对串行代码块for的操作,也就是把for并行化。

// 串行
void sumArraysOnHost(float *A, float *B, float *C, const int N) {
  for (int i = 0; i < N; i++)
    C[i] = A[i] + B[i];
}

//并行
__global__ void sumArraysOnGPU(float *A, float *B, float *C) {
  int i = threadIdx.x;
  C[i] = A[i] + B[i];
}

验证CUDA核函数

验证核函数就是验证其正确性,下面这段代码上文出现过,但是同样包含验证核函数的方法:

/*
* https://github.com/Tony-Tan/CUDA_Freshman
* 3_sum_arrays
*/
#include <cuda_runtime.h>
#include <stdio.h>
#include "freshman.h"


void sumArrays(float * a,float * b,float * res,const int size)
{
  for(int i=0;i<size;i+=4)
  {
    res[i]=a[i]+b[i];
    res[i+1]=a[i+1]+b[i+1];
    res[i+2]=a[i+2]+b[i+2];
    res[i+3]=a[i+3]+b[i+3];
  }
}
__global__ void sumArraysGPU(float*a,float*b,float*res)
{
  int i=threadIdx.x;
  res[i]=a[i]+b[i];
}
int main(int argc,char **argv)
{
  int dev = 0;
  cudaSetDevice(dev);

  int nElem=32;
  printf("Vector size:%d\n",nElem);
  int nByte=sizeof(float)*nElem;
  float *a_h=(float*)malloc(nByte);
  float *b_h=(float*)malloc(nByte);
  float *res_h=(float*)malloc(nByte);
  float *res_from_gpu_h=(float*)malloc(nByte);
  memset(res_h,0,nByte);
  memset(res_from_gpu_h,0,nByte);

  float *a_d,*b_d,*res_d;
  CHECK(cudaMalloc((float**)&a_d,nByte));
  CHECK(cudaMalloc((float**)&b_d,nByte));
  CHECK(cudaMalloc((float**)&res_d,nByte));

  initialData(a_h,nElem);
  initialData(b_h,nElem);

  CHECK(cudaMemcpy(a_d,a_h,nByte,cudaMemcpyHostToDevice));
  CHECK(cudaMemcpy(b_d,b_h,nByte,cudaMemcpyHostToDevice));

  dim3 block(nElem);
  dim3 grid(nElem/block.x);
  sumArraysGPU<<<grid,block>>>(a_d,b_d,res_d);
  printf("Execution configuration<<<%d,%d>>>\n",block.x,grid.x);

  CHECK(cudaMemcpy(res_from_gpu_h,res_d,nByte,cudaMemcpyDeviceToHost));
  sumArrays(a_h,b_h,res_h,nElem);

  checkResult(res_h,res_from_gpu_h,nElem);
  cudaFree(a_d);
  cudaFree(b_d);
  cudaFree(res_d);

  free(a_h);
  free(b_h);
  free(res_h);
  free(res_from_gpu_h);

  return 0;
}

CUDA错误处理

获得每个函数执行后的返回结果,然后对不成功的信息加以处理,CUDA C 的API每个调用都会返回一个错误代码

#define CHECK(call)\
{\
  const cudaError_t error=call;\
  if(error!=cudaSuccess)\
  {\
      printf("ERROR: %s:%d,",__FILE__,__LINE__);\
      printf("code:%d,reason:%s\n",error,cudaGetErrorString(error));\
      exit(1);\
  }\
}

CUDA编译执行

nvcc xxxx.cu -o xxxx

参考文献

  • 《Professional CUDA C Programming》John Cheng
  • https://www.cnblogs.com/ldxsuanfa/p/9913830.html
  • https://zhuanlan.zhihu.com/p/97044592
  • https://face2ai.com/

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

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

相关文章

MQTT Broker mosquito配置以及使用tls证书登录附上Python调用代码

MQTT Broker mosquito配置以及使用tls证书登录 文章目录MQTT Broker mosquito配置以及使用tls证书登录1. 前言2. 安装3. mosquito相关命令3.1 运行停止查看状态3.2 创建可以登录mosquito的用户3.3 配置权限规则文件3.4 配置mosquito3.5 使用mosquito_pub和mosquito_sub测试3.5.…

web手势库Alloyfinger

前言 在上一篇文章 前端pdf预览、pdfjs的使用&#xff0c;我们使用pdf.js 来实现了pdf的预览。但是客户车间里的电脑是触摸屏&#xff0c;要求能够手势放大图纸&#xff0c;能够拖动图纸。最终决定使用 Alloyfinger 来解决手势的问题。 官方github https://github.com/Alloy…

计算机网络(一):计算机网络概念、功能、组成

计算机网络的概念 计算机网络&#xff1a;是一个将分散的、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统 计算机网络是互连的、自治的计算机集合。 互连&#xff1a;互联互通 自治&#xff1a;无…

前端面经 前端优化

前端面经 前端优化 文章目录前端面经 前端优化HTTP/2 Web优化最佳实践DNS与解析如何使用CDN分发缓存策略页面渲染优化避免CSS、JS阻塞CSS阻塞JS的阻塞改变JS阻塞的方式使用字体图标iconfont代替图片图标降低CSS选择器的复杂性减少重绘和回流如何避免图片资源优化Webpack优化降低…

【Vue】VueCLI 的使用和单文件组件(1)

学习内容&#xff1a; 1&#xff09;了解一下 Vue 的脚手架工具&#xff1b; 2) 认识一下 Vue 里面的单文件组件。‍‍ 在真正开发大型项目的时候&#xff0c;实际上我们并不能通过一个这样的index点html的方式去编写一个比较大型的项目&#xff0c;‍‍ 所以我们要学习使用 V…

JavaScript 69 JavaScript Web API 69.3 Web History API

JavaScript 文章目录JavaScript69 JavaScript Web API69.3 Web History API69.3.1 History back() 方法69.3.2 History go() 方法69.3.3 History 对象属性69.3.4 History 对象方法69 JavaScript Web API 69.3 Web History API Web History API 提供了访问 windows.history 对…

Linux进程与操作系统详解

文章目录一&#xff1a;冯诺依曼体系二&#xff1a;操作系统三&#xff1a;进程和PCB四&#xff1a;进程相关的指令五&#xff1a;getpid和getppid系统调用六&#xff1a;fork系统调用七&#xff1a;进程状态八&#xff1a;Linux下进程状态大全8.1&#xff1a;R(running)8.2&am…

Hive3 - HiveSQL 特征及操作

一、HiveSQL特征 Hive SQL&#xff08;HQL&#xff09;与SQL的语法大同小异&#xff0c;基本上是相通的&#xff0c;对SQL掌握的可以非常快的上手使用Hive SQL。不过在一些细节上需要特别注意Hive自己特有的语法知识点&#xff0c;下面分别进行介绍。 1. 字段数据类型 Hive数…

【附源码】Python计算机毕业设计烹饪课程预约系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

电脑重装系统c盘如何备份资料

现在越来越多的用户都喜欢重装自己所喜欢的电脑系统&#xff0c;但需要在重新安装系统之前备份自己的电脑。因此有很多用户问重装系统c盘如何备份的文件。今天小编就教下大家重装系统c盘如何备份的教程。 工具/原料&#xff1a; 系统版本&#xff1a;windows7系统 品牌型号&…

多目标应用:非支配排序的鲸鱼优化算法NSWOA优化RBF神经网络实现数据预测(RBF隐藏层神经元个数可以自行设定)

一、非支配排序的鲸鱼优化算法 非支配排序的鲸鱼优化算法&#xff08;Non-Dominated Sorting Whale Optimization Algorithm&#xff0c;NSWOA&#xff09;由Pradeep Jangir和 Narottam Jangir于2017年提出。 NSWOA算法流程如下&#xff1a; 二、RBF神经网络 1988年&#xf…

Spring-Aop面向切面编程

文章目录一、简介1、作用2、AOP核心概念3、五种&#xff08;增强&#xff09;通知类型二、AOP入门小案例&#xff08;注解版&#xff09;1.导入坐标(pom.xml)2.制作连接点(原始操作&#xff0c;Dao接口与实现类)3:定义通知类和通知4:定义切入点5:制作切面6:将通知类配给容器并标…

Linux操作系统

Linux 基础入门 Linux操作系统 操作系统 首先&#xff0c;我们需要知道什么是操作系统&#xff1a; 操作系统是应用程序运行的基础支撑环境操作系统作用是管理与控制计算机系统硬件与软件资源&#xff08;软件与硬件的中间层&#xff09;Intel X86 架构上的常见的操作系统&…

linux基本指令(上)

文章目录1.whomai指令2. pwd指令3. ls 指令1. ls指令2. ls -l指令3.ls -la指令1. cd .2. cd . .4. ls -ld指令5. ls -i指令1. windows 与linux标识文件之间的区别6. ls -R指令4. cd指令1.cd 指令2. cd ~ 指令3.cd -指令5. 根目录1.绝对路径2.相对路径例判断相对路径是否唯一6. …

leetcode-每日一题-119-杨辉三角2(简单,dp)

今天这道题是一道简单的dp题说实话很好想到解法&#xff0c;就是用两个dp来回倒换即可解除&#xff0c;然后也没啥难度所以就不解释了&#xff0c;养成一个好习惯就是设置数组的时候需要malloc动态获取即可&#xff0c;然后使用memset进行初始化&#xff0c;注意memset只能进行…

期货行权(期货行权日)

​ 期权如何行权&#xff1f;期货到期了如何进行实物交割&#xff1f; 期权到期日 Expiration Date&#xff1a;到期日即是指期权合约所规定的&#xff0c;期权购买者可以实际执行该期权的最后日期。 对欧式期权而言&#xff0c;为期权合约可以行权的唯一一天&#xff1b;对…

[附源码]计算机毕业设计JAVAjsp心理测评系统

[附源码]计算机毕业设计JAVAjsp心理测评系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis…

经典图割算法中图的构建及实现:Graph-Cut

经典图割算法中图的:Graph-Cut一&#xff0e;graph-cut&#xff1a;准则函数二&#xff0e;Graph-cut&#xff1a;图的建立1.术语&#xff1a;2.图的建立3. 头文件4. 源文件5. 测试图像讲解目前典型的3种图割算法&#xff1a;graph-cut、grab-but、one-cut。本文主要讲解graph-…

【IEEE2014】EET:基于采样的机器人运动规划中的平衡勘探与开发

EET&#xff1a;基于采样的机器人运动规划中的平衡勘探与开发 摘要&#xff1a; 本文提出了一种用于运动规划的探索/利用树&#xff08;EET&#xff09;算法。EET规划者故意用概率的完整性来换取计算效率。这种权衡使EET规划器能够比最先进的基于采样的规划器多三个数量级。我们…

【蓝桥杯真题练习】STEMA科技素养练习题库 答案版013 持续更新中~

1、人工神经网络是一种模仿动物神经系统设计的机器学习方法,它被用于解决各种传统编程无法解决的问题。“神经网络”一词在英文中是( )。 A.Neural Network B.Neural Engine C.Machine Learning D.Machine Network 答案:A 2、中央处理器(CPU)是手机,电脑…