当前位置: 首页 > news >正文

【BBuf的CUDA笔记】八,对比学习OneFlow 和 FasterTransformer 的 Softmax Cuda实现

0x1. OneFlow/FasterTransformer SoftMax CUDA Kernel 实现学习

这篇文章主要学习了oneflow的softmax kernel实现以及Faster Transformer softmax kernel的实现,并以个人的角度分别解析了原理和代码实现,最后对性能做一个对比方便大家直观的感受到oneflow softmax kernel相比于FasterTransformer的优越性。我目前处于尽可能去理解oneflow的一些优秀的cuda实现的阶段,做一些知识储备,同时也调研和学习下相关的一些优化库的的cuda kernel。欢迎大家关注这个仓库 https://github.com/BBuf/how-to-optim-algorithm-in-cuda 一起交流学习。性能测试结果如下:

在这里插入图片描述

可以看到在各个 seq_len 下,oneflow 的 softmax cuda kernel性能均显著优于 FasterTransformer 的 cuda kernel性能,优化是非常有效的。测试的细节请看下面的 0x4. 性能测试章节。

0x2. OneFlow Softmax

OneFlow 已经有SoftMax的实现介绍:https://zhuanlan.zhihu.com/p/341059988 。我这里会更加详细的介绍其中的代码,最后和 Faster Transformer 的实现做一个对比。

OneFlow 的 softmax 实现被独立到了一个头文件中方便广大开发者使用或者改进,地址为:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/core/cuda/softmax.cuh 。

0x2.1 环境准备

我这里也直接将其搬过来使用并更详细的了解了其中的原理最后测试下性能,需要注意的是oneflow版本的softmax实现依赖了nvidia cub的block reduce,因为要在外面单独运行这个实现需要手动编译下 cub 才可以。接下来展示一下 cub 的完全编译流程:

  1. 构建trust
# Clone Thrust and CUB from Github. CUB is located in Thrust's
# `dependencies/cub` submodule.
git clone --recursive https://github.com/NVIDIA/thrust.git
cd thrust# Create build directory:
mkdir build
cd build# Configure -- use one of the following:
cmake -DTHRUST_INCLUDE_CUB_CMAKE=ON ..   # Command line interface.# Build:
#cmake --build . -j <num jobs>   # invokes make (or ninja, etc)
make -j16# Run tests and examples:
ctest

注意如果 cmake 时出现 Failed to detect a default CUDA architecture. 错误,需要我们自己手动指定一下自己的 nvcc 编译器以及 GPU 架构,也就是在 thrust 的顶层 CMakeLists.txt 加入:

set(CMAKE_CUDA_ARCHITECTURES 80) # 80对应修改为你自己的 GPU 架构
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")
  1. 构建 cub

构建完 thrust 之后我们才可以构建cub。按照上述构建 thrust 的流程走完之后在 thrust/dependencies 这个文件夹下会有 cub 文件夹,我们需要进到这个文件夹里面执行:

$ mkdir build
$ cd build
$ cmake ..
$ make -j16

注意如果这里 cmake 时出现 Failed to detect a default CUDA architecture. 错误,需要我们自己手动指定一下自己的 nvcc 编译器以及 GPU 架构,也就是在 thrust/dependencies/CMakeLists.txt 的顶层加入:

set(CMAKE_CUDA_ARCHITECTURES 80) # 80对应修改为你自己的 GPU 架构
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")

最后在编译 oneflow_softmax.cu 这个源文件时我们需要额外指定一下 cub 的头文件路径:

/usr/local/cuda/bin/nvcc -arch=sm_80 -o bin/oneflow_softmax oneflow_softmax.cu -I/home/xxx/thrust/dependencies/cub/build/headers

0x2.2 优化解读

在 如何实现一个高效的Softmax CUDA kernel?——OneFlow 性能优化分享 (https://zhuanlan.zhihu.com/p/341059988) 这篇文章中已经较为详细的阐述了 OneFlow 的 softmax cuda kernel的优化技巧,我这里就不重复讲解其中的内容了。不过当时阅读这个文章的时候感觉对其中一些代码细节以及用法还有一些疑问,本次我在重新阅读的过程中为 oneflow 的 softmax cuda kernel添加了详细的注释以及用法示例。oneflow softmax cuda kernel要求输入数据的shape为(num_rows, num_cols),然后根据 num_cols 的大小进行分段处理:

oneflow cuda kernel实现中最复杂的就是第一种实现(一个 warp 处理一行或者两行),为了方便感兴趣的读者理解,我为这部分实现添加了详细的注释:https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/softmax/oneflow_softmax.cu#L12-L540 。由于篇幅太长可以自行查看,欢迎交流。

------------------分割线-----------------

当输入数据的列数也就是 1024 < num_cols <= 4096 时,oneflow softmax kernel使用一个 Block 来处理一行,并且借助 Shared Memory 来保存中间的计算结果。第二种实现的代码实现和解释如下:

// 一个 Block 处理一行元素, 利用 BlockAllReduce 完成 Warp 内各线程间的求 Global Max 和 Global Sum 操作。
// BlockAllReduce 是借助 Cub 的 BlockReduce 方法实现的。template<typename LOAD, typename STORE, typename ComputeType, int pack_size, int block_size,Algorithm algorithm>
__global__ void SoftmaxBlockSMemImpl(LOAD load, STORE store, const int64_t rows,const int64_t cols) {extern __shared__ __align__(sizeof(double)) unsigned char shared_buf[];auto* buf = reinterpret_cast<ComputeType*>(shared_buf);const int tid = threadIdx.x;assert(cols % pack_size == 0);const int num_packs = cols / pack_size;// 一个 Block 处理一行元素for (int64_t row = blockIdx.x; row < rows; row += gridDim.x) {// 当前线程的最大值初始化为 -infComputeType thread_max = -Inf<ComputeType>();// 以向量化的方式加载一行数据,然后执行pack reduce操作// 这里的 pack reduce操作我在 https://zhuanlan.zhihu.com/p/596012674 最后一节也有介绍for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];load.template load<pack_size>(pack, row, pack_id * pack_size);
#pragma unrollfor (int i = 0; i < pack_size; ++i) {buf[i * num_packs + pack_id] = pack[i];thread_max = max(thread_max, pack[i]);}}// 执行block reduce获取当前行(由一个 Block 进行处理)的最大值const ComputeType row_max = BlockAllReduce<MaxOp, ComputeType, block_size>(thread_max);ComputeType thread_sum = 0;for (int col = tid; col < cols; col += block_size) {if (algorithm == Algorithm::kSoftmax) {const ComputeType exp_x = Exp(buf[col] - row_max);buf[col] = exp_x;thread_sum += exp_x;} else {const ComputeType x = buf[col] - row_max;buf[col] = x;thread_sum += Exp(x);}}// 同理,获得当前行的sumconst ComputeType row_sum = BlockAllReduce<SumOp, ComputeType, block_size>(thread_sum);// 计算结果并写回到全局内存中for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];
#pragma unrollfor (int i = 0; i < pack_size; ++i) {if (algorithm == Algorithm::kSoftmax) {pack[i] = Div(buf[i * num_packs + pack_id], row_sum);} else if (algorithm == Algorithm::kLogSoftmax) {pack[i] = buf[i * num_packs + pack_id] - Log(row_sum);} else {__trap();}}store.template store<pack_size>(pack, row, pack_id * pack_size);}}
}template<typename LOAD, typename STORE, typename ComputeType, int pack_size, int block_size,Algorithm algorithm>
inline cudaError_t LaunchSoftmaxBlockSMemImpl(cudaStream_t stream, LOAD load, STORE store, int smem,const int64_t rows, const int64_t cols) {constexpr int waves = 32;int grid_dim_x;{cudaError_t err = GetNumBlocks(block_size, rows, waves, &grid_dim_x);if (err != cudaSuccess) { return err; }}SoftmaxBlockSMemImpl<LOAD, STORE, ComputeType, pack_size, block_size, algorithm><<<grid_dim_x, block_size, smem, stream>>>(load, store, rows, cols);return cudaPeekAtLastError();
}// 执行的主体循环逻辑如下,根据 num_cols算出需要的 Shared Memory 大小作为 Launch Kernel 参数,
// 借助 Shared Memory 保存输入,后续计算直接从 Shared Memory 读取。
// 由于 SM 内的 Shared Memory 资源同样有限,因此当 num_cols超过一定范围,kernel 启动时申请 Shared Memory 超过最大限制,
// 就会出现无法启动的问题,因此,仅在调用 cudaOccupancyMaxActiveBlocksPerMultiprocessor 返回值大于0时采用 Shared Memory 方案。
// 此外,需要注意的是,由于 Block 内线程要做同步,当 SM 中正在调度执行的一个 Block 到达同步点时,SM 内可执行 Warp 逐渐减少,
// 若同时执行的 Block 只有一个,则 SM 中可同时执行的 Warp 会在此时逐渐降成0,会导致计算资源空闲,造成浪费,若此时同时有其他 Block 在执行,
// 则在一个 Block 到达同步点时仍然有其他 Block 可以执行。当 block_size 越小时,SM 可同时调度的 Block 越多,因此在这种情况下 block_size 越小越好。
// 但是当在调大 block_size,SM 能同时调度的 Block 数不变的情况下,block_size 应该是越大越好,越大就有越好的并行度。
// 因此代码中在选择 block_size 时,对不同 block_size 都计算了 cudaOccupancyMaxActiveBlocksPerMultiprocessor,若结果相同,使用较大的 block_size。
template<typename LOAD, typename STORE, typename ComputeType, int pack_size, Algorithm algorithm>
inline cudaError_t TryDispatchSoftmaxBlockSMemImplBlockSize(cudaStream_t stream, LOAD load,STORE store, const int64_t rows,const int64_t cols, bool* success) {// 设置4个不同的block_sizeconstexpr int block_size_conf_1 = 128;constexpr int block_size_conf_2 = 256;constexpr int block_size_conf_3 = 512;constexpr int block_size_conf_4 = 1024;// 计算第二种方案需要的共享内存大小const size_t smem = cols * sizeof(ComputeType);int max_active_blocks_conf_1;{// 占用计算器API cudaOccupancyMaxActiveBlocksPerMultiprocessor可以根据 kernel 的 block 大小和共享内存使用情况提供占用率预测。cudaError_t err = cudaOccupancyMaxActiveBlocksPerMultiprocessor(&max_active_blocks_conf_1,SoftmaxBlockSMemImpl<LOAD, STORE, ComputeType, pack_size, block_size_conf_1, algorithm>,block_size_conf_1, smem);if (err != cudaSuccess) { return err; }}if (max_active_blocks_conf_1 <= 0) {*success = false;return cudaSuccess;}// ... 省略了一部分代码return LaunchSoftmaxBlockSMemImpl<LOAD, STORE, ComputeType, pack_size, block_size_conf_1,algorithm>(stream, load, store, smem, rows, cols);
}

在第二种实现中,需要特别注意的是给 Shared memory 赋值过程中,若采用下面方法,当 pack size=2,每个线程写连续两个4 byte 地址,就会产生 Bank Conflicts。

#pragma unrollfor (int i = 0; i < pack_size; ++i) {buf[pack_id * pack_size * i] = pack[i];thread_max = max(thread_max, pack[i]);}

因此,在实现(2)中,对Shared memory采用了新的内存布局,避免了同一个Warp访问相同bank的不同地址,避免了Bank Conflicts。

#pragma unrollfor (int i = 0; i < pack_size; ++i) {buf[num_packs * i + pack_id] = pack[i];thread_max = max(thread_max, pack[i]);}

我们需要仔细思考一下这里的原因,我们知道使用 shared memory 要特别小心 bank conflict 。实际上,shared memory 是由 32 个 bank 组成的,如下面这张 PPT 所示:

而 bank conflict 指的就是在一个 warp 内,有2个或者以上的线程访问了同一个 bank 上不同地址的内存。比如:

当 pack_size=1,每个线程连续写4个字节时,每个warp刚好完整访问shared memory的一行,这个时候并不会出现bank conflict。而当pack_size=2时,每个线程写连续2个4字节时(可以看成8个字节),这个时候以0号warp为例,0号线程访问的地址在第0和第1个 bank,1号线程访问的地址在第2和第3个 bank,以此类推,16号线程访问地址又在第0和第1个 bank内,和0号线程访问了同一个bank的不同地址,此时即产生了 Bank Conflicts。实际上这里的连续写就对应了这段代码:

for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];load.template load<pack_size>(pack, row, pack_id * pack_size);
#pragma unrollfor (int i = 0; i < pack_size; ++i) {buf[pack_id * pack_size * i] = pack[i];thread_max = max(thread_max, pack[i]);}}

要避免它产生的bank conflict,就需要对内存的布局进行调整,把它从按行连续写(【num_packs, pack_size】)变成按列的非连续写(【pack_size, num_packs】)。

    for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];load.template load<pack_size>(pack, row, pack_id * pack_size);
#pragma unrollfor (int i = 0; i < pack_size; ++i) {buf[i * num_packs + pack_id] = pack[i];thread_max = max(thread_max, pack[i]);}}

还是当pack_size=2时,每个线程写连续2个4字节时(可以看成8个字节),这个时候以0号warp为例,0号线程访问的地址总是0号 bank,1号线程访问的地址在总是1号 bank,以此类推,现在这种数据排布方法并不会产生 Bank Conflicts。

------------------分割线-----------------

oneflow softmax kernel的最后一种实现是针对仍然是一个 Block 处理一行元素,不同的是,不再用 Shared Memory 缓存输入x,而是在每次计算时重新读输入 x,这种实现没有最大 num_cols的限制,可以支持任意大小。

此外,需要注意的是,在这种实现中,block_size 应该设越大越好,block_size 越大,SM 中能同时并行执行的 Block 数就越少,对 cache 的需求就越少,就有更多机会命中 Cache,多次读x不会多次访问 Global Memory,因此在实际测试中,在能利用 Cache 情况下,有效带宽不会因为读3次x而降低几倍。代码实现如下:

template<typename LOAD, typename STORE, typename ComputeType, int pack_size, int block_size,Algorithm algorithm>
__global__ void SoftmaxBlockUncachedImpl(LOAD load, STORE store, const int64_t rows,const int64_t cols) {const int tid = threadIdx.x;assert(cols % pack_size == 0);const int num_packs = cols / pack_size;for (int64_t row = blockIdx.x; row < rows; row += gridDim.x) {ComputeType thread_max = -Inf<ComputeType>();for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];load.template load<pack_size>(pack, row, pack_id * pack_size);
#pragma unrollfor (int i = 0; i < pack_size; ++i) { thread_max = max(thread_max, pack[i]); }}const ComputeType row_max = BlockAllReduce<MaxOp, ComputeType, block_size>(thread_max);ComputeType thread_sum = 0;for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];load.template load<pack_size>(pack, row, pack_id * pack_size);
#pragma unrollfor (int i = 0; i < pack_size; ++i) { thread_sum += Exp(pack[i] - row_max); }}const ComputeType row_sum = BlockAllReduce<SumOp, ComputeType, block_size>(thread_sum);for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {ComputeType pack[pack_size];load.template load<pack_size>(pack, row, pack_id * pack_size);
#pragma unrollfor (int i = 0; i < pack_size; ++i) {if (algorithm == Algorithm::kSoftmax) {pack[i] = Div(Exp(pack[i] - row_max), row_sum);} else if (algorithm == Algorithm::kLogSoftmax) {pack[i] = (pack[i] - row_max) - Log(row_sum);} else {__trap();}}store.template store<pack_size>(pack, row, pack_id * pack_size);}}
}

第三种实现的代码较为简单,就不仔细添加注释了。

0x3. FasterTransformer Softmax

接下来我们看一下 FasterTransformer 的 softmax kernel实现,这部分的代码实现在:https://github.com/NVIDIA/FasterTransformer/blob/release/v1.0_tag/fastertransformer/cuda/open_attention.cu#L189-L268 。是需要注意的是这里的 kernel 需要传入一个 mask 的输入作用于解码器,这个大家应该都比较熟悉,是 transformer 架构解码器的一个操作,因为解码的时候我们无法看到句子中当前 token 的后续 token。这个地方为了便于对比 oneflow 的 softmax kernel,我在最小实现中屏蔽掉了这个 mask 只对输入做一个裸的 softmax 运算来对比性能。我的最小实现代码位置在:https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/softmax/faster_transformer_softmax.cu 。

这个代码和 oneflow softmax kernel的第三种实现类似,我们先来看一下如何启动FasterTransformer的2种softmax kernel:

// input shape: [bacth_size, head_num, seq_len, seq_len]const int batch_size = 32;const int head_num = 64;const int seq_len = 32;const float scaler = 1.0;const int N = batch_size * head_num * seq_len * seq_len;float* input_host = (float*)malloc(N*sizeof(float));float *input_device;cudaMalloc((void **)&input_device, N*sizeof(float));for (int i = 0; i < N; i++) input_host[i] = 1.0;cudaMemcpy(input_device, input_host, N*sizeof(float), cudaMemcpyHostToDevice);cudaStream_t stream;cudaStreamCreate(&stream);dim3 grid, block;if(seq_len <= 32)block.x = 32;else if(seq_len > 32 && seq_len <= 64)block.x = 64;else if(seq_len > 64 && seq_len <= 128)block.x = 128;else if(seq_len > 128 && seq_len <= 256)block.x = 256;else if(seq_len > 256 && seq_len <= 512)block.x = 512;elseblock.x = 1024;// 如果 batch_size 和 head_num 的乘积 <= 120,block 数量设置为 batch_size * head_num * seq_len,也就是一个 block 负责处理 seq_len 个元素if(batch_size * head_num <= 120){grid.x = batch_size * head_num * seq_len;softmax_kernel_v2<float><<<grid, block, 0, stream>>>(input_device, /*attr_mask*/ batch_size, head_num, seq_len, scaler); }// 否则,block的数量设置为 batch_size * head_num, 也就是一个 block 负责处理 seq_len * seq_len 个元素else{grid.x = batch_size * head_num;softmax_kernel<float><<<grid, block, 0, stream>>>(input_device, /*attr_mask*/ batch_size, head_num, seq_len, scaler); }float *output_host = (float*)malloc(N * sizeof(float));cudaMemcpy(output_host, input_device, N * sizeof(float), cudaMemcpyDeviceToHost);// 1 / 32 = 0.03125for (int i = 0; i < 32; i++){printf("%.5f\n", output_host[i]);}

对于这里的 softmax_kernel 以及 softmax_kernel_v2 它们的输入参数都是一致的:

// 以 BERT 为例
// query, key, value shape: [batch_size, head_num, seq_len, size_per_head]
// qk_buf shape: [batch_size, head_num, seq_len, seq_len]
// attr_mask shape: [seq_len, seq_len]
// scaler: 缩放系数
template <typename T>
__global__
void softmax_kernel(T* qk_buf_, /*const T* attr_mask*/ const int batch_size, const int head_num, const int seq_len, const T scaler){...}

也就是说FasterTransformer里面的softmax kernel需要满足输入数据的shape是 [batch_size, head_num, seq_len, seq_len],然后在最后一个维度上进行 softmax 得到每个句子里面的单词和单词的相似度。

从上面启动 kernel 的代码来看,trick也非常简单,首先根据 seq_len 来选一个合适的 block_size,然后如果 batch_size 和 head_num 的乘积 <= 120,block 数量设置为 batch_size * head_num * seq_len,也就是一个 block 负责处理 seq_len 个元素。否则,block的数量设置为 batch_size * head_num, 也就是一个 block 负责处理 seq_len * seq_len 个元素。kernel的代码实现就是一个经典的 block reduce代码,这里就不再赘述。可以直接查看我上面给出的最小代码的链接:https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/softmax/faster_transformer_softmax.cu 。

0x4. 性能对比

使用ncu在A100 PCIE 40G上进行profile,以FasterTransformer为准,我们挑选如下的几个shape(batch_size, num_heads, seq_len, seq_len)我们只变化 seq_len:

  • (32, 64, 16, 16)
  • (32, 64, 32, 32)
  • (32, 64, 64, 64)
  • (32, 64, 128, 128)
  • (32, 64, 512, 512)

对于 oneflow 的实现来说,我们只需要把 num_rows 设置成 batch_size * num_heads * seq_len ,把 num_cols 设置成 seq_len 即可。接下来我们分别测试下上面这些情况下 oneflow 和 FasterTransformer softmax cuda kernel 的性能表现(此处设定数据类型都为 float ):

seq_len框架耗时(us)
16FasterTransformer26.43
16OneFlow9.66
32FasterTransformer46.40
32OneFlow18.91
64FasterTransformer120.16
64OneFlow59.65
128FasterTransformer430.18
128OneFlow208.93
512FasterTransformer6090
512OneFlow3100

为了更直观,这里画一个图:

在这里插入图片描述

可以看到在各个 seq_len 下,oneflow 的 softmax cuda kernel性能均显著优于 FasterTransformer 的 cuda kernel性能,优化是非常有效的。

0x5. 总结

这篇文章主要学习了oneflow的softmax kernel实现以及Faster Transformer softmax kernel的实现,并以个人的角度分别解析了原理和代码实现,最后对性能做一个对比方便大家直观的感受到oneflow softmax kernel相比于FasterTransformer的优越性。我目前处于尽可能去理解oneflow的一些优秀的cuda实现的阶段,做一些知识储备,同时也调研和学习下相关的一些优化库的的cuda kernel。欢迎大家关注这个仓库 https://github.com/BBuf/how-to-optim-algorithm-in-cuda 一起交流学习。

相关文章:

【BBuf的CUDA笔记】八,对比学习OneFlow 和 FasterTransformer 的 Softmax Cuda实现

0x1. OneFlow/FasterTransformer SoftMax CUDA Kernel 实现学习 这篇文章主要学习了oneflow的softmax kernel实现以及Faster Transformer softmax kernel的实现&#xff0c;并以个人的角度分别解析了原理和代码实现&#xff0c;最后对性能做一个对比方便大家直观的感受到onefl…...

python 类对象的析构释放代码演示

文章目录一、类的构造函数与析构函数二、代码演示1. 引用的更迭2. 只在函数内部的类对象三、函数内部返回的类对象1. 使用全局变量 引用 函数内部的类对象一、类的构造函数与析构函数 init 函数是python 类的构造函数&#xff0c;在创建一个类对象的时候&#xff0c;就会自动调…...

Hadoop Shell常用命令

Hadoop Shell命令在管理HDFS的时候还是比较常用的&#xff0c;Hadoop Shell命令与shell命令极为相似&#xff0c;但是方便查询&#xff0c;在这里总结分享&#xff0c;大家enjoy~~ 1&#xff0c;cat 语法格式&#xff1a;hadoop fs -cat URI [URI …] 含义&#xff1a;将路径…...

Android中级——色彩处理和图像处理

色彩处理 通过色彩矩阵处理 色彩矩阵介绍 图像的RGBA可拆分为一个4行5列的矩阵和5行1列矩阵相乘 其中4行5列矩阵即为ColorMatrix&#xff0c;可通过调整ColorMatrix间接调整RGBA 第一行 abcde 决定新的 R第二行 fghij 决定新的 G第三行 klmno 决定新的 G第四行 pqrst 决定新…...

C++类和对象:类的定义、类对象的存储、this指针

目录 一. 对于面向过程和面向对象的认识 二. 类 2.1 struct关键字定义类 2.1.1 C语言中的struct关键字 2.1.2 C中的struct关键字 2.2 class关键字 2.1 使用class关键字定义类 三. 类的访问限定及封装 3.1 类的访问权限及访问限定符 3.1.1 访问权限 3.1.2 访问限定…...

代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II

一、参考资料不同路径https://programmercarl.com/0062.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1ve4y1x7Eu不同路径 IIhttps://programmercarl.com/0063.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84II.htmlhttps://progr…...

数据库复习3

一. 简答题&#xff08;共1题&#xff0c;100分&#xff09; 1. (简答题) 存在数据库test&#xff0c;数据库中有如下表&#xff1a; 1.学生表 Student(Sno,Sname,Sage,Ssex) --Sno 学号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 主键Sno 2.教师表 Teacher(Tno,Tname) --T…...

顺序表的增删查改

数据结构 是数据存储的方式&#xff0c;对于不同的数据我们要采用不同的数据结构。就像交通运输&#xff0c;选用什么交通工具取决于你要运输的是人还是货物&#xff0c;以及它们的数量。 顺序存储结构 包括顺序表、链表、栈和队列等。 例如腾讯QQ中的好友列表&#xff0c;…...

jupyter matplotlib中文乱码解决

中文乱码可能有两种情况 1. matplotlib里面有中文字体 2. 没有中文字体 查看是否有中文字体: # 查询当前系统所有字体 from matplotlib.font_manager import FontManager import subprocessmpl_fonts = set(f.name for f in FontManager().ttflist)print(all font list get f…...

Smtplib之发邮件模块

目录 创建Smtp对象 Smtp类中的方法 MIME MIMEBase MIMEBase MIMEMultipart MIMEApplication MIMEAudio MIMEImage MIMEText 实例 texthtml格式 发送带图片附件的邮件 发送带附件的邮件 含多种格式 SMTP模块 SMTP 简单传输协议&#xff0c;它是一组用于由源…...

Android 适配手机和平板

一、屏幕适配限定符Android 系统加载应用资源时 , 会根据当前运行应用的设备的相关属性 , 如 : 屏幕尺寸 / 屏幕像素密度 / 宽高比 / 屏幕方向 等属性 , 加载不同的屏幕适配限定符目录下的资源 ;如 : 横竖屏切换时 , res/layout-land 目录中 , 存放的是横屏布局 , res/layout-p…...

时序预测 | MATLAB实现LSTM-SVR(长短期记忆神经网络-支持向量机)时间序列预测

时序预测 | MATLAB实现LSTM-SVR(长短期记忆神经网络-支持向量机)时间序列预测 目录时序预测 | MATLAB实现LSTM-SVR(长短期记忆神经网络-支持向量机)时间序列预测效果一览基本介绍模型介绍LSTM模型SVR模型LSTM-SVR模型程序设计参考资料致谢效果一览 基本介绍 本次运行测试环境MA…...

分阶段构建golang运行环境Dockerfile镜像

在开始这项工作之前大家可以先去看一下docker官方给出关于空镜像scratch的说明&#xff0c;采用官方简单的一句话就是&#xff1a;scratch是一个明确的空图像&#xff0c;特别是对于“从头开始”构建图像。分阶段构建镜像就会用到scratch这个空镜像&#xff0c;这样的好处是可以…...

Vue-cli脚手架在做些什么(源码角度分析)

什么是Vue脚手架&#xff1f;在学习初期&#xff0c;我们的项目往往需要借助webpack、vite等打包工具配置Vue的开发环境&#xff0c;但是在真实开发中我们不可能每个项目从头来完成所有的webpack配置&#xff0c;这样显得开发的效率会大大的降低&#xff1b;所有的真实开发中&a…...

【Nginx】|入门连续剧——安装

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《Nginx从入门到超神》 坚持做好每一步&#xff0c;幸运之神自然会降临在你的身上 目录一. &#x1f981; 前言Ⅰ. &#x1f407; 为啥我们要使用Nginx&#xff1f;二. &#x1f981; 搭建流程Ⅰ. &#x1f407; 环境准备Ⅱ. &…...

从0开始学python -38

Python3 面向对象-1 Python从设计之初就已经是一门面向对象的语言&#xff0c;正因为如此&#xff0c;在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。 如果你以前没有接触过面向对象的编程语言&#xff0c;那你可能需要先了解一些面向对…...

算法设计与分析期末考试复习(二)

分治法 将一个难以直接解决的大问题&#xff0c;分割成一些规模较小的相同问题&#xff0c;以便各个击破&#xff0c;分而治之。最好使子问题的规模大致相同。 分解&#xff08;Divide&#xff09;&#xff1a;将一个难以直接解决的大问题&#xff0c;分割成一些规模较小的子…...

九龙证券|4D毫米波雷达成市场新宠,相关概念股大涨,会贡献多少业绩?

近日&#xff0c;4D毫米波雷达成为A股新宠&#xff0c;相关概念股如经纬恒润&#xff08;688326.SH&#xff09;一周内涨幅接近20%&#xff0c;威孚高科&#xff08;000581.SZ&#xff09;5个买卖日内涨幅超越25%。 有音讯称特斯拉将在3月1日投资者活动日会宣告新款Model 3的全…...

Git天天用,不得不看的那些事

作为一个工作两年的开发同学&#xff0c;git是每天都要接触的工具。但IDEA对git的封装已经满足了日常的代码提交需求&#xff0c;所以一直是以点点点的形式进行代码提交与更新&#xff0c;几乎没用命令行提交过&#xff08;现在想来也是有些惭愧&#xff09;&#xff0c;对于gi…...

IDE 文档注释使用,模板注释,ide配置templates

文档注释基于javadoc模板 类注释 /*** 暂无介绍** author admin* version 1.0.0* <dt><span class"simpleTagLabel">时间:</span></dt>* <dd>2023/2/24</dd>*/方法注释 /*** 暂无描述** author admin* param args */javadoc相…...

力扣-查询近30天活跃用户数

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1141. 查询近30天活跃用户数二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.其他总结前言 一、题目&…...

企企通聚源池| 聚合海量资源全网寻源,赋能供采双方撮合交易

目前&#xff0c;我们正处于一个飞速发展的信息时代&#xff0c;随着大数据时代的来临&#xff0c;在企业的日常经营中&#xff0c;数据无处不在&#xff0c;各类数据的采集、整合、分析对企业的发展、决策有着十分重要的作用。数据管理作为企业一项重要的建设工作&#xff0c;…...

【算法数据结构体系篇class09】:链表问题:快慢指针、回文结构、复制、中点,分区、相交

一、链表解题的方法论 1)对于笔试&#xff0c;不用太在乎空间复杂度&#xff0c;一切为了时间复杂度2)对于面试&#xff0c;时间复杂度依然放在第一位&#xff0c;但是一定要找到空间最省的方法二、链表常用数据结构和技巧1&#xff09;使用容器(哈希表、数组等)2&#xff09;快…...

实验室信息化管理行业方案

为适应新时代下的管理机制与应用场景&#xff0c;越来越多的检测实验室需对研发部门和实验部门进行全面的、现代化的、电子化的综合管理&#xff0c;帮助检测机构对实验室的规划与计划、项目立项与管理、项目成果、合同&#xff0c;以及基建等工作进行统一的管理&#xff0c;而…...

docker学习

docker 环境搭建 MySql # mysql5.7 docker run --name mysql10 -p 3306:3306 -v D:\MySql\conf:/etc/mysql/conf.d -v D:\MySql\data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD123456 -d mysql:5.7docker run --name mysql10 -p 3306:3306 -v /etc/mysql/conf.d:/etc/mysql/co…...

Linux 常用命令

重启 # 重启&#xff08;root 用户操作&#xff09; reboot# 强制重启 reboot -f关机 # 关机 # shutdown [OPTION] [TIME] [MESSAGE] shutdown-h 关机 -r 重启-c 取消上一个命令 第二个参数指的是多少分钟后执行操作&#xff0c;以分钟为单位&#xff0c;如果不加时间&am…...

数据结构-顺序表(2)

目录 1. 线性表 2. 顺序表 2.1 动态顺序表 3. 接口实现 前期工作 3.1 初始化、销毁与检查容量 3.1.1 初始化 3.1.2 销毁 3.1.3 检查容量 3.2 尾插 3.3 尾删 3.4 头插 3.5 头删 3.6 插入 3.7 删除 顺序表源码 SeqList.h SeqList.c test.c 写在最后&#xff…...

初学C/C++内存管理--new和delete的使用

一&#xff0c;内存分布 栈区&#xff1a; 一般的局部变量和函数的返回数据以及返回地址&#xff0c;函数的参数都在战栈区上开辟空间。栈区开空间一般由编译器自动管理&#xff0c;出了生命周期自动释放。也可以通过一些方式自己手动开辟栈区空间&#xff0c;不过一般用不到…...

【Java】volatile

一、volatile volatile是Java虚拟机提供的轻量级的同步机制&#xff0c;它有&#xff13;个特性&#xff1a; &#xff11;&#xff09;保证可见性 &#xff12;&#xff09;不保证原子性 &#xff13;&#xff09;禁止指令重排 当写一个volatile变量时&#xff0c;JMM会把该…...

混沌工程 Chaos Mesh 实践经验(持续更新)

使用 k8s JVM故障 Linux内核版本 Linux 系统内核必须为 4.1 及以上版本。 不然会一直失败&#xff0c;可以从Chaos Mesh dashboard前端看到。 对native方法注入故障无效 实测对Thread.sleep(Long) 注入故障无效&#xff0c;猜测是因为对native方法无效&#xff0c;大概因为…...