【CUDA代码实践03】m维网格n维线程块对二维矩阵的索引

文章目录

  • 一、数据存储方式
  • 二、二维网格二维线程块
  • 三、二维网格一维线程块
  • 四、一维网格一维线程块

为了方便下次找到文章,也方便联系我给大家提供帮助,欢迎大家点赞👍、收藏📂和关注🔔!一起讨论技术问题💻,一起学习成长📚!如果你有任何问题或想法,随时留言,我会尽快回复哦😊!


近年来,人工智能(AI)技术,尤其是大模型的快速发展,打开了全新的时代大门。对于想要在这个时代迅速成长并提升自身能力的个人而言,学会利用AI辅助学习已经成为一种趋势。不论是国内的文心一言、豆包,还是国外的ChatGPT、Claude,它们都能成为我们编程学习的有力助手。利用AI进行编程学习将大大提升自己的编程学习效率,这里给大家推荐一个我自己在用的集成ChatGPT和Claude的网站(国内可用,站点稳定):传送门

抓住AI时代每一个机会,加速自己成长,提高自己的核心价值!

一、数据存储方式

  • 数据在内存中是以线性、以行为主的方式存储
  • 本篇文章中,16x8的二维数组,在内存中一段连续的128个地址存储该数组
    在这里插入图片描述
    代码结构
    在这里插入图片描述

先放上头文件及main文件
common.cuh

#ifndef COMMON_CUH
#define COMMON_CUH

#include "cuda_runtime.h"
#include <stdio.h>

// 声明外部函数,它们将在其他文件中实现。
// 这些函数定义了 CUDA 的网格和块结构,分别表示
// 2维网格和2维线程块、2维网格和1维线程块、1维网格和1维线程块。
extern void grid2D_block2D();
extern void grid2D_block1D();
extern void grid1D_block1D();

// ErrorCheck 是一个内联函数,用于检查 CUDA 函数的返回错误码。
// 如果有错误发生,它将打印错误代码、错误名称、错误描述、文件名和行号。
// 此函数的目的是帮助调试 CUDA 错误。
inline cudaError_t ErrorCheck(cudaError_t error_code, const char* filename, int lineNumber) {
    if (error_code != cudaSuccess) {
        printf("CUDA error:\ncode=%d, name=%s, description=%s\nfile=%s,line=%d\n",
               error_code, cudaGetErrorName(error_code), cudaGetErrorString(error_code), filename, lineNumber);
        return error_code;  // 返回错误码以便调用方了解错误情况。
    }
    return error_code;  // 如果没有错误,返回相同的错误码。
}

// setGPU 是一个内联函数,用于设置 GPU 设备。
// 它首先获取系统中可用的 CUDA 兼容 GPU 数量。
// 如果没有找到可用的 GPU,程序将退出,否则设置设备并显示相应信息。
inline void setGPU() {
    int iDeviceCount = 0;  // 存储系统中可用 GPU 的数量
    // 获取设备数量并检查返回的错误码。
    cudaError_t error = ErrorCheck(cudaGetDeviceCount(&iDeviceCount), __FILE__, __LINE__);

    // 如果没有 GPU 或发生错误,则终止程序。
    if (error != cudaSuccess || iDeviceCount == 0) {
        printf("No CUDA compatible GPU found\n");
        exit(-1);  // 返回非零值,表示错误。
    } else {
        printf("The count of GPUs is %d.\n", iDeviceCount);  // 显示找到的 GPU 数量。
    }

    // 设置设备 ID 为 0 的 GPU
    int iDevice = 0;
    error = ErrorCheck(cudaSetDevice(iDevice), __FILE__, __LINE__);
    if (error != cudaSuccess) {
        printf("cudaSetDevice failed!\n");
        exit(-1);  // 设置失败时终止程序。
    } else {
        printf("cudaSetDevice success!\n");  // 成功设置 GPU 后的确认信息。
    }
}

#endif // COMMON_CUH

main.cu

#include "cuda_runtime.h"
#include <stdio.h>
#include "./common.cuh" // 包含自定义的通用 CUDA 工具,例如 setGPU 和 ErrorCheck

int main() {
    // grid2D_block2D();  // 使用 2维网格和 2维线程块的函数,已注释掉
    // grid2D_block1D();  // 使用 2维网格和 1维线程块的函数,已注释掉
    grid1D_block1D();    // 使用 1维网格和 1维线程块的函数,执行矩阵加法

    return 0; // 返回 0 表示程序执行成功
}

二、二维网格二维线程块

二维网格和二维线程块对二维矩阵进行索引,每个线程可负责一个矩阵元素的计算任务

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

//
// Created by Administrator on 2024/10/25.
//
#include "common.cuh"

// 定义一个 CUDA 内核函数 addMatrix,用于对两个矩阵进行元素逐一相加。
// A、B 是输入矩阵,C 是输出矩阵,nx 和 ny 分别是矩阵的列数和行数。
__global__ void addMatrix(int *A, int *B, int *C, const int nx, const int ny) {
    int ix = blockIdx.x * blockDim.x + threadIdx.x; // 确定线程在 x 方向上的索引
    int iy = blockIdx.y * blockDim.y + threadIdx.y; // 确定线程在 y 方向上的索引

    unsigned int idx = iy * nx + ix; // 计算该线程对应矩阵中的一维索引

    // 仅当索引在矩阵范围内时执行加法运算,以避免越界访问
    if (ix < nx && iy < ny) {
        C[idx] = A[idx] + B[idx];
    }
}

// 定义一个函数 grid2D_block2D 来设置并调用 CUDA 内核
// 该函数配置并使用二维网格和二维块结构
void grid2D_block2D(void) {
   setGPU(); // 设置 GPU

   // 初始化矩阵大小和字节数
   int nx = 16; // 列数
   int ny = 8;  // 行数
   int nxy = nx * ny; // 矩阵元素总数
   size_t stBytesCount = nxy * sizeof(int); // 矩阵所需的总字节数

   // 在主机(CPU)上分配内存
   int *ipHost_A, *ipHost_B, *ipHost_C;
   ipHost_A = (int *)malloc(stBytesCount); // 矩阵 A
   ipHost_B = (int *)malloc(stBytesCount); // 矩阵 B
   ipHost_C = (int *)malloc(stBytesCount); // 矩阵 C

   // 初始化 A 和 B 的值,C 初始化为零
   if (ipHost_A != NULL && ipHost_B != NULL && ipHost_C != NULL) {
       for (int i = 0; i < nxy; i++) {
           ipHost_A[i] = i;        // 矩阵 A 的元素值设为 i
           ipHost_B[i] = i + 1;    // 矩阵 B 的元素值设为 i+1
       }
       memset(ipHost_C, 0, stBytesCount); // 矩阵 C 的元素初始化为 0
   }
   else {
       printf("fail to malloc memory.\n");
       exit(-1); // 如果内存分配失败,退出程序
   }

   // 在设备(GPU)上分配内存
   int *ipDevice_A, *ipDevice_B, *ipDevice_C;
   ErrorCheck(cudaMalloc((int **)&ipDevice_A, stBytesCount), __FILE__, __LINE__);
   ErrorCheck(cudaMalloc((int **)&ipDevice_B, stBytesCount), __FILE__, __LINE__);
   ErrorCheck(cudaMalloc((int **)&ipDevice_C, stBytesCount), __FILE__, __LINE__);
   if (ipDevice_A != NULL && ipDevice_B != NULL && ipDevice_C != NULL) {
       // 将 A 和 B 从主机复制到设备
       ErrorCheck(cudaMemcpy(ipDevice_A, ipHost_A, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
       ErrorCheck(cudaMemcpy(ipDevice_B, ipHost_B, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
       ErrorCheck(cudaMemcpy(ipDevice_C, ipHost_C, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
   }
   else {
       printf("fail to malloc memory.\n");
       free(ipHost_A);
       free(ipHost_B);
       free(ipHost_C);
       exit(-1); // 如果设备内存分配失败,退出程序
   }

   // 设置线程块和网格维度
   dim3 block(4, 4); // 定义每个块的尺寸(4x4 线程块)
   dim3 grid((nx + block.x - 1) / block.x, (ny + block.y - 1) / block.y); // 定义网格尺寸
   printf("Thread config : grid (%d, %d) block (%d, %d)\n", grid.x, grid.y, block.x, block.y);

   // 启动 CUDA 内核
   addMatrix<<<grid, block>>>(ipDevice_A, ipDevice_B, ipDevice_C, nx, ny);
   ErrorCheck(cudaDeviceSynchronize(), __FILE__, __LINE__);

   // 将结果从设备复制回主机
   ErrorCheck(cudaMemcpy(ipHost_C, ipDevice_C, stBytesCount, cudaMemcpyDeviceToHost), __FILE__, __LINE__);

   // 输出前 10 个元素的加法结果,验证计算是否正确
   for (int i = 0; i < 10; i++) {
       printf("idx=%2d\tmatrix_A:%d\tmatrix_B:%d\tresult=%d\n", i + 1, ipHost_A[i], ipHost_B[i], ipHost_C[i]);
   }

   // 释放主机和设备上的内存
   free(ipHost_A);
   free(ipHost_B);
   free(ipHost_C);
   ErrorCheck(cudaFree(ipDevice_A), __FILE__, __LINE__);
   ErrorCheck(cudaFree(ipDevice_B), __FILE__, __LINE__);
   ErrorCheck(cudaFree(ipDevice_C), __FILE__, __LINE__);
   ErrorCheck(cudaDeviceReset(), __FILE__, __LINE__);

   return;
}

三、二维网格一维线程块

二维网格和一维线程块对二维矩阵进行索引
每个线程可负责一个矩阵元素的计算任务
与二维网格二维线程块的情况极为相似

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

//
// Created by Administrator on 2024/10/25.
//
#include "common.cuh"

// 定义一个 CUDA 内核函数 addMatrix_21D,用于执行矩阵相加操作。
// 与标准的 2维线程块不同,此内核使用 2维网格和 1维线程块配置。
// A、B 是输入矩阵,C 是输出矩阵,nx 和 ny 分别是矩阵的列数和行数。
__global__ void addMatrix_21D(int *A, int *B, int *C, const int nx, const int ny)
{
  int ix = blockIdx.x * blockDim.x + threadIdx.x; // 计算线程在 x 方向上的索引
  int iy = threadIdx.y;                           // 线程在 y 方向上的索引

  unsigned int idx = iy * nx + ix; // 将二维索引转换为一维索引

  // 仅在索引位于矩阵范围内时执行加法操作,避免越界访问
  if (ix < nx && iy < ny)
  {
    C[idx] = A[idx] + B[idx];
  }
}

// 定义一个函数 grid2D_block1D 来配置并调用 CUDA 内核。
// 该函数使用二维网格和一维块的配置。
void grid2D_block1D(void)
{
  setGPU(); // 设置 GPU 设备

  // 设置矩阵的尺寸
  int nx = 16; // 矩阵列数
  int ny = 8;  // 矩阵行数
  int nxy = nx * ny; // 矩阵总元素数
  size_t stBytesCount = nxy * sizeof(int); // 矩阵所需的总字节数

  // 在主机(CPU)上分配内存
  int *ipHost_A, *ipHost_B, *ipHost_C;
  ipHost_A = (int *)malloc(stBytesCount); // 分配矩阵 A 的内存
  ipHost_B = (int *)malloc(stBytesCount); // 分配矩阵 B 的内存
  ipHost_C = (int *)malloc(stBytesCount); // 分配结果矩阵 C 的内存

  // 初始化矩阵 A 和 B 的数据
  if(ipHost_A != NULL && ipHost_B != NULL && ipHost_C != NULL){
     for(int i = 0; i < nxy; i++){
       ipHost_A[i] = i;        // A 的每个元素为 i
       ipHost_B[i] = i + 1;    // B 的每个元素为 i+1
     }
  } else {
     printf("fail to malloc memory.\n");
     exit(-1); // 如果内存分配失败,退出程序
  }

  // 在设备(GPU)上分配内存
  int *ipDevice_A, *ipDevice_B, *ipDevice_C;
  ErrorCheck(cudaMalloc((int **)&ipDevice_A, stBytesCount), __FILE__, __LINE__);
  ErrorCheck(cudaMalloc((int **)&ipDevice_B, stBytesCount), __FILE__, __LINE__);
  ErrorCheck(cudaMalloc((int **)&ipDevice_C, stBytesCount), __FILE__, __LINE__);
  if(ipDevice_A != NULL && ipDevice_B != NULL && ipDevice_C != NULL){
    // 将主机内存复制到设备
    ErrorCheck(cudaMemcpy(ipDevice_A, ipHost_A, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
    ErrorCheck(cudaMemcpy(ipDevice_B, ipHost_B, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
    ErrorCheck(cudaMemcpy(ipDevice_C, ipHost_C, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
  } else {
    // 如果分配失败,释放内存并退出程序
    free(ipHost_A);
    free(ipHost_B);
    free(ipHost_C);
    exit(-1);
  }

  // 定义线程块和网格的尺寸
  dim3 block(4); // 每个块有 4 个线程
  dim3 grid((nx + block.x - 1) / block.x, (ny + block.y - 1) / block.y); // 配置网格尺寸
  printf("Thread config: grid (%d, %d), block(%d, %d).\n", grid.x, grid.y, block.x, block.y);

  // 启动 CUDA 内核
  addMatrix_21D<<<grid, block>>>(ipDevice_A, ipDevice_B, ipDevice_C, nx , ny);
  ErrorCheck(cudaDeviceSynchronize(), __FILE__, __LINE__);

  // 将结果从设备复制回主机
  ErrorCheck(cudaMemcpy(ipHost_C, ipDevice_C, stBytesCount, cudaMemcpyDeviceToHost), __FILE__, __LINE__);

  // 输出前 10 个元素的加法结果,进行验证
  for(int i = 0; i < 10; i++){
    printf("idx=%2d\tmatrix_A:%d\tmatrix_B:%d\tresult=%d\n", i + 1, ipHost_A[i], ipHost_B[i], ipHost_C[i]);
  }

  // 释放主机和设备上的内存
  free(ipHost_A);
  free(ipHost_B);
  free(ipHost_C);
  ErrorCheck(cudaFree(ipDevice_A), __FILE__, __LINE__);
  ErrorCheck(cudaFree(ipDevice_B), __FILE__, __LINE__);
  ErrorCheck(cudaFree(ipDevice_C), __FILE__, __LINE__);
  ErrorCheck(cudaDeviceReset(), __FILE__, __LINE__);
  return;
}

四、一维网格一维线程块

一维网格和一维线程块对二维矩阵进行索引
每个线程负责矩阵一列的运算
编写核函数时,需要使用循环

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

//
// Created by Administrator on 2024/10/28.
//
#include "common.cuh"

// 定义一个 CUDA 内核函数 addMatrix_11D,使用 1D 网格和 1D 块来执行矩阵相加。
// A、B 是输入矩阵,C 是输出矩阵,nx 和 ny 分别是矩阵的列数和行数。
__global__ void addMatrix_11D(int *A, int *B, int *C, const int nx, const int ny)
{
  int ix = blockIdx.x * blockDim.x + threadIdx.x; // 计算线程在 x 方向的索引

  // 确保索引在矩阵范围内
  if (ix < nx)
  {
    // 在 y 方向上循环遍历
    for (int iy = 0; iy < ny; iy++)
    {
      unsigned int idx = iy * nx + ix; // 将二维索引转换为一维索引
      C[idx] = A[idx] + B[idx]; // 将 A 和 B 对应位置相加并存储在 C 中
    }
  }
}

// 定义一个函数 grid1D_block1D 来配置并调用 CUDA 内核,使用 1D 网格和 1D 块
void grid1D_block1D(void)
{
  printf("grid1D_block1D\n");
  setGPU(); // 设置 GPU 设备

  // 定义矩阵的尺寸
  int nx = 16; // 矩阵的列数
  int ny = 8;  // 矩阵的行数
  int nxy = nx * ny; // 矩阵的总元素数
  size_t stBytesCount = nxy * sizeof(int); // 矩阵所需的总字节数

  // 在主机(CPU)上分配内存
  int *ipHost_A, *ipHost_B, *ipHost_C;
  ipHost_A = (int *)malloc(stBytesCount); // 分配矩阵 A 的内存
  ipHost_B = (int *)malloc(stBytesCount); // 分配矩阵 B 的内存
  ipHost_C = (int *)malloc(stBytesCount); // 分配结果矩阵 C 的内存

  // 初始化矩阵 A 和 B 的数据
  if(ipHost_A != NULL && ipHost_B != NULL && ipHost_C != NULL){
     for(int i = 0; i < nxy; i++){
       ipHost_A[i] = i;       // 矩阵 A 的元素设为 i
       ipHost_B[i] = i + 1;   // 矩阵 B 的元素设为 i+1
     }
  } else {
     printf("fail to malloc memory.\n");
     exit(-1); // 如果分配失败,退出程序
  }

  // 在设备(GPU)上分配内存
  int *ipDevice_A, *ipDevice_B, *ipDevice_C;
  ErrorCheck(cudaMalloc((int **)&ipDevice_A, stBytesCount), __FILE__, __LINE__);
  ErrorCheck(cudaMalloc((int **)&ipDevice_B, stBytesCount), __FILE__, __LINE__);
  ErrorCheck(cudaMalloc((int **)&ipDevice_C, stBytesCount), __FILE__, __LINE__);
  if(ipDevice_A != NULL && ipDevice_B != NULL && ipDevice_C != NULL){
    // 将主机内存复制到设备
    ErrorCheck(cudaMemcpy(ipDevice_A, ipHost_A, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
    ErrorCheck(cudaMemcpy(ipDevice_B, ipHost_B, stBytesCount, cudaMemcpyHostToDevice), __FILE__, __LINE__);
  } else {
    // 如果分配失败,释放内存并退出程序
    free(ipHost_A);
    free(ipHost_B);
    free(ipHost_C);
    exit(-1);
  }

  // 设置线程块和网格的尺寸
  dim3 block(4); // 每个块包含 4 个线程
  dim3 grid((nx + block.x - 1) / block.x); // 设置 1D 网格的维度
  printf("Thread config: grid (%d, %d), block(%d, %d).\n", grid.x, grid.y, block.x, block.y);

  // 启动 CUDA 内核
  addMatrix_11D<<<grid, block>>>(ipDevice_A, ipDevice_B, ipDevice_C, nx , ny);
  ErrorCheck(cudaDeviceSynchronize(), __FILE__, __LINE__);

  // 将结果从设备复制回主机
  ErrorCheck(cudaMemcpy(ipHost_C, ipDevice_C, stBytesCount, cudaMemcpyDeviceToHost), __FILE__, __LINE__);

  // 输出前 10 个元素的加法结果,验证计算正确性
  for(int i = 0; i < 10; i++){
    printf("idx=%2d\tmatrix_A:%d\tmatrix_B:%d\tresult=%d\n", i + 1, ipHost_A[i], ipHost_B[i], ipHost_C[i]);
  }

  // 释放主机和设备上的内存
  free(ipHost_A);
  free(ipHost_B);
  free(ipHost_C);
  ErrorCheck(cudaFree(ipDevice_A), __FILE__, __LINE__);
  ErrorCheck(cudaFree(ipDevice_B), __FILE__, __LINE__);
  ErrorCheck(cudaFree(ipDevice_C), __FILE__, __LINE__);
  ErrorCheck(cudaDeviceReset(), __FILE__, __LINE__);
  return;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

低功耗4G模组:FTP应用示例

一、FTP 概述 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09; 是 TCP/IP 协议组中的协议之一。 FTP协议包括两个组成部分&#xff0c;其一为FTP服务器&#xff0c;其二为FTP客户端。 其中FTP服务器用来存储文件&#xff0c;用户可以使用FTP客户…

鸿蒙UI开发——基于组件安全区方案实现沉浸式界面

1、概 述 本文是接着上篇文章 鸿蒙UI开发——基于全屏方案实现沉浸式界面 的继续讨论。除了全屏方案实现沉浸式界面外&#xff0c;我们还可以使用组件安全区的方案。 当我们没有使用setWindowLayoutFullScreen()接口设置窗口为全屏布局时&#xff0c;默认使用的策略就是组件安…

智慧税务管理:金融企业报税效率与合规性提升

前言 在数字化浪潮席卷全球的今天&#xff0c;金融行业正面临前所未有的挑战与机遇。如何在复杂的税务环境中保持合规并提高效率&#xff0c;已成为每个金融企业的重中之重。今天小编就为大家介绍一下如何通过借助智能税务平台&#xff0c;实现税务管理的智能化革新&#xff0…

Docker 常用命令全解析:提升对雷池社区版的使用经验

Docker 常用命令解析 Docker 是一个开源的容器化平台&#xff0c;允许开发者将应用及其依赖打包到一个可移植的容器中。以下是一些常用的 Docker 命令及其解析&#xff0c;帮助您更好地使用 Docker。 1. Docker 基础命令 查看 Docker 版本 docker --version查看 Docker 运行…

华为OD机试 - 无向图染色(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

Python+pandas读取Excel将表头为键:对应行为值存为字典—再转json

目录 专栏导读1、库的介绍2、库的安装3、核心代码4、方法1:5、方法2总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自…

摸鱼小工具-窗口隐藏透明

摸鱼小工具-窗口隐藏透明 介绍 就很简单的一个工具&#xff0c;鼠标移上去显示&#xff0c;鼠标离开就透明。具体看图。 源码以及下载地址

vue封装信号强度

图标下载链接: https://pan.baidu.com/s/1828AidkCKU1KTkw1SvBwQg?pwd4k7n 共五格信号 信号5为绿色&#xff0c;信号4为绿色&#xff0c;信号3为黄色&#xff0c;信号2为黄色&#xff0c;信号1为红色&#xff0c;信号0为灰色。 子组件 /components/SignalStrength/index.vu…

使用常数指针作为函数参数

在main.cpp里输入程序如下&#xff1a; #include <iostream> //使能cin(),cout(); #include <iomanip> //使能setbase(),setfill(),setw(),setprecision(),setiosflags()和resetiosflags(); //setbase( char x )是设置输出数字的基数,如输出进制数则用setbas…

简易了解Pytorch中的@ 和 * 运算符(附Demo)

目录 1. 基本知识2. 3. * 1. 基本知识 在 PyTorch 中&#xff0c; 和 * 运算符用于不同类型的数学运算&#xff0c;具体是矩阵乘法和逐元素乘法 基本知识 运算符功能适用场景示例矩阵乘法&#xff08;或点乘&#xff09;用于执行线性代数中的矩阵乘法C A B&#xff0c;其中…

VulkanTutorial(8·Shader modules)

Shader modules 与早期的API不同&#xff0c;Vulkan中的着色器代码必须以字节码格式指定&#xff0c;而不是人类可读的语法&#xff0c;如GLSL和HLSL。这种字节码格式称为SPIR-V它是一种可用于编写图形和计算着色器的格式 使用像SPIR-V这样简单的字节码格式&#xff0c;不会面…

读数据工程之道:设计和构建健壮的数据系统23批量获取的考虑因素

1. 批量获取的考虑因素 1.1. 批量获取&#xff0c;通常是获取数据的一种便捷方式 1.1.1. 通过从源系统中抽取一个数据子集&#xff0c;根据时间间隔或累积数据的大小来获取数据 1.2. 基于时间间隔的批量获取在传统ETL的数据仓库中很普遍 1.2.1. 每天在非工作时间&#xff0…

Cyber​​Panel upgrademysqlstatus 远程命令执行漏洞(QVD-2024-44346)

0x01 产品简介 CyberPanel是一个开源的Web控制面板,它提供了一个用户友好的界面,用于管理网站、电子邮件、数据库、FTP账户等。CyberPanel旨在简化网站管理任务,使非技术用户也能轻松管理自己的在线资源。 0x02 漏洞概述 该漏洞源于upgrademysqlstatus接口未做身份验证和…

【形态学 - 击中-击不中变换(很多都讲得不直观不清楚,甚至是错的,我来个通俗易懂的)】

简单描述过程&#xff1a; 一般的目标匹配是&#xff0c;知道目标长什么样&#xff0c;用这个模板去匹配。这里还知道目标周围环境长什么样。 如何把环境的信息加进来用来帮助匹配呢。这个就是击中-击不中联合匹配了。 就是用亮图去匹配目标。 再用暗图去匹配背景。 两个联合起…

【蓝桥杯选拔赛真题78】python电话号码 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python电话号码 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python电话号码 第十五届蓝桥杯青少年组python比赛选拔赛真题 一、题目要…

单细胞数据分析(四):细胞亚型注释

文章目录 介绍加载R包导入数据细胞簇可视化细胞簇标记基因细胞识别输出结果系统信息介绍 单细胞细胞亚型注释是指在单细胞聚类分析后,对每个聚类得到的细胞群体进行生物学意义上的分类和识别的过程。这一步骤的目的是为了确定每个细胞群体对应的具体细胞类型或状态,从而更好…

CI/CD 的原理

一、CI/CD 的概念 CI/CD是一种软件开发流程&#xff0c;旨在通过自动化和持续的集成、测试和交付实现高质量的软件产品。 CI(Continuous Integration)持续集成 目前主流的开发方式是协同开发&#xff0c;即多位开发人员同事处理同意应用不同模块或功能。 如果企业在同一时间将…

常见大气校正模型及6S模型安装部署【20241028】

⛄常见大气校正模型 大气校正是遥感图像标准化处理的重要环节&#xff0c;消除太阳辐射传输过程中大气对于遥感图像的影响&#xff0c;提高影像的清晰度&#xff0c;获取地物真实的光谱信息。由于大气条件较为复杂&#xff0c;且随区域地理分布和观测时间是动态变化的&#xf…

map 和 set 的使用

文章目录 一.序列式容器和关联式容器二. set 系列的使用1. set 和 multiset 参考文档2. set 类介绍3. set 的构造和迭代器4. set 的增删查5. insert 和迭代器遍历使用样例6. find 和 erase 使用样例7. multiset 和 set 的差异 三. map 系列的使用1. map 和 multimap参考文档2. …

区块链可投会议CCF A--ICDE 2025 截止11.25 附2024录用数

Conference&#xff1a; IEEE International Conference on Data Engineering (ICDE) CCF level&#xff1a;CCF A Categories&#xff1a;Database/Data Mining/Content Retrieval Year&#xff1a;2025 Conference time&#xff1a; May 19-23, 2025 录用数&#xff1a;…