《cuda c编程权威指南》05 - cuda矩阵求和

目录

1. 使用一个二维网格和二维块的矩阵加法

1.1 关键代码

1.2 完整代码

1.3 运行时间

2. 使用一维网格和一维块的矩阵加法

2.1 关键代码

2.2 完整代码

2.3 运行时间

3. 使用二维网格和一维块的矩阵矩阵加法

3.1 关键代码

3.2 完整代码

3.3 运行时间


1. 使用一个二维网格和二维块的矩阵加法

这里建立二维网格(2,3)+二维块(4,2)为例,使用其块和线程索引映射矩阵索引。

(1)第一步,可以用以下公式把线程和块索引映射到矩阵坐标上;

 (2)第二步,可以用以下公式把矩阵坐标映射到全局内存中的索引/存储单元上;

 比如要获取矩阵元素(col,row) = (2,4) ,其全局索引是34,映射到矩阵坐标上,

ix = 2 + 0*3=2; iy = 0 + 2*2=4. 然后再映射到全局内存idx = 4*8 + 2 = 34.

1.1 关键代码

(1) 先固定二维线程块尺寸,再利用矩阵尺寸结合被分块尺寸,推导出二维网格尺寸。

// config
int dimx = 32;
int dimy = 32;
dim3 block(dimx, dimy);  // 二维线程块(x,y)=(4,2)
dim3 grid((nx + block.x - 1) / block.x, (ny + block.y - 1) / block.y); // 二维网格(2,3)
// 直接nx/block.x = 8/4=2. (8+4-1)/4=2. (9+4-1)/4=3

(2) 前面建立好了二维网格和二维线程块,根据公式去求矩阵索引。

// 去掉了循环
// 利用公式,使用二维网格、二维线程块映射矩阵索引。
__global__ void sumMatrixOnDevice2D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{// 二维网格和二维块,映射到矩阵坐标unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;unsigned int iy = threadIdx.y + blockIdx.y * blockDim.y;// 由矩阵坐标, 映射到全局坐标(都是线性存储的)unsigned int idx = iy * nx + ix;  // 坐标(ix, iy),前面由iy行,每行有nx个元素// 相加if (ix < nx && iy < ny)  // 配置线程的可能过多,这里防止越界。{d_c[idx] = d_a[idx] + d_b[idx];}
}

1.2 完整代码

#include "cuda_runtime.h"
#include "device_launch_parameters.h"  // threadIdx#include <stdio.h>    // io
#include <time.h>     // time_t clock_t
#include <stdlib.h>  // rand
#include <memory.h>  //memset#define CHECK(call)                                   \
{                                                     \const cudaError_t error_code = call;              \if (error_code != cudaSuccess)                    \{                                                 \printf("CUDA Error:\n");                      \printf("    File:       %s\n", __FILE__);     \printf("    Line:       %d\n", __LINE__);     \printf("    Error code: %d\n", error_code);   \printf("    Error text: %s\n",                \cudaGetErrorString(error_code));          \exit(1);                                      \}                                                 \
}/// <summary>
/// 矩阵相加,线性存储的二维矩阵
/// </summary>
/// <param name="h_a"></param>
/// <param name="h_b"></param>
/// <param name="h_c"></param>
/// <param name="nx"></param>
/// <param name="ny"></param>
void sumMatrixOnHost(float* h_a, float* h_b, float* h_c, const int nx, const int ny)
{float* ia = h_a;float* ib = h_b;float* ic = h_c;for (int iy = 0; iy < ny; iy++){for (int ix = 0; ix < nx; ix++)  // 处理当前行{ic[ix] = ia[ix] + ib[ix];}ia += nx; ib += nx; ic += nx;  // 移动到下一行,ia下一行的第一个索引变成了0.}
}// 去掉循环
__global__ void sumMatrixOnDevice2D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{// 二维网格和二维块,映射到矩阵坐标unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;unsigned int iy = threadIdx.y + blockIdx.y * blockDim.y;// 由矩阵坐标, 映射到全局坐标(都是线性存储的)unsigned int idx = iy * nx + ix;  // 坐标(ix, iy),前面由iy行,每行有nx个元素// 相加if (ix < nx && iy < ny)  // 配置线程的可能过多,这里防止越界。{d_c[idx] = d_a[idx] + d_b[idx];}
}void initialData(float* p, const int N)
{//generate different seed from random numbertime_t t;srand((unsigned int)time(&t));  // 生成种子for (int i = 0; i < N; i++){p[i] = (float)(rand() & 0xFF) / 10.0f;  // 随机数}
}void checkResult(float* hostRef, float* deviceRef, const int N)
{double eps = 1.0E-8;int match = 1;for (int i = 0; i < N; i++){if (hostRef[i] - deviceRef[i] > eps){match = 0;printf("\nArrays do not match\n");printf("host %5.2f gpu %5.2f at current %d\n", hostRef[i], deviceRef[i], i);break;}}if (match)printf("\nArrays match!\n");
}int main(void)
{// get device infoint device = 0;cudaDeviceProp deviceProp;CHECK(cudaGetDeviceProperties(&deviceProp, device));printf("Using device: %d %s", device, deviceProp.name);  // 卡号0的显卡名称。CHECK(cudaSetDevice(device));  // 设置显卡号// set matrix dimension. 2^14 = 16384行列数int nx = 1 << 13, ny = 1 << 13, nxy = nx * ny;int nBytes = nxy * sizeof(float);// malloc host memoryfloat* h_a, * h_b, * hostRef, * gpuRef;h_a = (float*)malloc(nBytes);h_b = (float*)malloc(nBytes);hostRef = (float*)malloc(nBytes); // 主机端求得的结果gpuRef = (float*)malloc(nBytes);  // 设备端拷回的数据// init datainitialData(h_a, nxy);initialData(h_b, nxy);memset(hostRef, 0, nBytes);memset(gpuRef, 0, nBytes);clock_t begin = clock();// add matrix on host side for result checks.sumMatrixOnHost(h_a, h_b, hostRef, nx, ny);printf("\ncpu: %f s\n", (double)(clock() - begin) / CLOCKS_PER_SEC);// malloc device memoryfloat* d_mat_a, * d_mat_b, * d_mat_c;cudaMalloc((void**)&d_mat_a, nBytes);cudaMalloc((void**)&d_mat_b, nBytes);cudaMalloc((void**)&d_mat_c, nBytes);// transfer data from host to devicecudaMemcpy(d_mat_a, h_a, nBytes, cudaMemcpyHostToDevice);cudaMemcpy(d_mat_b, h_b, nBytes, cudaMemcpyHostToDevice);// configint dimx = 32;int dimy = 32;dim3 block(dimx, dimy);  // 二维线程块(x,y)=(4,2)dim3 grid((nx + block.x - 1) / block.x, (ny + block.y - 1) / block.y); // 二维网格(2,3)// 直接nx/block.x = 8/4=2. (8+4-1)/4=2.// invoke kernelbegin = clock();sumMatrixOnDevice2D << <grid, block >> > (d_mat_a, d_mat_b, d_mat_c, nx, ny);CHECK(cudaDeviceSynchronize());printf("\ngpu: %f s\n", (double)(clock() - begin) / CLOCKS_PER_SEC);// check kernel errorCHECK(cudaGetLastError());// copy kernel result back to host sidecudaMemcpy(gpuRef, d_mat_c, nBytes, cudaMemcpyDeviceToHost);// check resultcheckResult(hostRef, gpuRef, nxy);// free memorycudaFree(d_mat_a);cudaFree(d_mat_b);cudaFree(d_mat_c);free(h_a);free(h_b);free(hostRef);free(gpuRef);// reset devicecudaDeviceReset();return 0;
}

1.3 运行时间

加法运行速度提高了8倍。数据量越大,提升越明显。

2. 使用一维网格和一维块的矩阵加法

 为了使用一维网格和一维块,需要写一个新的核函数,其中每个线程处理ny个数据元素。如上图,一维网格是水平方向的一维,一维块是水平方向的一维。

2.1 关键代码

(1) 建立垂直方向的一维块,再是水平方向一维网格

// config
int dimx = 32;
int dimy = 1;
dim3 block(dimx, dimy);  // 一维线程块(x,y)=(32,1),其中每一个线程都处理ny个元素。
// 一维网格((nx+block.x-1)/block.x,1)
dim3 grid((nx + block.x - 1) / block.x, 1); 

 (2) 前面建立好了一维网格和一维线程块,根据公式去求矩阵索引。

 假设blockDim = (32,1). gridDim = (1024,1). 比如当前的threadIdx.x = 10, 由于前面有一个块,当前的位置映射矩阵索引

ix = threadIdx.x + blockIdx.x * blockDim.x = 10 + 1 * 32 = 42

由于一个线程处理ny个元素(所以这里有一个循环,ix不变,iy从第一行开始到ny行)。

__global__ void sumMatrixOnDevice1D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;// 一个线程处理ny个数据if (ix < nx){for (int iy = 0; iy < ny; iy++)  // 处理ix这一列元素。{int idx = iy * nx + ix;  // 第iy行前面有iy*nx个元素,再加上当前行第ix个d_c[idx] = d_a[idx] + d_b[idx];}}
}

2.2 完整代码

#include "cuda_runtime.h"
#include "device_launch_parameters.h"  // threadIdx#include <stdio.h>    // io
#include <time.h>     // time_t clock_t
#include <stdlib.h>  // rand
#include <memory.h>  //memset#define CHECK(call)                                   \
{                                                     \const cudaError_t error_code = call;              \if (error_code != cudaSuccess)                    \{                                                 \printf("CUDA Error:\n");                      \printf("    File:       %s\n", __FILE__);     \printf("    Line:       %d\n", __LINE__);     \printf("    Error code: %d\n", error_code);   \printf("    Error text: %s\n",                \cudaGetErrorString(error_code));          \exit(1);                                      \}                                                 \
}/// <summary>
/// 矩阵相加,线性存储的二维矩阵
/// </summary>
/// <param name="h_a"></param>
/// <param name="h_b"></param>
/// <param name="h_c"></param>
/// <param name="nx"></param>
/// <param name="ny"></param>
void sumMatrixOnHost(float* h_a, float* h_b, float* h_c, const int nx, const int ny)
{float* ia = h_a;float* ib = h_b;float* ic = h_c;for (int iy = 0; iy < ny; iy++){for (int ix = 0; ix < nx; ix++)  // 处理当前行{ic[ix] = ia[ix] + ib[ix];}ia += nx; ib += nx; ic += nx;  // 移动到下一行,ia下一行的第一个索引变成了0.}
}// 去掉了循环
__global__ void sumMatrixOnDevice2D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{// 二维网格和二维块,映射到矩阵坐标unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;unsigned int iy = threadIdx.y + blockIdx.y * blockDim.y;// 由矩阵坐标, 映射到全局坐标(都是线性存储的)unsigned int idx = iy * nx + ix;  // 坐标(ix, iy),前面由iy行,每行有nx个元素// 相加if (ix < nx && iy < ny)  // 配置线程的可能过多,这里防止越界。{d_c[idx] = d_a[idx] + d_b[idx];}
}__global__ void sumMatrixOnDevice1D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;// 一个线程处理ny个数据if (ix < nx){for (int iy = 0; iy < ny; iy++){int idx = iy * nx + ix;  // 第iy行前面有iy*nx个元素,再加上当前行第ix个d_c[idx] = d_a[idx] + d_b[idx];}}
}void initialData(float* p, const int N)
{//generate different seed from random numbertime_t t;srand((unsigned int)time(&t));  // 生成种子for (int i = 0; i < N; i++){p[i] = (float)(rand() & 0xFF) / 10.0f;  // 随机数}
}void checkResult(float* hostRef, float* deviceRef, const int N)
{double eps = 1.0E-8;int match = 1;for (int i = 0; i < N; i++){if (hostRef[i] - deviceRef[i] > eps){match = 0;printf("\nArrays do not match\n");printf("host %5.2f gpu %5.2f at current %d\n", hostRef[i], deviceRef[i], i);break;}}if (match)printf("\nArrays match!\n");
}int main(void)
{// get device infoint device = 0;cudaDeviceProp deviceProp;CHECK(cudaGetDeviceProperties(&deviceProp, device));printf("Using device: %d %s", device, deviceProp.name);  // 卡号0的显卡名称。CHECK(cudaSetDevice(device));  // 设置显卡号// set matrix dimension. 2^14 = 16384行列数int nx = 1 << 13, ny = 1 << 13, nxy = nx * ny;int nBytes = nxy * sizeof(float);// malloc host memoryfloat* h_a, * h_b, * hostRef, * gpuRef;h_a = (float*)malloc(nBytes);h_b = (float*)malloc(nBytes);hostRef = (float*)malloc(nBytes); // 主机端求得的结果gpuRef = (float*)malloc(nBytes);  // 设备端拷回的数据// init datainitialData(h_a, nxy);initialData(h_b, nxy);memset(hostRef, 0, nBytes);memset(gpuRef, 0, nBytes);clock_t begin = clock();// add matrix on host side for result checks.sumMatrixOnHost(h_a, h_b, hostRef, nx, ny);printf("\ncpu: %f s\n", (double)(clock() - begin) / CLOCKS_PER_SEC);// malloc device memoryfloat* d_mat_a, * d_mat_b, * d_mat_c;cudaMalloc((void**)&d_mat_a, nBytes);cudaMalloc((void**)&d_mat_b, nBytes);cudaMalloc((void**)&d_mat_c, nBytes);// transfer data from host to devicecudaMemcpy(d_mat_a, h_a, nBytes, cudaMemcpyHostToDevice);cudaMemcpy(d_mat_b, h_b, nBytes, cudaMemcpyHostToDevice);// configint dimx = 32;int dimy = 1;dim3 block(dimx, dimy);  // 一维线程块(x,y)=(32,1),其中每一个线程都处理ny个元素。dim3 grid((nx + block.x - 1) / block.x, 1); // 一维网格((nx+block.x-1)/block.x,1)// invoke kernelbegin = clock();//sumMatrixOnDevice2D << <grid, block >> > (d_mat_a, d_mat_b, d_mat_c, nx, ny);sumMatrixOnDevice1D << <grid, block >> > (d_mat_a, d_mat_b, d_mat_c, nx, ny);CHECK(cudaDeviceSynchronize());printf("\ngpu: %f s\n", (double)(clock() - begin) / CLOCKS_PER_SEC);// check kernel errorCHECK(cudaGetLastError());// copy kernel result back to host sidecudaMemcpy(gpuRef, d_mat_c, nBytes, cudaMemcpyDeviceToHost);// check resultcheckResult(hostRef, gpuRef, nxy);// free memorycudaFree(d_mat_a);cudaFree(d_mat_b);cudaFree(d_mat_c);free(h_a);free(h_b);free(hostRef);free(gpuRef);// reset devicecudaDeviceReset();return 0;
}

2.3 运行时间

3. 使用二维网格和一维块的矩阵矩阵加法

当使用一个包含一维块的二维网格时,每个线程都只关注一个数据元素并且网格的第二个维数等于ny。比如块的维度是(32,1),网格的维度是((nx+32-1)/32, (ny+1-1)/1) = ((nx+32-1)/32, ny).  

这可以看作是含有一个二维块的二维网格的特殊情况,其中块的第二个维数是1. 

利用块和网格索引,映射矩阵索引,如上图,blockIdx.y等于矩阵索引iy: 

ix = threadIdx.x + blockIdx.x * blockDim.x;

iy = threadIdx.y + blockIdx.y * blockDim.y = threadIdx.y + 0 * 1 = threadIdx.y 

再利用矩阵索引,映射全局内存索引:

idx = iy * nx + ix;   // 当前行iy,块外的前面有iy * nx个元素,块内索引是ix

3.1 关键代码

(1) 建立一维线程块和二维网格

// config
int dimx = 32;
int dimy = 1;
dim3 block(dimx, dimy);  // 一维线程块(x,y)=(32,1),其中每一个线程都处理一个元素。
// ((nx+32-1)/32, (ny+1-1)/1) = ((nx+32-1)/32, ny)
dim3 grid((nx + block.x - 1) / block.x, ny);

(2)利用块和网格索引,映射矩阵索引,再映射全局内存索引

__global__ void sumMatrixOnDevice2DGrid1DBlock(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;unsigned int iy = blockIdx.y;  // threadIdx.y + blockIdx.y * blockDim.y = threadIdx.y + 0 * 1unsigned int idx = iy * nx + ix;if (ix < nx && iy < ny){d_c[idx] = d_a[idx] + d_b[idx];}
}

3.2 完整代码

 

#include "cuda_runtime.h"
#include "device_launch_parameters.h"  // threadIdx#include <stdio.h>    // io
#include <time.h>     // time_t clock_t
#include <stdlib.h>  // rand
#include <memory.h>  //memset#define CHECK(call)                                   \
{                                                     \const cudaError_t error_code = call;              \if (error_code != cudaSuccess)                    \{                                                 \printf("CUDA Error:\n");                      \printf("    File:       %s\n", __FILE__);     \printf("    Line:       %d\n", __LINE__);     \printf("    Error code: %d\n", error_code);   \printf("    Error text: %s\n",                \cudaGetErrorString(error_code));          \exit(1);                                      \}                                                 \
}/// <summary>
/// 矩阵相加,线性存储的二维矩阵
/// </summary>
/// <param name="h_a"></param>
/// <param name="h_b"></param>
/// <param name="h_c"></param>
/// <param name="nx"></param>
/// <param name="ny"></param>
void sumMatrixOnHost(float* h_a, float* h_b, float* h_c, const int nx, const int ny)
{float* ia = h_a;float* ib = h_b;float* ic = h_c;for (int iy = 0; iy < ny; iy++){for (int ix = 0; ix < nx; ix++)  // 处理当前行{ic[ix] = ia[ix] + ib[ix];}ia += nx; ib += nx; ic += nx;  // 移动到下一行,ia下一行的第一个索引变成了0.}
}// 去掉了循环
__global__ void sumMatrixOnDevice2D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{// 二维网格和二维块,映射到矩阵坐标unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;unsigned int iy = threadIdx.y + blockIdx.y * blockDim.y;// 由矩阵坐标, 映射到全局坐标(都是线性存储的)unsigned int idx = iy * nx + ix;  // 坐标(ix, iy),前面由iy行,每行有nx个元素// 相加if (ix < nx && iy < ny)  // 配置线程的可能过多,这里防止越界。{d_c[idx] = d_a[idx] + d_b[idx];}
}__global__ void sumMatrixOnDevice1D(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;// 一个线程处理ny个数据if (ix < nx){for (int iy = 0; iy < ny; iy++){int idx = iy * nx + ix;  // 第iy行前面有iy*nx个元素,再加上当前行第ix个d_c[idx] = d_a[idx] + d_b[idx];}}
}__global__ void sumMatrixOnDevice2DGrid1DBlock(float* d_a, float* d_b, float* d_c, const int nx, const int ny)
{unsigned int ix = threadIdx.x + blockIdx.x * blockDim.x;unsigned int iy = blockIdx.y;  // threadIdx.y + blockIdx.y * blockDim.y = threadIdx.y + 0 * 1unsigned int idx = iy * nx + ix;if (ix < nx && iy < ny){d_c[idx] = d_a[idx] + d_b[idx];}
}void initialData(float* p, const int N)
{//generate different seed from random numbertime_t t;srand((unsigned int)time(&t));  // 生成种子for (int i = 0; i < N; i++){p[i] = (float)(rand() & 0xFF) / 10.0f;  // 随机数}
}void checkResult(float* hostRef, float* deviceRef, const int N)
{double eps = 1.0E-8;int match = 1;for (int i = 0; i < N; i++){if (hostRef[i] - deviceRef[i] > eps){match = 0;printf("\nArrays do not match\n");printf("host %5.2f gpu %5.2f at current %d\n", hostRef[i], deviceRef[i], i);break;}}if (match)printf("\nArrays match!\n");
}int main(void)
{// get device infoint device = 0;cudaDeviceProp deviceProp;CHECK(cudaGetDeviceProperties(&deviceProp, device));printf("Using device: %d %s", device, deviceProp.name);  // 卡号0的显卡名称。CHECK(cudaSetDevice(device));  // 设置显卡号// set matrix dimension. 2^14 = 16384行列数int nx = 1 << 13, ny = 1 << 13, nxy = nx * ny;int nBytes = nxy * sizeof(float);// malloc host memoryfloat* h_a, * h_b, * hostRef, * gpuRef;h_a = (float*)malloc(nBytes);h_b = (float*)malloc(nBytes);hostRef = (float*)malloc(nBytes); // 主机端求得的结果gpuRef = (float*)malloc(nBytes);  // 设备端拷回的数据// init datainitialData(h_a, nxy);initialData(h_b, nxy);memset(hostRef, 0, nBytes);memset(gpuRef, 0, nBytes);clock_t begin = clock();// add matrix on host side for result checks.sumMatrixOnHost(h_a, h_b, hostRef, nx, ny);printf("\ncpu: %f s\n", (double)(clock() - begin) / CLOCKS_PER_SEC);// malloc device memoryfloat* d_mat_a, * d_mat_b, * d_mat_c;cudaMalloc((void**)&d_mat_a, nBytes);cudaMalloc((void**)&d_mat_b, nBytes);cudaMalloc((void**)&d_mat_c, nBytes);// transfer data from host to devicecudaMemcpy(d_mat_a, h_a, nBytes, cudaMemcpyHostToDevice);cudaMemcpy(d_mat_b, h_b, nBytes, cudaMemcpyHostToDevice);// configint dimx = 32;int dimy = 1;dim3 block(dimx, dimy);  // 一维线程块(x,y)=(32,1),其中每一个线程都处理一个元素。// ((nx+32-1)/32, (ny+1-1)/1) = ((nx+32-1)/32, ny)dim3 grid((nx + block.x - 1) / block.x, ny);// invoke kernelbegin = clock();//sumMatrixOnDevice2D << <grid, block >> > (d_mat_a, d_mat_b, d_mat_c, nx, ny);  //sumMatrixOnDevice1D << <grid, block >> > (d_mat_a, d_mat_b, d_mat_c, nx, ny);sumMatrixOnDevice2DGrid1DBlock << <grid, block >> > (d_mat_a, d_mat_b, d_mat_c, nx, ny);CHECK(cudaDeviceSynchronize());printf("\ngpu: %f s\n", (double)(clock() - begin) / CLOCKS_PER_SEC);// check kernel errorCHECK(cudaGetLastError());// copy kernel result back to host sidecudaMemcpy(gpuRef, d_mat_c, nBytes, cudaMemcpyDeviceToHost);// check resultcheckResult(hostRef, gpuRef, nxy);// free memorycudaFree(d_mat_a);cudaFree(d_mat_b);cudaFree(d_mat_c);free(h_a);free(h_b);free(hostRef);free(gpuRef);// reset devicecudaDeviceReset();return 0;
}

3.3 运行时间

 

 

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

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

相关文章

记一次ubuntu16误删libc.so.6操作的恢复过程

背景 操作系统&#xff1a;ubuntu16 glibc版本&#xff1a;2.23 修改原因&#xff1a; 经过一系列报错和手工构建之后&#xff0c;vulkansdk成功安装&#xff08;起码运行./vulkansdu成功&#xff09;&#xff0c;在进行./vulkaninfo进行验证时&#xff0c;报错&#xff1a…

拦截器在SpringBoot中使用,HandlerInterceptor,WebMvcConfigurer

拦截器在Controller之前执行。 用于权限校验&#xff0c;日志记录&#xff0c;性能监控 在SpringBoot中使用 创建拦截器类&#xff1a;首先&#xff0c;创建一个Java类来实现拦截器逻辑。拦截器类应该实现Spring提供的HandlerInterceptor接口。实现拦截器方法&#xff1a;拦…

【Ubuntu】Ubuntu 22.04 升级 OpenSSH 9.3p2 修复CVE-2023-38408

升级原因 近日Openssh暴露出一个安全漏洞CVE-2023-38408&#xff0c;以下是相关资讯&#xff1a; 一、漏洞详情 OpenSSH是一个用于安全远程登录和文件传输的开源软件套件。它提供了一系列的客户端和服务器程序&#xff0c;包括 ssh、scp、sftp等&#xff0c;用于在网络上进行…

visual studio 生成dll文件以及修改输出dll文件名称操作

目录 visual studio 生成dll文件以及修改dll文件名称一、准备测试代码二、设置导出dll属性三、生成dll文件 .lib .dll .pdb 的简单介绍dll文件使用方式lib文件使用方式1、动态链接 &#xff08;原理&#xff09;2、静态链接&#xff1a; visual studio 生成dll文件以及修改dll文…

基于DiscordMidjourney API接口实现文生图

https://discord.com/api/v9/interactions 请求头&#xff1a; authorization:取自 浏览器中discord 文生图请求头中的 authorization 的值 Content-Type:application/json 请求体&#xff1a; {“type”:2,“application_id”:“93692956130267xxxx”,“guild_id”:“1135900…

Qt Creator中designer使用QWebEngine异常排查

Qt Creator中designer使用QWebEngine异常排查 1、前提背景 最近由于版权的原因&#xff0c;我们采取了自编译的Qt Creator。编译完成之后启动Qt Creator刚开始一切都是很顺利。 但是在Creator中打开designer&#xff0c;使用QWebEngine控件就发生了异常&#xff0c;Qt Creat…

「从零入门推荐系统」22:chatGPT、大模型在推荐系统中的应用

作者 | gongyouliu 编辑 | gongyouliu 提示&#xff1a;全文2.5万字&#xff0c;预计阅读时长2小时&#xff0c;可以先收藏再慢慢阅读。 我们在上一章介绍了chatGPT、大模型的基本概念、核心技术原理等基础知识&#xff0c;有了这些背景知识的铺垫&#xff0c;下面我们来介绍ch…

复现sci顶刊中的画中画(局部细节放大)

简介 小编在撰写学术论文时&#xff0c;为了突出所提模型的优越性&#xff0c;你可以通过放大图形中的局部位置来进行比较。尽管从全局来看&#xff0c;各个方法的拟合效果都还不错&#xff0c;但通过放大图中的特定区域&#xff0c;可以更清楚地展示所提模型相对于其他模型的…

HTML5中Canvas学习笔记:Canvas

目录 一、HTML中Canvas画图strokeStyle 和 fillStyle 的区别是什么&#xff1f; 二、如何设置一幅canvas图中某个颜色透明&#xff1f; 三、H5 canvas中strokeRect参数如果是小数&#xff0c;如何处理&#xff1f; 四、H5 Canvas中如何画圆角矩形框&#xff1f; 一、HTML中…

海外应用商店优化实用指南之关键词

和SEO一样&#xff0c;关键词是ASO中的一个重要因素。就像应用程序标题一样&#xff0c;在Apple App Store和Google Play中处理应用程序关键字的方式也有所不同。 关键词研究。 对于Apple&#xff0c;我们的所有关键词只能获得100个字符&#xff0c;Google Play没有特定的关键…

STL空间配置器入门

STL简介   STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;&#xff0c;从根本上说&#xff0c;STL是一些“容器”的集合&#xff0c;这些“容器”有list,vector,set,map等&#xff0c;STL也是算法和其他一些组件的集合。 谈及组件&#xff0c…

8.5day06 框架基础--反射+注解

文章目录 反射获取类的各种信息获取类的字节码文件 注解元注解 复习redis两道算法题 摆烂了&#xff0c;不想学啦&#xff01;&#xff01;&#xff01; 反射 反射主要用来做框架; 学习内容 获取类的各种信息 第一步 加载类&#xff0c;获取类的字节码文件 第二步 获取类的…

IDEA基础使用

IDEA基础使用 1、IDEA中显示用法和用户截图展示有调用显示无调用显示 对应方法 2、如何找出项目中所有不被调用方法截图展示对应方法 3、常用代码(Code)说明及快捷键:4、未完待续待日后更新。。。总结&#xff1a;欢迎指导&#xff0c;也祝码友们代码越来越棒&#xff0c;技术越…

AMEYA360:瑞萨电子MCU和MPU产品线将支持Microsoft Visual Studio Code

全球半导体解决方案供应商瑞萨电子宣布其客户现可以使用Microsoft Visual Studio Code&#xff08;VS Code&#xff09;开发瑞萨全系列微控制器&#xff08;MCU&#xff09;和微处理器&#xff08;MPU&#xff09;。瑞萨已为其所有嵌入式处理器开发了工具扩展&#xff0c;并将其…

月报总结|Moonbeam 7月份大事一览

炎炎夏日&#xff0c;Moonbeam于越南举办了线下交流会&#xff0c;在EthCC 2023和以太坊社区成员共同讨论多链应用&#xff0c;在Polkadot Decoded中分享了Moonbeam的与众不同之处。 Bear Necessities Hackathon也于本月圆满结束&#xff0c;选出了每个赛道最杰出的项目&#…

CMake:检测python解释器和python库

CMake:检测python解释器和python库 导言检测python解释器CMakeLists.txt输出附录 检测python库项目结构CMakeLists.txt相关源码附录 导言 python是一种非常流行的语言。许多项目用python编写的工具&#xff0c;从而将主程序和库打包在一起&#xff0c;或者在配置或构建过程中使…

MCU的类型和应用领域简介

MCU&#xff08;Microcontroller Unit&#xff09;根据存储器类型可分为无片内ROM型和带片内ROM型。无片内ROM型的芯片需要外接EPROM才能应用&#xff0c;而带片内ROM型则有不同的子类型&#xff0c;如片内EPROM型、MASK片内掩模ROM型和片内Flash型。 MCU还可以按照用途分为通…

Cesium 实战教程 - 调整 3dtiles 倾斜摄影大小

Cesium 实战教程 - 调整 3dtiles 倾斜摄影大小 核心代码完整代码在线示例 之前由于误解遇到一个特殊的需求&#xff1a;想要把三维球上叠加倾斜摄影进行自由放大缩小&#xff0c;跟随地图的缩放进行缩放。 后来经过搜索、尝试&#xff0c;终于实现了需求。 但是&#xff0c;后…

用Abp实现找回密码和密码强制过期策略

用户找回密码&#xff0c;确切地说是重置密码&#xff0c;为了保证用户账号安全&#xff0c;原始密码将不再以明文的方式找回&#xff0c;而是通过短信或者邮件的方式发送一个随机的重置校验码&#xff08;带校验码的页面连接&#xff09;&#xff0c;用户点击该链接&#xff0…

SpringBoot 日志文件

一、日志的作用 日志是程序的重要组成部分&#xff0c;想象一下&#xff0c;如果程序报错了&#xff0c;不让你打开控制台看日志&#xff0c;那么你能找到报错的原因吗 答案是否定的&#xff0c;写程序不是买彩票&#xff0c;不能完全靠猜&#xff0c;因此日志对于我们来说&a…