CUDA编程 - 用向量化访存优化 elementwise 核函数 - 学习记录

news2024/9/28 6:27:37

Cuda elementwise

  • 一、简介
    • 1.1、ElementWise
    • 1.2、 float4 - 向量化访存
  • 二、实践
    • 2.1、如何使用向量化访存
    • 2.2、Cuda elementwise - Add
    • 2.3、Cuda elementwise - Sigmoid
      • 2.3.1、简单的 Sigmoid 函数
      • 2.3.2、ElementWise Sigmoid+ float4(向量化访存)
    • 2.4、Cuda elementwise - relu
      • 2.3.1、简单的 relu 函数
      • 2.3.2、ElementWise relu + float4(向量化访存)
  • 三、不能使用向量化访存的情况
    • Cuda elementwise - Histogram(直方图)
  • 四、完整代码
    • 4.1、sigmoid.cu
    • 4.2、relu.cu
  • 4.3、histogram.cu

一、简介

1.1、ElementWise

Element-wise操作是最基础,最简单的一种核函数的类型,它的计算特点很符合GPU的工作方式:对于每个元素单独做一个算术操作,然后直接输出。虽然简单,但深度学习领域,有很多算子都是这个类型。常见的有Add,Mul,Concat,各种激活Sigmoid,Relu及它们的变体,归一化里的BatchNorm都属于这个类型。

1.2、 float4 - 向量化访存

所谓向量化访存,就是一次性读 4 个 float,而不是单单 1 个

要点:

  • 小数据规模情况下,可以不考虑向量化访存的优化方式
  • 大规模数据情况下,考虑使用向量化访存,且 最好是缩小grid的维度为原来的1/4,避免影响Occupancy
  • float4 向量化访存只对数据规模大的时候有加速效果,数据规模小的时候没有加速效果

float4 的性能提升主要在于访存指令减少了(同样的数据规模,以前需要4条指令,现在只需1/4的指令),指令cache里就能存下更多指令,提高指令cache的命中率。

判断是否用上了向量化访存,是在 nsight compute 看生成的SASS代码里会有没有 LDG.E.128 Rx, [Rx.64]或 STG.E.128 [R6.64], Rx这些指令的存在。有则向量化成功,没有则向量化失败。

在这里插入图片描述

官方参考链接1
官方参考链接2

二、实践

2.1、如何使用向量化访存

c :

#define FLOAT4(value)  *(float4*)(&(value))

宏解释:

对于一个值,先对他取地址,然后再把这个地址解释成 float4
对于这个 float4的指针,对它再取一个值
这样编译器就可以一次读四个 float

c++ :

#define FLOAT4(value) (reinterpret_cast<float4*>(&(value))[0])

将核函数写成 float4 的形式的时候,首先要先使用宏定义,其次要注意线程数的变化。

线程数变化原因:因为一个线程可以处理 4个float了,所以要减少 四倍的线程。 (具体看下面的例子)

2.2、Cuda elementwise - Add

参考链接

2.3、Cuda elementwise - Sigmoid

2.3.1、简单的 Sigmoid 函数

a: Nx1, b: Nx1, c: Nx1, c = sigmoid(a, b)

//sigmoid<<<CeilDiv(N, block_size), block_size>>>(d_A, d_B, N)
//
__global__ void sigmoid(float* x, float* y, int N) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx < N) y[idx] = 1.0f / (1.0f + expf(-x[idx]));
}

2.3.2、ElementWise Sigmoid+ float4(向量化访存)

Sigmoid x: N, y: N y=1/(1+exp(-x)) float4

__global__ void sigmoid_vec4(float4* x, float4* y, int N) {
  int idx = (blockIdx.x * blockDim.x + threadIdx.x) * 4;
  if (idx < N) {
    float4 tmp_x = FLOAT4(x[idx]);
    float4 tmp_y;
    tmp_y.x = 1.0f / (1.0f + expf(-tmp_x.x));
    tmp_y.y = 1.0f / (1.0f + expf(-tmp_x.y));
    tmp_y.z = 1.0f / (1.0f + expf(-tmp_x.z));
    tmp_y.w = 1.0f / (1.0f + expf(-tmp_x.w));
    FLOAT4(y[idx]) = tmp_y;
  }
}

2.4、Cuda elementwise - relu

2.3.1、简单的 relu 函数

Relu x: N, y: N y=max(0,x)

__global__ void relu(float* x, float* y, int N) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx < N) y[idx] = fmaxf(0.0f, x[idx]);
}

2.3.2、ElementWise relu + float4(向量化访存)

Relu x: N, y: N y=max(0,x) float4

__global__ void relu_float4(float* x, float* y, int N) {
  int idx = (blockIdx.x * blockDim.x + threadIdx.x) * 4;
  if (idx < N) {
    float4 tmp_x = FLOAT4(x[idx]);
    float4 tmp_y;
    tmp_y.x = fmaxf(0.0f, tmp_x.x);
    tmp_y.y = fmaxf(0.0f, tmp_x.y);
    tmp_y.z = fmaxf(0.0f, tmp_x.z);
    tmp_y.w = fmaxf(0.0f, tmp_x.w);
    FLOAT4(y[idx]) = tmp_y;
  }
}

三、不能使用向量化访存的情况

Cuda elementwise - Histogram(直方图)

x[i] = i
        -
      - -
    - - -
  - - - -
- - - - -

goal: 统计每个元素出现的次数

①、简单的 Histogram函数

x: Nx1, y: count histogram

__global__ void histogram(int* x, int* y, int N) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx < N) atomicAdd(&(y[x[idx]]), 1);
}

其中 int atomicAdd(int* address, int val);

1、atomicAdd 是 CUDA 中的一个原子加函数,用于实现在多个线程同时修改同一个全局变量的情况下,保证数据一致性和正确性
2、该函数会将原始值和 val 相加,并将结果存储在 address 所指向的内存位置,同时返回原始值
3、当多个线程同时调用 atomicAdd 函数时,CUDA 会自动为它们创建一个硬件级的同步访问机制,从而避免了数据竞争和数据不一致性的问题。

参考链接

②、ElementWise Histogram + float4(向量化访存)

__global__ void histogram_float4(int* x, int* y, int N) {
  int idx = 4 * (blockIdx.x * blockDim.x + threadIdx.x);
  if (idx < N) {
    int4 tmp_y = INT4(x[idx]);
    atomicAdd(&(y[tmp_y.x]), 1);
    atomicAdd(&(y[tmp_y.y]), 1);
    atomicAdd(&(y[tmp_y.z]), 1);
    atomicAdd(&(y[tmp_y.w]), 1);
  }
}

运行 histogram_float4 后发现比 histogram 还慢。为什么呢?

1、nvidia 官方回答: Can float4 be used for atomicAdd efficiently?
2、histogram的写入(load)没有向量化指令,只有读取(load)向量化。
3、使用 nsight compute 看生成的SASS,只有 LDG ,没有STG
在这里插入图片描述
4、在一个warp里相邻线程对全局内存y的访问没有合并( 因为在一个warp里面不同线程对全局内存是跳着访问的 )

四、完整代码

4.1、sigmoid.cu

#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <vector>
#include<assert.h>
#include <algorithm>
#include <cublas_v2.h>
#include <cuda_runtime.h>

#define FLOAT4(value)  *(float4*)(&(value))

#define checkCudaErrors(func)               \
{                                   \
    cudaError_t e = (func);         \
    if(e != cudaSuccess)                                        \
        printf ("%s %d CUDA: %s\n", __FILE__,  __LINE__, cudaGetErrorString(e));        \
}

__global__ void relu(float* x, float* y, int N){
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if(idx < N) y[idx] = fmaxf(0.0f, x[idx]);
}

__global__ void relu_float4(float* x, float* y, int N){

    int idx = (blockIdx.x * blockDim.x + threadIdx.x) * 4;
    if(idx < N){
        float4 tmp_x = FLOAT4(x[idx]);
        float4 tmp_y;
        tmp_y.x = fmaxf(0.0f, tmp_x.x);
        tmp_y.y = fmaxf(0.0f, tmp_x.y);
        tmp_y.z = fmaxf(0.0f, tmp_x.z);
        tmp_y.w = fmaxf(0.0f, tmp_x.w);
        FLOAT4(y[idx]) = tmp_y;
    }
}

//nvcc -o sigmoid sigmoid.cu && ./sigmoid
//sigmoid<<<CeilDiv(N, block_size), block_size>>>(d_A, d_B, N)
//a: Nx1, b: Nx1, c: Nx1, c = sigmoid(a, b)
__global__ void sigmoid(float* a, float* b, int N) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx < N) b[idx] = 1.0f / (1.0f + expf(-a[idx]));
}

__global__ void sigmoid_float4(float* a, float* b, int N)
{
    int idx = (blockDim.x * blockIdx.x + threadIdx.x) * 4;
    if(idx < N){
        float4 tmp_a = FLOAT4(a[idx]);
        float4 tmp_b;
        tmp_b.x = 1.0f /(1.0f + expf(-tmp_a.x));
        tmp_b.y = 1.0f /(1.0f + expf(-tmp_a.y));
        tmp_b.z = 1.0f /(1.0f + expf(-tmp_a.z));
        tmp_b.w = 1.0f /(1.0f + expf(-tmp_a.w));
        FLOAT4(b[idx]) = tmp_b;
    }
}

template <typename T> 
inline T CeilDiv(const T& a, const T& b) {
    return (a + b - 1) / b;
}

int main(){

    size_t block_size = 128;
    size_t N = 1 * 1024;
    size_t bytes_A = sizeof(float) * N;
    size_t bytes_B = sizeof(float) * N;

    float* h_A = (float*)malloc(bytes_A);
    float* h_B = (float*)malloc(bytes_B);

    for( int i = 0; i < N; i++ ){
        h_A[i] = (i / 666) * ((i % 2 == 0) ? 1: -1);
    }

    float* d_A;
    float* d_B;

    checkCudaErrors(cudaMalloc(&d_A, bytes_A));
    checkCudaErrors(cudaMalloc(&d_B, bytes_B));

    checkCudaErrors(cudaMemcpy( d_A, h_A, bytes_A, cudaMemcpyHostToDevice));

    cudaEvent_t start, stop;
    checkCudaErrors(cudaEventCreate(&start));
    checkCudaErrors(cudaEventCreate(&stop));
    float msec = 0;
    int iteration = 1000;
    checkCudaErrors(cudaEventRecord(start));
    for(int i = 0; i < iteration; i++)
    {
        //sigmoid<<<CeilDiv(N, block_size), block_size>>>(d_A, d_B, N);
        //sigmoid_float4<<<CeilDiv(N, block_size), block_size/4>>>(d_A, d_B, N);
        sigmoid_float4<<<CeilDiv(N/4,block_size), block_size>>>(d_A, d_B, N);
    }

    checkCudaErrors(cudaEventRecord(stop));
    checkCudaErrors(cudaEventSynchronize(stop));
    checkCudaErrors(cudaEventElapsedTime(&msec, start, stop));
    printf("sigmoid takes %.3f msec\n", msec/iteration);
    checkCudaErrors(cudaMemcpy(h_B, d_B, bytes_B, cudaMemcpyDeviceToHost));

    for(int i = 0; i < N; i++){
        double err = fabs(h_B[i] - 1.0f/(1.0f + expf(-h_A[i])));
        if(err > 1.e-6) {
            printf("wrong answer!\n");
            break;
        }
    }
    
    cudaFree(d_A);
    cudaFree(d_B);

    free(h_A);
    free(h_B);

    return 0;
}

编译和运行代码:

nvcc -o sigmoid sigmoid.cu
./sigmoid 

4.2、relu.cu

#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <vector>
#include<assert.h>
#include <algorithm>
#include <cublas_v2.h>
#include <cuda_runtime.h>

#define FLOAT4(value)  *(float4*)(&(value))

#define checkCudaErrors(func)               \
{                                   \
    cudaError_t e = (func);         \
    if(e != cudaSuccess)                                        \
        printf ("%s %d CUDA: %s\n", __FILE__,  __LINE__, cudaGetErrorString(e));        \
}

//nvcc -o relu relu.cu && ./relu
//sigmoid<<<CeilDiv(N, block_size), block_size>>>(d_A, d_B, N)
//a: Nx1, b: Nx1, c: Nx1, y = relu(x)
__global__ void relu(float* x, float* y, int N){
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if(idx < N) y[idx] = fmaxf(0.0f, x[idx]);
}

__global__ void relu_float4(float* x, float* y, int N){

    int idx = (blockIdx.x * blockDim.x + threadIdx.x) * 4;
    if(idx < N){
        float4 tmp_x = FLOAT4(x[idx]);
        float4 tmp_y;
        tmp_y.x = fmaxf(0.0f, tmp_x.x);
        tmp_y.y = fmaxf(0.0f, tmp_x.y);
        tmp_y.z = fmaxf(0.0f, tmp_x.z);
        tmp_y.w = fmaxf(0.0f, tmp_x.w);
        FLOAT4(y[idx]) = tmp_y;
    }
}


template <typename T> 
inline T CeilDiv(const T& a, const T& b) {
    return (a + b - 1) / b;
}


int main(){

    size_t block_size = 128;
    size_t N = 1 * 1024;
    size_t bytes_A = sizeof(float) * N;
    size_t bytes_B = sizeof(float) * N;

    float* h_A = (float*)malloc(bytes_A);
    float* h_B = (float*)malloc(bytes_B);

    for( int i = 0; i < N; i++ ){
        h_A[i] = (i / 666) * ((i % 2 == 0) ? 1: -1);
    }

    float* d_A;
    float* d_B;

    checkCudaErrors(cudaMalloc(&d_A, bytes_A));
    checkCudaErrors(cudaMalloc(&d_B, bytes_B));

    checkCudaErrors(cudaMemcpy( d_A, h_A, bytes_A, cudaMemcpyHostToDevice));

    cudaEvent_t start, stop;
    checkCudaErrors(cudaEventCreate(&start));
    checkCudaErrors(cudaEventCreate(&stop));
    float msec = 0;
    int iteration = 1000;
    checkCudaErrors(cudaEventRecord(start));
    for(int i = 0; i < iteration; i++)
    {
        //relu<<<CeilDiv(N, block_size), block_size>>>(d_A, d_B, N);
        //relu_float4<<<CeilDiv(N, block_size), block_size/4>>>(d_A, d_B, N);
        relu_float4<<<CeilDiv(N/4, block_size), block_size>>>(d_A, d_B, N);
    }

    checkCudaErrors(cudaEventRecord(stop));
    checkCudaErrors(cudaEventSynchronize(stop));
    checkCudaErrors(cudaEventElapsedTime(&msec, start, stop));
    printf("relu takes %.3f msec\n", msec/iteration);
    checkCudaErrors(cudaMemcpy(h_B, d_B, bytes_B, cudaMemcpyDeviceToHost));

    for(int i = 0; i < N; i++){
        double err = fabs(h_B[i] - fmaxf(0.0f, h_A[i]));

        if(err > 1.e-6) {
            printf("wrong answer!\n");
            break;
        }
    }
    
    cudaFree(d_A);
    cudaFree(d_B);

    free(h_A);
    free(h_B);

    return 0;
}

编译和运行代码:

nvcc -o relu relu.cu
./relu

4.3、histogram.cu

#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <vector>
#include<assert.h>
#include <algorithm>
#include <cublas_v2.h>
#include <cuda_runtime.h>

#define INT4(value) *(int4*)(&(value))
#define FLOAT4(value)  *(float4*)(&(value))

#define checkCudaErrors(func)               \
{                                   \
    cudaError_t e = (func);         \
    if(e != cudaSuccess)                                        \
        printf ("%s %d CUDA: %s\n", __FILE__,  __LINE__, cudaGetErrorString(e));        \
}


/*
x[i] = i
        -
      - -
    - - -
  - - - -
- - - - -
考虑一个warp里相邻线程对全局内存y的访问是否合并(coalesced global access)
warp thread[0]: 0, 1, 2, 3, 
warp thread[1]: 4, 5, 6, 7
*/
__global__ void histogram(int* x, int* y, int N){

    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if(idx < N) atomicAdd(&y[x[idx]], 1);
}

__global__ void histogram_int4(int* x, int* y, int N) {
  int idx = 4 * (blockIdx.x * blockDim.x + threadIdx.x);
  if (idx < N) {
    int4 tmp_y = INT4(x[idx]);
    atomicAdd(&(y[tmp_y.x]), 1);
    atomicAdd(&(y[tmp_y.y]), 1);
    atomicAdd(&(y[tmp_y.z]), 1);
    atomicAdd(&(y[tmp_y.w]), 1);
  }
}

template <typename T> 
inline T CeilDiv(const T& a, const T& b) {
    return (a + b - 1) / b;
}

int main(){

    size_t block_size = 128;
    size_t N = 32 * 1024 * 1024;
    size_t bytes_A = sizeof(int) * N;
    size_t bytes_B = sizeof(int) * N;

    int* h_A = (int*)malloc(bytes_A);
    int* h_B = (int*)malloc(bytes_B);

    for( int i = 0; i < N; i++ ){
        h_A[i] = i;
    }

    int* d_A;
    int* d_B;

    checkCudaErrors(cudaMalloc(&d_A, bytes_A));
    checkCudaErrors(cudaMalloc(&d_B, bytes_B));

    checkCudaErrors(cudaMemcpy( d_A, h_A, bytes_A, cudaMemcpyHostToDevice));

    cudaEvent_t start, stop;
    checkCudaErrors(cudaEventCreate(&start));
    checkCudaErrors(cudaEventCreate(&stop));
    float msec = 0;

    int iteration = 1;
    checkCudaErrors(cudaEventRecord(start));
    for(int i = 0; i < iteration; i++){
        //histogram<<<CeilDiv(N, block_size), block_size>>>(d_A, d_B, N);
        //histogram_int4<<<CeilDiv(N, block_size), block_size/4>>>(d_A, d_B, N);
        histogram_int4<<<CeilDiv(N/4, block_size), block_size>>>(d_A, d_B, N);
    }

    checkCudaErrors(cudaEventRecord(stop));
    checkCudaErrors(cudaEventSynchronize(stop));
    checkCudaErrors(cudaEventElapsedTime(&msec, start, stop));
    printf("histogram takes %.3f msec\n", msec/iteration);

    checkCudaErrors(cudaMemcpy(h_B, d_B, bytes_B, cudaMemcpyDeviceToHost));

    for(int i = 0; i < N; i++){
        //all ones;
        double err = fabs(h_B[i] - iteration * 1.0f);
        if(err > 1.e-6) {
            printf("wrong answer!\n");
            break;
        }
    }

    cudaFree(d_A);
    cudaFree(d_B);

    free(h_A);
    free(h_B);

    return 0;
}

编译和运行代码:

nvcc -o histogram histogram.cu
./histogram

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

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

相关文章

Facebook与社交创新:数字时代的社交构建者

在当今数字化时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。而在这个庞大的社交网络中&#xff0c;Facebook作为其中的巨头之一&#xff0c;不仅扮演着连接人们的桥梁&#xff0c;更是社交创新的领导者和推动者。本文将探讨Facebook在数字时代的社交构建中…

算法打卡day3|链表篇|Leetcode 203.移除链表元素、 707.设计链表 、 206.反转链表

链表基本概念 定义 链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指针域&#xff08;存放指向下一个节点的指针&#xff09;&#xff0c;最后一个节点的指针域指向null&#xff08;空指针的意思&#xff09;。其…

leetcode.无重复字符的最长字串(刷题日记)

自从刷题开始之后&#xff0c;就突然有种感觉。 就是在刷完题之后当时是知道方法了&#xff0c;但是当再次遇到知道就又不会做了&#xff0c;就只好打开解题观摩大佬的代码&#xff0c;你别说&#xff0c;每次都感觉自己是s13。 所以我就想通过写博客来总结一下每次做完新的题…

十一、计算机视觉-膨胀操作

文章目录 前言一、什么是膨胀二、膨胀操作的实现1.引入库 三、膨胀的原理 前言 上节我们学习了腐蚀操作&#xff0c;本节我们讲一下膨胀操作&#xff0c;膨胀和腐蚀实际上是相反的操作。上节我们把云峰这2个字周围没用的像素去掉了&#xff0c;但是云峰这2个字也变细了&#x…

C#,弗洛伊德-瑞文斯特(Floyd-Rivest)算法与源代码

Robert W. Floyd 1 Floyd-Rivest 算法 Floyd-Rivest 算法是一种选择算法&#xff0c;用于在不同元素的数组中找到第k个最小元素。它类似于快速选择算法&#xff0c;但在实际运行中有更好的运行时间。 和 QuickSelect 一样&#xff0c;该算法基于分区的思想工作。对数组进行分…

SINAMICS V90 指导手册 第2章 2.2_系统配套表

V90 PN配套表一共有三张&#xff0c;分别是200V低惯量配套表、400V高惯量配套表和400V带直型连接器的配套表。其中200V电压等级低惯量伺服功率范围从0.05-2kW&#xff0c;额定扭矩从0.16-6.37Nm&#xff0c;电缆长度分别是3m、5m、10m、20m四种型号&#xff1b;400V电压等级带直…

《数据治理简易速速上手小册》第4章 数据安全与合规性(2024 最新版)

文章目录 4.1 数据安全的基本原则4.1.1 基础知识4.1.2 重点案例&#xff1a;在线零售商的数据加密4.1.3 拓展案例 1&#xff1a;医疗机构的访问控制4.1.4 拓展案例 2&#xff1a;金融服务提供商的数据备份和恢复 4.2 遵循数据合规性的策略4.2.1 基础知识4.2.2 重点案例&#xf…

如何在项目中考虑非功能需求

软件的非功能需求指的是除了软件的功能需求以外&#xff0c;软件需要满足的一些其他需求。常见的非功能需求包括&#xff1a; 性能需求&#xff1a;软件需要在特定的时间内完成特定的任务&#xff0c;例如响应时间、吞吐量等。可靠性需求&#xff1a;软件需要在各种环境下都能…

MySQL基础(二)

文章目录 MySQL基础&#xff08;二&#xff09;1. 数据库操作-DQL1.1 介绍1.2 语法1.3 基本查询1.4 条件查询1.5 聚合函数1.6 分组查询1.7 排序查询1.8 分页查询1.9 案例1.9.1 案例一1.9.2 案例二 2. 多表设计2.1 一对多2.1.1 表设计2.1.2 外键约束 2.2 一对一2.3 多对多2.4 案…

电机应用中的大功率电阻器?

在这篇文章中&#xff0c;我们将考虑电机应用中的电阻器。 交流、直流和专用电机用于广泛的应用。一些电机应用相对简单&#xff0c;唯一需要关注的是电机的启动和关闭。在这里&#xff0c;成本、简单性和可靠性是主要问题&#xff0c;而电机控制电阻器是常见的解决方案。 在…

水印相机小程序源码

水印相机前端源码&#xff0c;本程序无需后端&#xff0c;前端直接导入即可&#xff0c;没有添加流量主功能&#xff0c;大家开通后自行添加 源码搜索&#xff1a;源码软件库 注意小程序后台的隐私权限设置&#xff0c;前端需要授权才可使用 真实时间地址拍照记录&#xff0c…

多线程系列(九) -ReentrantLock常用方法详解

一、简介 在上一篇文章中&#xff0c;我们介绍了ReentrantLock类的一些基本用法&#xff0c;今天我们重点来介绍一下ReentrantLock其它的常用方法&#xff0c;以便对ReentrantLock类的使用有更深入的理解。 二、常用方法介绍 2.1、构造方法 ReentrantLock类有两个构造方法&…

(undone) 如何计算 Hessian Matrix 海森矩阵 海塞矩阵

参考视频1&#xff1a;https://www.bilibili.com/video/BV1H64y1T7zQ/?spm_id_from333.337.search-card.all.click 参考视频2&#xff08;正定矩阵&#xff09;&#xff1a;https://www.bilibili.com/video/BV1Ag411M76G/?spm_id_from333.337.search-card.all.click&vd_…

.NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】

设计模式是软件工程中常用的解决特定问题的通用设计方法。它们提供了经过验证的解决方案&#xff0c;可用于解决在软件开发过程中经常遇到的一些常见问题。设计模式不是一种具体的编程语言特性或语法&#xff0c;而是一种通用的设计思想或模板&#xff0c;可以帮助开发人员设计…

Delphi 报错 Type androidx.collection.ArraySet is defined multiple times

Delphi 11 建立一个新的 Multi-Device Application 编译成app的时候报错 报错信息 [PAClient Error] Error: E7688 Unable to execute "E:\Program\Java\jdk1.8.0_301\bin\java.exe" -cp "e:\program\embarcadero\studio\22.0\bin\Android\r8-3.3.28.jar"…

【力扣 - 买卖股票的最佳时机】

题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的…

新的一年,如何优化企业库存管理?

随着社会的发展和经济的不断增长&#xff0c;库存管理成为了企业运营中非常重要的一环。库存作为企业的资产之一&#xff0c;直接影响着企业的盈利能力和竞争优势。因此&#xff0c;对企业库存进行科学的分析和管理&#xff0c;成为了确保企业持续稳定发展的必要手段之一。企业…

lv21 QT 常用控件 2

1 QT GUI 类继承简介 布局管理器 输出控件 输入控件 按钮 容器 2 按钮示例 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QCheckBox> #include <QLineEdit> #include <QPushButton>class Widget : public QWidget {Q_OBJECTpublic…

改善C++程序与设计的55个具体做法——2.尽量以const,enum,inline替换#define

const和#define 这个条款或许改为“宁可以编译器替换预处理器”比较好&#xff0c;因为或许#define不被视为语言的一部分。那正是它的问题所在。当你做出这样的事情&#xff1a; #define ASPECT RATIO 1.653 记号名称ASPECT_RATIO也许从未被编译器看见&#xff1b;也许在编译…

Vue+SpringBoot打造社区买菜系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 数据中心模块2.1.2 菜品分类模块2.1.3 菜品档案模块2.1.4 菜品订单模块2.1.5 菜品收藏模块2.1.6 收货地址模块 2.2 可行性分析2.3 用例分析2.4 实体类设计2.4.1 菜品分类模块2.4.2 菜品档案模块2.4.3…