CUDA-共享内存法实现矩阵乘法(比常规方案提速一倍)

作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

共享内存是什么?

       共享内存是在多个处理单元之间共享数据的一种内存区域。在计算机体系结构中,共享内存通常指的是多个处理器核心或线程之间可以直接访问的内存区域。这些处理单元可以是在同一片硅芯片上的多个处理器核心,也可以是在多个硬件设备上运行的并行线程。

       共享内存在并行计算中扮演着重要的角色,因为它允许多个处理单元之间快速共享数据,而无需通过显式的通信机制。这种直接的共享机制可以大大提高并行计算的效率和性能。

       在GPU编程中,共享内存通常是在GPU的多个线程之间共享的硬件缓存区域。每个线程块(block)内的线程可以访问共享内存中存储的数据,并且这些数据对于同一个线程块内的所有线程都是可见的。共享内存通常具有更高的带宽和更低的访问延迟,因此可以用来加速数据的读取和写入操作。

       共享内存的使用可以提高并行计算的性能,并减少通信开销,特别是在需要大量数据共享的情况下,如矩阵乘法、图像处理等算法中。因此,共享内存是许多并行计算框架和编程模型中的重要组成部分。

常规算法

       在讲解共享内存法之前先回顾常规算法,常规算法通过分配区block和线程thread,使矩阵计算过程拆解,进而达到并行目的。

// 常规算法的核
__global__ void vectorMulGPU(const float* a, const float* b, float* c, int size)
{int row = blockIdx.y * blockDim.y + threadIdx.y;int col = blockIdx.x * blockDim.x + threadIdx.x;if (row < size && col < size){float sum = 0.0f;for (int k = 0; k < size; ++k){sum += a[row * size + k] * b[k * size + col];}c[row * size + col] = sum;}
}

共享内存法

       矩阵计算中(row0,col0)的值,等于前一个矩阵对应行row0和后一个矩阵对应列col0的数据点乘之和;同理,在(row0,col1)的计算中,又读取了一次row0的数据信息;那么如何将这两个读取类似数据的过程进行优化,便是共享内存法的意义所在。

       下面是共享内存法的核函数。

// 共享内存法的核
__global__ void vectorMulGPU2(const float* a, const float* b, float* c, int size)
{__shared__ float as[TILE_WIDTH][TILE_WIDTH];__shared__ float bs[TILE_WIDTH][TILE_WIDTH];// 索引int bx = blockIdx.x;int by = blockIdx.y;int tx = threadIdx.x;int ty = threadIdx.y;// 行列int row = by * TILE_WIDTH + ty;int col = bx * TILE_WIDTH + tx;// 遍历不同块float temp = 0.0;for (int k = 0; k < size / TILE_WIDTH; k++){// 在同一块中的不同线程会计算各自数据,并存在共享存储中互相使用as[ty][tx] = a[row * size + k * TILE_WIDTH + tx];bs[ty][tx] = b[(k * TILE_WIDTH + ty) * size + col];__syncthreads(); // 等待块中其他线程同步// 将对应数据计算for (int m = 0; m < TILE_WIDTH; m++)temp += as[ty][m] * bs[m][tx];__syncthreads(); // 等待其他线程,如果不加这一句,部分线程中的as和bs会被后续循环的计算覆盖,进而导致结果错误}c[row * size + col] = temp;
}

       逐步解释代码:

  1. vectorMulGPU2是一个 GPU 的核函数,用于执行向量相乘操作。它接受三个参数:两个输入向量 ab,一个输出向量 c,以及向量的大小 size

  2. asbs这两行定义了共享内存中的两个二维数组,用于存储输入向量的部分数据。

  3. 接下来是一系列的变量定义和计算,其中涉及到了线程、块、行、列等概念的索引计算,以及关于如何在输入矩阵中定位数据的计算。

  4. for (int k = 0; k < size / TILE_WIDTH; k++)这个循环迭代地处理输入向量,将其分割成大小为 TILE_WIDTH 的小块。

  5. 在循环中,首先将当前处理的小块数据从输入向量 ab 中加载到共享内存中的 asbs 数组中,每个线程负责加载一部分数据。

  6. __syncthreads()这是一个同步函数,用于确保所有线程都已经加载完数据,才能继续执行下一步计算。

  7. 然后,在加载完数据后,每个线程会计算部分结果,并将其叠加在 temp 变量中,这个过程利用了共享内存中的数据。

  8. 再次使用同步函数确保所有线程都完成了计算,以免后续的数据加载覆盖了当前线程的计算结果。

  9. 最后,将计算得到的结果写入到输出向量 c 中。

       相较常规算法,共享内存法速度之所以提升,在于如下几点:

  1. 共享内存(Shared Memory):在CUDA编程中,共享内存是每个线程块共享的内存空间,它的读写速度比全局内存快得多。这段代码中使用了__shared__修饰符定义了asbs两个二维数组,用于存储每个线程块所需的部分输入数据。

  2. 矩阵块计算(Matrix Tile Computation):这段代码将整个矩阵分割成小块,每个线程块负责处理一个小块的计算。这样做的好处是利用了共享内存的局部性原理,减少了全局内存的访问次数,提高了内存访问效率。

  3. 线程同步(Thread Synchronization):在每个矩阵块计算完毕后,使用__syncthreads()函数进行线程同步,确保所有线程都完成了对共享内存的写入操作,然后再进行下一步的计算。这样可以避免数据竞争和不一致性。

  4. 数据重用(Data Reuse):由于每个线程块的计算结果都存储在共享内存中,可以重复利用这些数据进行后续计算,减少了对全局内存的访问次数,提高了数据重用率。

  5. 数据局部性(Data Locality):通过共享内存和矩阵块计算,每个线程块都在处理相邻的数据片段,这样可以提高数据局部性,减少了数据的跨越和访问延迟。

改进共享内存法

       如果你观察细致,那你必定会注意到如果矩阵行列数不是分块尺寸的整数倍,则上述代码便会出错,它会读取不在原计算序列中的数据,进而导致计算错误。假设矩阵是64*64,那单列或者单行可以分块为4个16,刚刚好,如果矩阵是70*70,那除了4个16外,还会余出6个数据没有参与运算。

       接下来对算法进行改进。

// 共享内存法(改进)的核
__global__ void vectorMulGPU3(const float* a, const float* b, float* c, int size)
{__shared__ float as[TILE_WIDTH][TILE_WIDTH];__shared__ float bs[TILE_WIDTH][TILE_WIDTH];// 索引int bx = blockIdx.x;int by = blockIdx.y;int tx = threadIdx.x;int ty = threadIdx.y;// 行列int row = by * TILE_WIDTH + ty;int col = bx * TILE_WIDTH + tx;// 遍历不同块float temp = 0.0;for (int k = 0; k < ceil(float(size) / TILE_WIDTH); k++){// 在同一块中的不同线程会计算各自数据,并存在共享存储中互相使用if ((k * TILE_WIDTH + tx) < size && row < size){as[ty][tx] = a[row * size + k * TILE_WIDTH + tx];}else{as[ty][tx] = 0.0f;}if ((k * TILE_WIDTH + ty) < size && col < size){bs[ty][tx] = b[(k * TILE_WIDTH + ty) * size + col];}else{bs[ty][tx] = 0.0f;}__syncthreads(); // 等待块中其他线程同步if (row < size && col < size){// 将对应数据计算for (int m = 0; m < TILE_WIDTH; m++)temp += as[ty][m] * bs[m][tx];}__syncthreads(); // 等待其他线程,如果不加这一句,部分线程中的as和bs会被后续循环的计算覆盖,进而导致结果错误}if (row < size && col < size){c[row * size + col] = temp;}
}

       改进的几点如下:

  1. 循环终止条件

    • 原算法中,循环终止条件是 k < size / TILE_WIDTH,这意味着它只会遍历足够数量的块以处理整个矩阵。
    • 改进的算法中,循环终止条件是 k < ceil(float(size) / TILE_WIDTH),这使得它处理了多余的块。这样做是为了确保即使矩阵的大小不是块大小的整数倍时,也能正确处理。
  2. 数据加载

    • 在原算法中,数据加载时没有进行边界检查。这可能导致一些线程加载了超出数组边界的数据,造成错误的计算。
    • 改进的算法在加载数据时,会检查当前索引是否超出数组边界,如果超出则将对应元素置为0。这样可以避免超出边界的数据加载,确保计算的正确性。
  3. 结果存储

    • 原算法和改进的算法都会在计算完毕后将结果存储到全局内存中。但是在改进的算法中,存储结果时也进行了边界检查,以避免将结果存储到超出数组边界的位置。
  4. 性能优化

    • 改进的算法中,对于不在矩阵边界内的线程,避免了不必要的计算。这样可以减少不必要的计算量,提高了性能。

C++完整代码

Test.cuh

#ifndef MUL_H
#define MUL_H#define TILE_WIDTH 16void warmupCUDA();
void vectorMulCUDA(const float* a, const float* b, float* c, int N);
void vectorMulCUDA2(const float* a, const float* b, float* c, int N);
void vectorMulCUDA3(const float* a, const float* b, float* c, int N);#endif // MUL_H

Test.cu

#include <vector>
#include <cuda_runtime.h>
#include <iostream>
#include "Test.cuh"// 常规算法的核
__global__ void vectorMulGPU(const float* a, const float* b, float* c, int size)
{int row = blockIdx.y * blockDim.y + threadIdx.y;int col = blockIdx.x * blockDim.x + threadIdx.x;if (row < size && col < size){float sum = 0.0f;for (int k = 0; k < size; ++k){sum += a[row * size + k] * b[k * size + col];}c[row * size + col] = sum;}
}// 共享内存法的核
__global__ void vectorMulGPU2(const float* a, const float* b, float* c, int size)
{__shared__ float as[TILE_WIDTH][TILE_WIDTH];__shared__ float bs[TILE_WIDTH][TILE_WIDTH];// 索引int bx = blockIdx.x;int by = blockIdx.y;int tx = threadIdx.x;int ty = threadIdx.y;// 行列int row = by * TILE_WIDTH + ty;int col = bx * TILE_WIDTH + tx;// 遍历不同块float temp = 0.0;for (int k = 0; k < size / TILE_WIDTH; k++){// 在同一块中的不同线程会计算各自数据,并存在共享存储中互相使用as[ty][tx] = a[row * size + k * TILE_WIDTH + tx];bs[ty][tx] = b[(k * TILE_WIDTH + ty) * size + col];__syncthreads(); // 等待块中其他线程同步// 将对应数据计算for (int m = 0; m < TILE_WIDTH; m++)temp += as[ty][m] * bs[m][tx];__syncthreads(); // 等待其他线程,如果不加这一句,部分线程中的as和bs会被后续循环的计算覆盖,进而导致结果错误}c[row * size + col] = temp;
}// 共享内存法(改进)的核
__global__ void vectorMulGPU3(const float* a, const float* b, float* c, int size)
{__shared__ float as[TILE_WIDTH][TILE_WIDTH];__shared__ float bs[TILE_WIDTH][TILE_WIDTH];// 索引int bx = blockIdx.x;int by = blockIdx.y;int tx = threadIdx.x;int ty = threadIdx.y;// 行列int row = by * TILE_WIDTH + ty;int col = bx * TILE_WIDTH + tx;// 遍历不同块float temp = 0.0;for (int k = 0; k < ceil(float(size) / TILE_WIDTH); k++){// 在同一块中的不同线程会计算各自数据,并存在共享存储中互相使用if ((k * TILE_WIDTH + tx) < size && row < size){as[ty][tx] = a[row * size + k * TILE_WIDTH + tx];}else{as[ty][tx] = 0.0f;}if ((k * TILE_WIDTH + ty) < size && col < size){bs[ty][tx] = b[(k * TILE_WIDTH + ty) * size + col];}else{bs[ty][tx] = 0.0f;}__syncthreads(); // 等待块中其他线程同步if (row < size && col < size){// 将对应数据计算for (int m = 0; m < TILE_WIDTH; m++)temp += as[ty][m] * bs[m][tx];}__syncthreads(); // 等待其他线程,如果不加这一句,部分线程中的as和bs会被后续循环的计算覆盖,进而导致结果错误}if (row < size && col < size){c[row * size + col] = temp;}
}// 预准备过程
void warmupCUDA() 
{float *dummy_data;cudaMalloc((void**)&dummy_data, sizeof(float));cudaFree(dummy_data);
}// 常规算法
void vectorMulCUDA(const float* a, const float* b, float* c, int size)
{float *d_a, *d_b, *d_c;cudaMalloc((void**)&d_a, size * size * sizeof(float));cudaMalloc((void**)&d_b, size * size * sizeof(float));cudaMalloc((void**)&d_c, size * size * sizeof(float));cudaMemcpy(d_a, a, size * size * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_b, b, size * size * sizeof(float), cudaMemcpyHostToDevice);dim3 threadsPerBlock(TILE_WIDTH, TILE_WIDTH);dim3 blocksPerGrid((size + threadsPerBlock.x - 1) / threadsPerBlock.x, (size + threadsPerBlock.y - 1) / threadsPerBlock.y);vectorMulGPU << <blocksPerGrid, threadsPerBlock >> > (d_a, d_b, d_c, size);cudaMemcpy(c, d_c, size * size * sizeof(float), cudaMemcpyDeviceToHost);cudaFree(d_a);cudaFree(d_b);cudaFree(d_c);
}// 共享内存法
void vectorMulCUDA2(const float* a, const float* b, float* c, int size)
{float *d_a, *d_b, *d_c;cudaMalloc((void**)&d_a, size * size * sizeof(float));cudaMalloc((void**)&d_b, size * size * sizeof(float));cudaMalloc((void**)&d_c, size * size * sizeof(float));cudaMemcpy(d_a, a, size * size * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_b, b, size * size * sizeof(float), cudaMemcpyHostToDevice);dim3 threadsPerBlock(TILE_WIDTH, TILE_WIDTH);dim3 blocksPerGrid((size + threadsPerBlock.x - 1) / threadsPerBlock.x, (size + threadsPerBlock.y - 1) / threadsPerBlock.y);vectorMulGPU2 << <blocksPerGrid, threadsPerBlock >> > (d_a, d_b, d_c, size);cudaMemcpy(c, d_c, size * size * sizeof(float), cudaMemcpyDeviceToHost);cudaFree(d_a);cudaFree(d_b);cudaFree(d_c);
}// 共享内存法改进
void vectorMulCUDA3(const float* a, const float* b, float* c, int size)
{float *d_a, *d_b, *d_c;cudaMalloc((void**)&d_a, size * size * sizeof(float));cudaMalloc((void**)&d_b, size * size * sizeof(float));cudaMalloc((void**)&d_c, size * size * sizeof(float));cudaMemcpy(d_a, a, size * size * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_b, b, size * size * sizeof(float), cudaMemcpyHostToDevice);dim3 threadsPerBlock(TILE_WIDTH, TILE_WIDTH);dim3 blocksPerGrid((size + threadsPerBlock.x - 1) / threadsPerBlock.x, (size + threadsPerBlock.y - 1) / threadsPerBlock.y);vectorMulGPU3 << <blocksPerGrid, threadsPerBlock >> > (d_a, d_b, d_c, size);cudaMemcpy(c, d_c, size * size * sizeof(float), cudaMemcpyDeviceToHost);cudaFree(d_a);cudaFree(d_b);cudaFree(d_c);
}

main.cpp

#include <iostream>
#include <vector>
#include <time.h>
#include "Test.cuh"using namespace std;void vectorMulCPU(const std::vector<float>& a, const std::vector<float>& b, std::vector<float>& c, int size)
{
#pragma omp parallel forfor (int i = 0; i < size; ++i){for (int j = 0; j < size; ++j){float sum = 0.0f;for (int k = 0; k < size; ++k){sum += a[i * size + k] * b[k * size + j];}c[i * size + j] = sum;}}
}int main()
{// 乘法测试const int N = 1000; // 矩阵大小std::vector<float> a0(N * N, 0.0f);std::vector<float> b0(N * N, 0.0f);std::vector<float> c0(N * N, 0.0f);for (int i = 0; i < N; ++i){for (int j = 0; j < N; ++j){a0[i * N + j] = float(rand() % 255);b0[i * N + j] = float(rand() % 255);}}std::vector<float> a1 = a0;std::vector<float> b1 = b0;std::vector<float> c1 = c0;std::vector<float> a2 = a0;std::vector<float> b2 = b0;std::vector<float> c2 = c0;std::vector<float> a3 = a0;std::vector<float> b3 = b0;std::vector<float> c3 = c0;// CPU矩阵乘法clock_t sc, ec;sc = clock();vectorMulCPU(a0, b0, c0, N);ec = clock();cout << "mul CPU time:" << float(ec - sc) / 1000 << endl;// 准备工作bool flag = true;clock_t sw, ew;sw = clock();warmupCUDA();ew = clock();cout << "warmup time:" << float(ew - sw) / 1000 << endl;// GPU矩阵乘法clock_t sg1, eg1;sg1 = clock();vectorMulCUDA(a1.data(), b1.data(), c1.data(), N);eg1 = clock();cout << "mul GPU time:" << float(eg1 - sg1) / 1000 << endl;// 检查结果for (int i = 0; i < N * N; ++i){if (c0[i] != c1[i]){std::cerr << "mul GPU Error: Incorrect result at index " << i << std::endl;flag = false;break;}}// GPU矩阵乘法2clock_t sg2, eg2;sg2 = clock();vectorMulCUDA2(a2.data(), b2.data(), c2.data(), N);eg2 = clock();cout << "mul GPU2 time:" << float(eg2 - sg2) / 1000 << endl;// 检查结果for (int i = 0; i < N * N; ++i){if (c0[i] != c2[i]){std::cerr << "mul GPU2 Error: Incorrect result at index " << i << std::endl;flag = false;break;}}// GPU矩阵乘法3clock_t sg3, eg3;sg3 = clock();vectorMulCUDA3(a3.data(), b3.data(), c3.data(), N);eg3 = clock();cout << "mul GPU3 time:" << float(eg3 - sg3) / 1000 << endl;// 检查结果for (int i = 0; i < N * N; ++i){if (c0[i] != c3[i]){std::cerr << "mul GPU3 Error: Incorrect result at index " << i << std::endl;flag = false;break;}}// 输出结果if (flag){std::cout << "successful!" << std::endl;}else{std::cout << "error!" << std::endl;}return 0;
}

测试效果

       在测试代码中有一段是warmup,这是GPU预准备过程,用于执行一些额外开销,如果不进行这一步直接运行后续算法,则会耗时变长,干扰判断。

       相较常规算法,共享内存法提升了约1倍,而改进的共享内存法面对矩阵1000*1000的计算,得到了正确的结果,共享内存法提示错误。如果你把矩阵尺寸改为16的整数倍,则共享内存法也可以顺利通过。

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

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

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

相关文章

S型曲线的几种设计(图像对比度调节)

一般来讲&#xff0c;图像调色模块都会提供“曲线”工具&#xff0c;这是一个极其灵活的功能&#xff0c;绝大部分的调色都可以通过该工具实现&#xff0c;但是曲线功能的交互相对而言比较复杂。出于简便性和效率方面的考量&#xff0c;调色模块往往还会提供一些具有很强的功能…

最后一块石头的重量 II ,目标和,一和0

最后一块石头的重量 II&#xff08;0-1背包问题 将石头尽可能分为两堆重量一样的&#xff0c;进行相撞则为0 class Solution {public int lastStoneWeightII(int[] stones) {int sum0;for(int x:stones){sumx;}int targetsum/2;int[] dpnew int[target1];//dp[j]表示最大石堆的…

AI视频教程下载:学会用AI创作文本图片音频视频

在不断发展的科技领域&#xff0c;人工智能 (AI) 是毋庸置疑的冠军&#xff0c;它是一种不断创新的力量&#xff0c;在我们的生活中扮演着越来越重要的角色。随着 2023 年的到来&#xff0c;我们诚挚地欢迎您加入人工智能精通课程的大门。 这不仅仅是一个课程&#xff0c;它专为…

springboot(3.2.5)初步集成MinIO(8.5.9)开发记录

springboot初步集成MinIO开发记录 说明一&#xff1a;引入maven依赖二&#xff1a;手动注入minioClient三&#xff1a;创建service类四&#xff1a;测试打印连接信息五&#xff1a;时区转化工具类六&#xff1a;常用操作演示 说明 这里只是作者开发的记录&#xff0c;已备将来…

UDP通讯的demo

udp通讯的demo&#xff0c;这个只是简单的实现。 后面我还会加入udp组播功能。 因为懒&#xff0c;所以我自己发&#xff0c;自己接收了。 经过测试&#xff0c;可以看到&#xff0c;发送消息和接收消息功能都没问题。 广播&#xff1a; 这个是点对点的通过对方的ip和端口发…

47.Redis学习笔记

小林coding -> 图解redis的学习笔记 文章目录 Rediswindwos安装docker安装redis启动redis使用RDM访问虚拟机中的redispython连接redis缓存穿透、击穿、雪崩基本数据类型高级数据类型高并发指标布隆过滤器分布式锁Redis 的有序集合底层为什么要用跳表&#xff0c;而不用平衡…

【数据结构】闲谈A股实时交易的数据结构-队列

今天有点忙&#xff0c;特意早起&#xff0c;要不先写点什么。看到个股的红红绿绿&#xff0c; 突然兴起&#xff0c;要不写篇文章分析下A股交易的简易版数据结构。 在A股实时股票交易系统中&#xff0c;按照个人理解&#xff0c;大致会用队列来完成整个交易。队列&#xff08;…

2024高校网络安全管理运维赛wp

文章目录 misc签到钓鱼邮件识别easyshellSecretDBGatewayzipApachef for r webphpsqlMessy Mongo misc 签到 钓鱼邮件识别 两部分解base64&#xff0c;各一个flag 后面没有什么地方有有用信息了&#xff0c;根据题目钓鱼邮件&#xff0c;可能第三段flag就跟DMARC、DKIM 和 SP…

c#实现音乐的“vip播放功能”

文章目录 前言1. c#窗体2. 功能3. 具体实现3.1 添加文件3.2 音乐播放3.3 其他功能 4. 整体代码和窗口5. 依赖的第三方库 前言 最近在QQ音乐里重温周杰伦的歌&#xff0c;觉得好听到耳朵怀孕&#xff0c;兴起想要下载下来反复听&#xff0c;发现QQ音乐VIP歌曲下载下来的格式居然…

基于SSM的“游戏交易网站”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“游戏交易网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 游戏交易网站功能结构图 游戏交易网站首页 游戏交易网站用户注册…

Android iw 工具

代码位置:Android/external/iw 查看支持的命令: console:/ # iw help Usage: iw [options] command Options:--debug enable netlink debugging--version show version (4.1) Commands:help [command]Print usage for all or a specific command, e.g."…

人工智能|机器学习——强大的 Scikit-learn 可视化让模型说话

一、显示 API 简介 使用 utils.discovery.all_displays 查找可用的 API。 Sklearn 的utils.discovery.all_displays可以让你看到哪些类可以使用。 from sklearn.utils.discovery import all_displays displays all_displays() displays Scikit-learn (sklearn) 总是会在新版本…

无人零售,重塑购物新纪元

在这个快节奏的时代&#xff0c;科技的每一次跃进都在悄无声息地改变着我们的生活方式。而今&#xff0c;无人零售正以雷霆之势&#xff0c;颠覆传统购物模式&#xff0c;为我们带来前所未有的便捷与智能体验。想知道无人零售如何彻底改变我们的购物方式吗&#xff1f;跟随我&a…

市场营销的酒店营销策略研究意义

在市场经济条件下&#xff0c;市场营销策略已成为企业经营管理中最重要的组成部分&#xff0c;其在企业管理中的地位日益显现出来。 然而&#xff0c;由于酒店营销环境的特殊性&#xff0c;酒店营销策略研究一直是咱们从业者研究的热点之一。 对于酒店营销策略的研究&#xf…

uts插件开发-继uniapp原生插件nativeplugins,uts插件开发可直接操作原生安卓sdk等,支持uniappx,支持源码授权价格等等

1.创建uts项目 2.创建uts插件cf-takepic 3.在index.uts中编写原生安卓代码&#xff0c;首先定义一个函数方法&#xff0c;在页面中看是否可引用成功 uts函数代码 /*** 拍照函数*/ export const takepicfunction():void{console.log("11111111") } index.vue代码 …

详细分析Mybatis与MybatisPlus中分页查询的差异(附Demo)

目录 前言1. Mybatis2. MybatisPlus3. 实战 前言 更多的知识点推荐阅读&#xff1a; 【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09;java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09; 本章节主要以Demo为例&#xff…

C# winform 连接mysql数据库(navicat)

1.解决方案资源管理器->右键->管理NuGet程序包->搜索&#xff0c; 安装Mysql.Data 2.解决方案资源管理器->右键->添加->引用->浏览-> C:\Program Files (x86)\MySQL\MySQL Installer for Windows ->选择->MySql.Data.dll 3.解决方案资源管理器…

深入剖析Tomcat(七) 日志记录器

在看原书第六章之前&#xff0c;一直觉得Tomcat记日志的架构可能是个“有点东西”的东西。在看了第六章之后呢&#xff0c;额… 就这&#xff1f;不甘心的我又翻了翻logback与新版tomcat的源码&#xff0c;额…&#xff0c;日志架构原来也没那么神秘。本篇文章先过一遍原书内容…

Github 2024-05-07 开源项目日报 Tp10

根据Github Trendings的统计,今日(2024-05-07统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目4Jupyter Notebook项目2Python项目1Batchfile项目1非开发语言项目1Java项目1HTML项目1C#项目1从零开始构建你喜爱的技术 创建周期…

k8s 资源文件参数介绍

Kubernetes资源文件yaml参数介绍 yaml 介绍 yaml 是一个类似 XML、JSON 的标记性语言。它强调以数据为中心&#xff0c;并不是以标识语言为重点例如 SpringBoot 的配置文件 application.yml 也是一个 yaml 格式的文件 语法格式 通过缩进表示层级关系不能使用tab进行缩进&am…