1. 介绍

1.1 GPU vs. CPU

  • GPU 使用更多的晶体管进行数据处理,而不是数据缓存和流控制,因此可以提供高度的并行计算。
  • GPU 可以通过计算来隐藏内存访问延迟,而不是依赖于大量的数据缓存和复杂的流控制来避免长时间的内存访问延迟。

1.2 CUDA

NVIDIA 推出的一种通用的并行计算平台编程模型,它利用了 NVIDIA GPU 中的并行计算引擎。

2. 编程模型

2.1 kernel

  • 就是一个 C++ 函数,但与普通 C++ 函数不同的是:当 kernel 被调用时,它会在 N 个不同的 CUDA 线程上被并行执行,也就是一共被执行了 N 次。
  • 使用 __global__ 来标识 kernel。
  • 执行 kernel 所使用的 CUDA 线程数在 <<<...>>> 中指定:kernelName<<<blocksPerGid, threadsPerBlock>>>(params...)
__global__ void vectorAdd(const float *A, const float *B, float *C, int numElements)
{
int i = blockDim.x * blockIdx.x + threadIdx.x; if (i < numElements)
{
C[i] = A[i] + B[i];
}
} int main(void)
{
// ...
// Launch the Vector Add CUDA Kernel
int threadsPerBlock = 256;
int blocksPerGrid =(numElements + threadsPerBlock - 1) / threadsPerBlock;
vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, numElements);
// ...
}

2.2 线程层次关系

  • 一个 grid 可以包含多个 block,一个 block 又可以包含多个 thread。
  • 内建变量 blockIdx(block在grid中的索引)是一个三维变量,threadIdx(thread在block中的索引)也是一个三维变量。这意味着 grid 中的 blocks 可以组织成三维,block 中的 threads 也可以组织成三维。如,blockIdx.x 表示 block 在 grid 中的 x 坐标(x轴方向上第几个 block);threadIdx.x 表示 thread 在 block 中的 x 坐标(x轴方向上第几个 thread)。
  • block 的各个维度的大小可用通过内建变量 blockDim 来获得,如,blockDim.x 表示 grid 在 x 轴方向上有多少个 block。
  • 线程索引和线程 ID 的对应关系如下:
线程块 线程块大小 线程索引 线程 ID
一维 (Dx) (x) x
二维 (Dx, Dy) (x, y) yDx + x
三维 (Dx, Dy, Dz) (x, y, z) zDxDy + yDx + x
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N])
{
   // blockIdx.x * blockDim.x 表示当前 block 的第一个 thread 在 grid 中的 x 坐标
   // 所以 blockIdx.x * blockDim.x + threadIdx.x 表示当前 thread 在 grid 中的 x 坐标
int i = blockIdx.x * blockDim.x + threadIdx.x;

   // blockIdx.y * blockDim.y 表示当前 block 的第一个 thread 在 grid 中的 y 坐标
   // 所以 blockIdx.y * blockDim.y + threadIdx.y 表示当前 thread 在 grid 中的 y 坐标
int j = blockIdx.y * blockDim.y + threadIdx.y;

if (i < N && j < N)
C[i][j] = A[i][j] + B[i][j];
} int main()
{
...
dim3 threadsPerBlock(16, 16);
dim3 blocksPerGrid(N / threadsPerBlock.x, N / threadsPerBlock.y);
MatAdd<<<blocksPerGrid, threadsPerBlock>>>(A, B, C);
...
}

2.3 内存层次关系

  • thread 私有内存:每个 thread 都有一个本地内存,该内存只有自己能够访问;
  • block 共享内存:每个 block 都有一个共享内存,该内存只有 block 内的 thread 可访问;
  • 全局内存:所有 thread 都可以访问全局内存;
  • 常量内存纹理(texture) 内存:只读、全局可访问。

全局内存、常量内存和纹理内存空间在由同一应用程序启动的多个 kernel 之间是持久的。

2.4 异构编程

kernel (device code)在 GPU (device)上执行,程序的其他部分(host code)在 CPU (host)上执行。

3. 编程接口

3.1 全局内存

3.1.1 一维

  • 分配内存:cudaMalloc()
  • 释放内存:cudaFree()
  • 在 host 和 device 之间拷贝内存:cudaMemcpy()
int N = 1024;
size_t size = N * sizeof(float); // 分配 host 内存
float* h_A = (float*)malloc(size);
float* h_B = (float*)malloc(size); // 初始化输入
... // 分配 device 内存
float* d_A;
cudaMalloc(&d_A, size);
float* d_B;
cudaMalloc(&d_B, size);
float* d_C;
cudaMalloc(&d_C, size); // 拷贝内存:host -> device
cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice); // 调用 kernel
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
VecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N); // 返回结果:拷贝内存,device -> host
cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost); // 释放 device 内存
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C); // 释放 host 内存
free(h_A)
free(h_B)
free(h_C)

对于二维、三维向量来说,也可以使用 cudaMalloc() 和 cudaMemcpy(),但是使用下列函数可以进行适当填充,实现内存对齐。

3.1.2 二维

  • 分配内存:cudaMallocPitch()
  • 在 host 和 device 之间拷贝内存:cudaMemcpy2D()
// Host code
int width = 64, height = 64;
float* devPtr;
size_t pitch;

cudaMallocPitch(&devPtr, &pitch, width * sizeof(float), height);

MyKernel<<<100, 512>>>(devPtr, pitch, width, height); // Device code
__global__ void MyKernel(float* devPtr, size_t pitch, int width, int height)
{
for (int r = 0; r < height; ++r) {
float* row = (float*)((char*)devPtr + r * pitch);
for (int c = 0; c < width; ++c) {
float element = row[c];
}
}
}

3.1.3 三维

  • 分配内存:cudaMalloc3D()
  • 在 host 和 device 之间拷贝内存:cudaMemcpy3D()
// Host code
int width = 64, height = 64, depth = 64;
cudaExtent extent = make_cudaExtent(width * sizeof(float), height, depth);

cudaPitchedPtr devPitchedPtr;
cudaMalloc3D(&devPitchedPtr, extent);

MyKernel<<<100, 512>>>(devPitchedPtr, width, height, depth); // Device code
__global__ void MyKernel(cudaPitchedPtr devPitchedPtr, int width, int height, int depth)
{
char* devPtr = devPitchedPtr.ptr;
size_t pitch = devPitchedPtr.pitch;
size_t slicePitch = pitch * height;
for (int z = 0; z < depth; ++z) {
char* slice = devPtr + z * slicePitch;
for (int y = 0; y < height; ++y) {
float* row = (float*)(slice + y * pitch);
for (int x = 0; x < width; ++x) {
float element = row[x];
}
}
}
}

3.2 共享内存

共享内存使用标识符 __shared__ 表示。

// Matrices are stored in row-major order:
// M(row, col) = *(M.elements + row * M.stride + col)
typedef struct {
int width; // 每个矩阵/子矩阵的宽
int height; // 每个矩阵/子矩阵的高
int stride; // 对子矩阵而言有意义,表示所在大矩阵的宽
float* elements; // 数据区起始地址
} Matrix; // Get a matrix element
__device__ float GetElement(const Matrix A, int row, int col)
{
return A.elements[row * A.stride + col];
} // Set a matrix element
__device__ void SetElement(Matrix A, int row, int col,
float value)
{
A.elements[row * A.stride + col] = value;
} // Get the BLOCK_SIZExBLOCK_SIZE sub-matrix Asub of A that is
// located col sub-matrices to the right and row sub-matrices down
// from the upper-left corner of A
__device__ Matrix GetSubMatrix(Matrix A, int row, int col)
{
Matrix Asub;
Asub.width = BLOCK_SIZE;
Asub.height = BLOCK_SIZE;
Asub.stride = A.stride;
Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row + BLOCK_SIZE * col];
return Asub;
} // Thread block size
#define BLOCK_SIZE 16 // Forward declaration of the matrix multiplication kernel
__global__ void MatMulKernel(const Matrix, const Matrix, Matrix); // Matrix multiplication - Host code
// Matrix dimensions are assumed to be multiples of BLOCK_SIZE
void MatMul(const Matrix A, const Matrix B, Matrix C)
{
// Load A and B to device memory
Matrix d_A;
d_A.width = d_A.stride = A.width; d_A.height = A.height;
size_t size = A.width * A.height * sizeof(float);
cudaMalloc(&d_A.elements, size);
cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice); Matrix d_B;
d_B.width = d_B.stride = B.width; d_B.height = B.height;
size = B.width * B.height * sizeof(float);
cudaMalloc(&d_B.elements, size);
cudaMemcpy(d_B.elements, B.elements, size, cudaMemcpyHostToDevice); // Allocate C in device memory
Matrix d_C;
d_C.width = d_C.stride = C.width; d_C.height = C.height;
size = C.width * C.height * sizeof(float);
cudaMalloc(&d_C.elements, size); // Invoke kernel
dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y);
MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C); // Read C from device memory
cudaMemcpy(C.elements, d_C.elements, size, cudaMemcpyDeviceToHost); // Free device memory
cudaFree(d_A.elements);
cudaFree(d_B.elements);
cudaFree(d_C.elements);
} // Matrix multiplication kernel called by MatMul()
__global__ void MatMulKernel(Matrix A, Matrix B, Matrix C)
{
// Block row and column
int blockRow = blockIdx.y;
int blockCol = blockIdx.x; // Each thread block computes one sub-matrix Csub of C
Matrix Csub = GetSubMatrix(C, blockRow, blockCol); // Each thread computes one element of Csub
// by accumulating results into Cvalue
float Cvalue = 0; // Thread row and column within Csub
int row = threadIdx.y;
int col = threadIdx.x; // Loop over all the sub-matrices of A and B that are
// required to compute Csub
// Multiply each pair of sub-matrices together
// and accumulate the results
for (int m = 0; m < (A.width / BLOCK_SIZE); ++m) { // Get sub-matrix Asub of A
Matrix Asub = GetSubMatrix(A, blockRow, m); // Get sub-matrix Bsub of B
Matrix Bsub = GetSubMatrix(B, m, blockCol); // Shared memory used to store Asub and Bsub respectively
__shared__ float As[BLOCK_SIZE][BLOCK_SIZE];
__shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE]; // Load Asub and Bsub from device memory to shared memory
// Each thread loads one element of each sub-matrix
As[row][col] = GetElement(Asub, row, col);
Bs[row][col] = GetElement(Bsub, row, col); // Synchronize to make sure the sub-matrices are loaded
// before starting the computation
__syncthreads();
// Multiply Asub and Bsub together
for (int e = 0; e < BLOCK_SIZE; ++e)
Cvalue += As[row][e] * Bs[e][col]; // Synchronize to make sure that the preceding
// computation is done before loading two new
// sub-matrices of A and B in the next iteration
__syncthreads();
} // Write Csub to device memory
// Each thread writes one element
SetElement(Csub, row, col, Cvalue);
}

4. C++ 扩展

4.1 函数标识符

__global__

  • 在 device 上执行;
  • 从 host 上调用;
  • 标识一个函数为 kernel;
  • 异步执行:在 device 完成计算之前就返回;
  • 返回值为 void,不可以是成员函数;

__device__

  • 在 device 上执行;
  • 只能从 device 上调用;

__host__

  • 在 host 上执行;
  • 只能从 host 上调用;

4.2 变量标识符

__device__

  • 驻留在 device 上;
  • 位于全局内存中;(如果不和其他标识符一起使用的话)
  • 拥有和 CUDA context 相同的生命周期;
  • 可以和以下标识符一起使用;

__constant__

  • 位于常量内存中;
  • 拥有和 CUDA context 相同的生命周期;

__shared__

  • 位于共享内存中;
  • 拥有和 block 相同的生命周期;
  • 只能被 block 内的线程访问;

__managed__

  • 可以在 device 和 host 上访问;
  • 拥有和应用程序相同的生命周期;

4.3 内建变量

  • gridDim:grid 的维度,dim3 类型;
  • blockDim:block 的维度,dim3 类型;
  • blockIdx:block 在 grid 内的索引,uint3 类型;
  • threadIdx:thread 在 block 内的索引,uint3 类型;

5. 例子

ps:本例子是在 cuda 自带的 samples 的基础上做的简化。

vectorAdd.cu:

#include <stdio.h>
#include <cuda_runtime.h>
#include <helper_cuda.h> // C = A + B
__global__ void vectorAdd(const float *A, const float *B, float *C, int numElements) {
int i = blockDim.x * blockIdx.x + threadIdx.x; if (i < numElements) {
C[i] = A[i] + B[i];
}
} int main(void) {
cudaError_t err = cudaSuccess; int numElements = 50000;
size_t size = numElements * sizeof(float); // 分配 host 内存
float *h_A = (float *)malloc(size);
float *h_B = (float *)malloc(size);
float *h_C = (float *)malloc(size); if (h_A == NULL || h_B == NULL || h_C == NULL) {
fprintf(stderr, "Failed to allocate host vectors!\n");
exit(EXIT_FAILURE);
} // 初始化输入向量
for (int i = 0; i < numElements; ++i) {
h_A[i] = rand()/(float)RAND_MAX;
h_B[i] = rand()/(float)RAND_MAX;
} // 分配 device 内存
float *d_A = NULL;
err = cudaMalloc((void **)&d_A, size);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to allocate device vector A (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} float *d_B = NULL;
err = cudaMalloc((void **)&d_B, size);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to allocate device vector B (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} float *d_C = NULL;
err = cudaMalloc((void **)&d_C, size);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to allocate device vector C (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} // 拷贝内存:host -> device
err = cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to copy vector A from host to device (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} err = cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to copy vector B from host to device (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} // 启动 kernel
int threadsPerBlock = 256;
int blocksPerGrid =(numElements + threadsPerBlock - 1) / threadsPerBlock; vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, numElements);
err = cudaGetLastError();
if (err != cudaSuccess) {
fprintf(stderr, "Failed to launch vectorAdd kernel (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} // 返回计算结果:device -> host
err = cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to copy vector C from device to host (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} // 验证计算结果
for (int i = 0; i < numElements; ++i) {
if (fabs(h_A[i] + h_B[i] - h_C[i]) > 1e-5) {
fprintf(stderr, "Result verification failed at element %d!\n", i);
exit(EXIT_FAILURE);
}
}
printf("Test PASSED\n"); // 释放 device 内存
err = cudaFree(d_A);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to free device vector A (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} err = cudaFree(d_B);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to free device vector B (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} err = cudaFree(d_C);
if (err != cudaSuccess) {
fprintf(stderr, "Failed to free device vector C (error code %s)!\n", cudaGetErrorString(err));
exit(EXIT_FAILURE);
} // 释放 host 内存
free(h_A);
free(h_B);
free(h_C); printf("Done\n");
return 0;
}

Makefile:

CUDA_PATH ?= "/usr/local/cuda-10.0"

# 架构
HOST_ARCH := $(shell uname -m)
TARGET_ARCH ?= $(HOST_ARCH)
TARGET_SIZE := 64 # 操作系统
HOST_OS := $(shell uname -s 2>/dev/null | tr "[:upper:]" "[:lower:]")
TARGET_OS ?= $(HOST_OS) # 编译器
HOST_COMPILER ?= g++
NVCC := $(CUDA_PATH)/bin/nvcc -ccbin $(HOST_COMPILER) NVCCFLAGS := -m${TARGET_SIZE}
CCFLAGS :=
LDFLAGS := NVCCFLAGS += -g -G
BUILD_TYPE := debug ALL_CCFLAGS :=
ALL_CCFLAGS += $(NVCCFLAGS)
ALL_CCFLAGS += $(EXTRA_NVCCFLAGS)
ALL_CCFLAGS += $(addprefix -Xcompiler ,$(CCFLAGS))
ALL_CCFLAGS += $(addprefix -Xcompiler ,$(EXTRA_CCFLAGS)) ALL_LDFLAGS :=
ALL_LDFLAGS += $(ALL_CCFLAGS)
ALL_LDFLAGS += $(addprefix -Xlinker ,$(LDFLAGS))
ALL_LDFLAGS += $(addprefix -Xlinker ,$(EXTRA_LDFLAGS)) INCLUDES := -I../../common/inc
LIBRARIES := SMS ?= 30 35 37 50 52 60 61 70 75
$(foreach sm,$(SMS),$(eval GENCODE_FLAGS += -gencode arch=compute_$(sm),code=sm_$(sm)))
HIGHEST_SM := $(lastword $(sort $(SMS)))
GENCODE_FLAGS += -gencode arch=compute_$(HIGHEST_SM),code=compute_$(HIGHEST_SM) all: build
build: vectorAdd vectorAdd.o:vectorAdd.cu
$(NVCC) $(INCLUDES) $(ALL_CCFLAGS) $(GENCODE_FLAGS) -o $@ -c $< vectorAdd: vectorAdd.o
$(NVCC) $(ALL_LDFLAGS) $(GENCODE_FLAGS) -o $@ $+ $(LIBRARIES)
mkdir -p ../../bin/$(TARGET_ARCH)/$(TARGET_OS)/$(BUILD_TYPE)
cp $@ ../../bin/$(TARGET_ARCH)/$(TARGET_OS)/$(BUILD_TYPE) run: build
./vectorAdd clean:
rm -f vectorAdd vectorAdd.o
rm -rf ../../bin/$(TARGET_ARCH)/$(TARGET_OS)/$(BUILD_TYPE)/vectorAdd
vectorAdd$ make
"/usr/local/cuda-10.0"/bin/nvcc -ccbin g++ -I../../common/inc -m64 -g -G -gencode arch=compute_30,code=sm_30 -gencode arch=compute_35,code=sm_35 -gencode arch=compute_37,code=sm_37 -gencode arch=compute_50,code=sm_50 -gencode arch=compute_52,code=sm_52 -gencode arch=compute_60,code=sm_60 -gencode arch=compute_61,code=sm_61 -gencode arch=compute_70,code=sm_70 -gencode arch=compute_75,code=sm_75 -gencode arch=compute_75,code=compute_75 -o vectorAdd.o -c vectorAdd.cu
"/usr/local/cuda-10.0"/bin/nvcc -ccbin g++ -m64 -g -G -gencode arch=compute_30,code=sm_30 -gencode arch=compute_35,code=sm_35 -gencode arch=compute_37,code=sm_37 -gencode arch=compute_50,code=sm_50 -gencode arch=compute_52,code=sm_52 -gencode arch=compute_60,code=sm_60 -gencode arch=compute_61,code=sm_61 -gencode arch=compute_70,code=sm_70 -gencode arch=compute_75,code=sm_75 -gencode arch=compute_75,code=compute_75 -o vectorAdd vectorAdd.o
mkdir -p ../../bin/x86_64/linux/debug
cp vectorAdd ../../bin/x86_64/linux/debug

参考资料

https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html

CUDA 介绍的更多相关文章

  1. CUDA基础介绍

    一.GPU简介 1985年8月20日ATi公司成立,同年10月ATi使用ASIC技术开发出了第一款图形芯片和图形卡,1992年4月ATi发布了Mach32图形卡集成了图形加速功能,1998年4月ATi ...

  2. professional cuda c programming--CUDA库简单介绍

    CUDA Libraries简单介绍   上图是CUDA 库的位置.本文简要介绍cuSPARSE.cuBLAS.cuFFT和cuRAND.之后会介绍OpenACC. cuSPARSE线性代数库,主要针 ...

  3. 56 Marvin: 一个支持GPU加速、且不依赖其他库(除cuda和cudnn)的轻量化多维深度学习(deep learning)框架介绍

    0 引言 Marvin是普林斯顿视觉实验室(PrincetonVision)于2015年提出的轻量化GPU加速的多维深度学习网络框架.该框架采用纯c/c++编写,除了cuda和cudnn以外,不依赖其 ...

  4. GPU 、APU、CUDA、TPU、FPGA介绍

    购买显卡主要关注:显存.带宽和浮点运算数量   GPU :图形处理器(英语:Graphics Processing Unit,缩写:GPU),又称显示核心.视觉处理器.显示芯片,是一种专门在个人电脑. ...

  5. 转载一篇介绍CUDA

    鉴于自己的毕设需要使用GPU CUDA这项技术,想找一本入门的教材,选择了Jason Sanders等所著的书<CUDA By Example an Introduction to Genera ...

  6. 【CUDA】CUDA框架介绍

    引用 出自Bookc的博客,链接在此http://bookc.github.io/2014/05/08/my-summery-the-book-cuda-by-example-an-introduct ...

  7. CUDA && GPU中dim3介绍

  8. 手把手教你搭建深度学习平台——避坑安装theano+CUDA

    python有多混乱我就不多说了.这个混论不仅是指整个python市场混乱,更混乱的还有python的各种附加依赖包.为了一劳永逸解决python的各种依赖包对深度学习造成的影响,本文中采用pytho ...

  9. [CUDA] CUDA to DL

    又是一枚祖国的骚年,阅览做做笔记:http://www.cnblogs.com/neopenx/p/4643705.html 这里只是一些基础知识.帮助理解DL tool的实现. “这也是深度学习带来 ...

随机推荐

  1. K8S构建1台master2台node+Harbor_笔记

    部署环境: master.node:centos7虚拟机 网络使用本地网卡共享到VMnet1(仅主机),虚拟机使用VMnet1. 准备文件: CentOS-7-x86_64-Minimal-1810. ...

  2. python菜鸟教程学习:数据结构

    列表方法 list.append(x):把一个元素添加到列表的结尾,相当于 a[len(a):] = [x]. list.extend(L):通过添加指定列表的所有元素来扩充列表,相当于 a[len( ...

  3. Geoserver 谷歌瓦片地图的使用 多级发布

    下面,我来介绍一下如何在离线的情况下,在Geoserver 中配置出如同谷歌地图般绚丽的效果. 为了让大家有动力看我我接下来写的东西,我先把结果图给大伙儿展现一下: 正如上图所示,该地图是谷歌第四级的 ...

  4. 【自定义轮播图】微信小程序自定义轮播图无缝滚动

    先试试效果,可以通过设置参数调整样式 微信小程序中的轮播图可以直接使用swiper组件,如下: <swiper indicator-dots="{{indicatorDots}}&qu ...

  5. 【整理】互联网服务端技术体系:高性能之并发(Java)

    分而合之,并行不悖. 综述入口见:"互联网应用服务端的常用技术思想与机制纲要" 引子 并发,就是在同一时间段内有多个任务同时进行着.这些任务或者互不影响互不干扰,或者共同协作来完成 ...

  6. vue+element对常用表格的简单封装

    在后台管理和中台项目中, table是使用率是特别的高的, 虽然element已经有table组件, 但是分页和其他各项操作还是要写一堆的代码, 所以就在原有的基础上做了进一步的封装 所涵盖的功能有: ...

  7. MySQL中的排序

    在编写SQL 语句时常常会用到 order by 进行排序,那么排序过程是什么样的?为什么有些排序执行比较快,有些排序执行很慢?又该如何去优化? 索引排序 索引排序指的是在通过索引查询时就完成了排序, ...

  8. Java基础经典案例

    案例列表 01减肥计划switch版本 02减肥计划if版本 03逢七跳过 04不死神兔 05百钱白鸡 06数组元素求和 07判断两个数组是否相同 08查找元素在数组中的索引 09数组元素反转 10评 ...

  9. MODBUS_RTU通信协议

    Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表.Modbus已经成为工业领域通信协议 ...

  10. Beta冲刺——第八天

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...