Attention显存统计与分析

Attention显存估计

简单的Attention函数

import torch
import torch.nn as nn
import einops
class Attention(nn.Module):def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):super().__init__()self.num_heads = num_headshead_dim = dim // num_headsself.scale = qk_scale or head_dim ** -0.5self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)self.attn_drop = nn.Dropout(attn_drop)self.proj = nn.Linear(dim, dim)self.proj_drop = nn.Dropout(proj_drop)def forward(self, x):B, L, C = x.shapeqkv = self.qkv(x)if ATTENTION_MODE == 'flash':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads).float()q, k, v = qkv[0], qkv[1], qkv[2]  # B H L Dx = torch.nn.functional.scaled_dot_product_attention(q, k, v)x = einops.rearrange(x, 'B H L D -> B L (H D)')elif ATTENTION_MODE == 'xformers':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B L H D', K=3, H=self.num_heads)q, k, v = qkv[0], qkv[1], qkv[2]  # B L H Dx = xformers.ops.memory_efficient_attention(q, k, v)x = einops.rearrange(x, 'B L H D -> B L (H D)', H=self.num_heads)elif ATTENTION_MODE == 'math':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads)q, k, v = qkv[0], qkv[1], qkv[2]  # B H L Dattn = (q @ k.transpose(-2, -1)) * self.scaleattn = attn.softmax(dim=-1)attn = self.attn_drop(attn)x = (attn @ v).transpose(1, 2).reshape(B, L, C)else:raise NotImplementedx = self.proj(x)x = self.proj_drop(x)return x
# 设置注意力模式
ATTENTION_MODE = 'math'
# 参数设置
B = 64  # batch size
L = 32  # sequence length
C = 512  # embedding dimension
H = 8  # number of heads
# 创建模型和输入张量
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
initial_memory_1 = torch.cuda.memory_allocated(device)
attention = Attention(dim=C, num_heads=H).to(device)
x = torch.randn(B, L, C).to(device)
# 监控显存使用情况
torch.cuda.reset_peak_memory_stats(device)
initial_memory = torch.cuda.memory_allocated(device)
# 使用 autograd profiler 来记录显存使用情况
with torch.autograd.profiler.profile(profile_memory=True, record_shapes=True) as prof:output = attention(x)
# 计算显存占用ru
final_memory = torch.cuda.memory_allocated(device)
max_memory = torch.cuda.max_memory_allocated(device)
# 打印结果
print(f"Initial Memory_1: {initial_memory_1 / 1024**2:.2f} MB")
print(f"Initial Memory: {initial_memory / 1024**2:.2f} MB")
print(f"Final Memory: {final_memory / 1024**2:.2f} MB")
print(f"Max Memory: {max_memory / 1024**2:.2f} MB")
print(f"Activation Memory: {(final_memory - initial_memory) / 1024**2:.2f} MB")
# 打印详细的显存使用情况
print(prof.key_averages().table(sort_by="self_cuda_memory_usage", row_limit=10))

1 模型占用的显存

两个线性层,
一个是qkv
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
假定嵌入为C,则参数量为
C×(3×C)+(3×C)(偏置项)
一层是线性层
self.proj = nn.Linear(dim, dim)
参数为
C×C+C(偏置项)
总共是 4 ∗ C 2 + 4 ∗ C 4*C^2+4*C 4C2+4C,需要乘以FP32的字节量即4
假定 C = 512,则为 ( 4 ∗ 51 2 2 + 4 ∗ 512 ) ∗ 4 / 1024 / 1024 = 4 M B (4*512^2+4*512)*4/1024/1024=4MB (45122+4512)4/1024/1024=4MB

2 前向过程产生的最大峰值

import torch
import torch.nn as nn
import einops
class Attention(nn.Module):def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):super().__init__()self.num_heads = num_headshead_dim = dim // num_headsself.scale = qk_scale or head_dim ** -0.5self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)self.attn_drop = nn.Dropout(attn_drop)self.proj = nn.Linear(dim, dim)self.proj_drop = nn.Dropout(proj_drop)def forward(self, x):B, L, C = x.shape# 记录显存使用print(f"Before qkv: {torch.cuda.memory_allocated() / 1024**2:.2f} MB")prev_memory = torch.cuda.memory_allocated()qkv = self.qkv(x)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After qkv: {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryif ATTENTION_MODE == 'flash':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads).float()q, k, v = qkv[0], qkv[1], qkv[2]  # B H L Dcurrent_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After rearrange (flash): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryx = torch.nn.functional.scaled_dot_product_attention(q, k, v)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After scaled_dot_product_attention (flash): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryx = einops.rearrange(x, 'B H L D -> B L (H D)')current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After rearrange (flash): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryelif ATTENTION_MODE == 'xformers':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B L H D', K=3, H=self.num_heads)q, k, v = qkv[0], qkv[1], qkv[2]  # B L H Dcurrent_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After rearrange (xformers): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryx = xformers.ops.memory_efficient_attention(q, k, v)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After memory_efficient_attention (xformers): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryx = einops.rearrange(x, 'B L H D -> B L (H D)', H=self.num_heads)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After rearrange (xformers): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryelif ATTENTION_MODE == 'math':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads)q, k, v = qkv[0], qkv[1], qkv[2]  # B H L Dcurrent_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After rearrange (math): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryattn = (q @ k.transpose(-2, -1)) * self.scalecurrent_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After matmul (math): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryattn = attn.softmax(dim=-1)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After softmax (math): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryattn = self.attn_drop(attn)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After dropout (math): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryx = (attn @ v).transpose(1, 2).reshape(B, L, C)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After final matmul and reshape (math): {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryelse:raise NotImplementedprint(f"Before proj: {torch.cuda.memory_allocated() / 1024**2:.2f} MB")x = self.proj(x)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After proj: {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")prev_memory = current_memory  # 更新 prev_memoryprint(f"Before proj_drop: {torch.cuda.memory_allocated() / 1024**2:.2f} MB")x = self.proj_drop(x)current_memory = torch.cuda.memory_allocated()memory_change = (current_memory - prev_memory) / 1024**2print(f"After proj_drop: {current_memory / 1024**2:.2f} MB, Change: {memory_change:.2f} MB")return x
# 设置注意力模式
ATTENTION_MODE = 'math'
# 参数设置
B = 64  # batch size
L = 32  # sequence length
C = 512  # embedding dimension
H = 8  # number of heads
# 创建模型和输入张量
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
initial_memory_1 = torch.cuda.memory_allocated(device)
attention = Attention(dim=C, num_heads=H).to(device)
x = torch.randn(B, L, C).to(device)
# 监控显存使用情况
torch.cuda.reset_peak_memory_stats(device)
initial_memory = torch.cuda.memory_allocated(device)
# 前向传播
output = attention(x)
# 计算显存占用
final_memory = torch.cuda.memory_allocated(device)
max_memory = torch.cuda.max_memory_allocated(device)
# 打印结果
print(f"Initial Memory_1: {initial_memory_1 / 1024**2:.2f} MB")
print(f"Initial Memory: {initial_memory / 1024**2:.2f} MB")
print(f"Final Memory: {final_memory / 1024**2:.2f} MB")
print(f"Max Memory: {max_memory / 1024**2:.2f} MB")
print(f"Activation Memory: {(final_memory - initial_memory) / 1024**2:.2f} MB")

结果如下显示

Before qkv: 8.00 MB
After qkv: 21.00 MB, Change: 13.00 MB
After rearrange (math): 21.00 MB, Change: 0.00 MB
After matmul (math): 31.00 MB, Change: 10.00 MB
After softmax (math): 31.00 MB, Change: 0.00 MB
After dropout (math): 31.00 MB, Change: 0.00 MB
After final matmul and reshape (math): 39.00 MB, Change: 8.00 MB
Before proj: 39.00 MB
After proj: 43.00 MB, Change: 4.00 MB
Before proj_drop: 43.00 MB
After proj_drop: 43.00 MB, Change: 0.00 MB
Initial Memory_1: 0.00 MB
Initial Memory: 8.00 MB
Final Memory: 30.00 MB
Max Memory: 44.00 MB
Activation Memory: 22.00 MB

根据打印语句进行分析

import torch
import torch.nn as nn
import einops
class Attention(nn.Module):def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):super().__init__()self.num_heads = num_headshead_dim = dim // num_headsself.scale = qk_scale or head_dim ** -0.5self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)self.attn_drop = nn.Dropout(attn_drop)self.proj = nn.Linear(dim, dim)self.proj_drop = nn.Dropout(proj_drop)def forward(self, x):B, L, C = x.shapeqkv = self.qkv(x)  # qkv矩阵,此处增加BLC*3if ATTENTION_MODE == 'flash':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads).float() q, k, v = qkv[0], qkv[1], qkv[2]  # B H L D x = torch.nn.functional.scaled_dot_product_attention(q, k, v)x = einops.rearrange(x, 'B H L D -> B L (H D)')elif ATTENTION_MODE == 'xformers':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B L H D', K=3, H=self.num_heads)q, k, v = qkv[0], qkv[1], qkv[2]  # B L H Dx = xformers.ops.memory_efficient_attention(q, k, v)x = einops.rearrange(x, 'B L H D -> B L (H D)', H=self.num_heads)elif ATTENTION_MODE == 'math':qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads) #不变q, k, v = qkv[0], qkv[1], qkv[2]  # B H L D # 不变,索引只是切片的操作attn = (q @ k.transpose(-2, -1)) * self.scale # 此处使用q和k,需要存储中间变量,因此产生 (B,H ,L D)*2的显存,存储结果,产生(B,H,L,L)的显存attn = attn.softmax(dim=-1) #显存不变attn = self.attn_drop(attn) #显存不变x = (attn @ v).transpose(1, 2).reshape(B, L, C) # 使用到v,存储变量,产生(BHLD)的显存,存储结果,产生BLC的显存else:raise NotImplementedx = self.proj(x) # 产生BLC的显存x = self.proj_drop(x)#不变return x
# 设置注意力模式
ATTENTION_MODE = 'math'
# 参数设置
B = 64  # batch size
L = 32  # sequence length
C = 512  # embedding dimension
H = 8  # number of heads
# 创建模型和输入张量
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
initial_memory_1 = torch.cuda.memory_allocated(device)
attention = Attention(dim=C, num_heads=H).to(device)
x = torch.randn(B, L, C).to(device)
# 监控显存使用情况
torch.cuda.reset_peak_memory_stats(device)
initial_memory = torch.cuda.memory_allocated(device)
# 使用 autograd profiler 来记录显存使用情况
with torch.autograd.profiler.profile(profile_memory=True, record_shapes=True) as prof:output = attention(x)
# 计算显存占用ru
final_memory = torch.cuda.memory_allocated(device)
max_memory = torch.cuda.max_memory_allocated(device)
# 打印结果
print(f"Initial Memory_1: {initial_memory_1 / 1024**2:.2f} MB")
print(f"Initial Memory: {initial_memory / 1024**2:.2f} MB")
print(f"Final Memory: {final_memory / 1024**2:.2f} MB")
print(f"Max Memory: {max_memory / 1024**2:.2f} MB")
print(f"Activation Memory: {(final_memory - initial_memory) / 1024**2:.2f} MB")
# 打印详细的显存使用情况
print(prof.key_averages().table(sort_by="self_cuda_memory_usage", row_limit=10))

因此,产生的总显存为
BLC*3(qkv) + BHLD*2(qk算点乘,属于中间变量)+BHLL(atten)+BHLD(v矩阵)+BLC(输出结果)+BLC(线性映射)=BLC*8+BHLL
和其余blog记录的差不多,只不过显存增加的时间点和之前想象的不同

线性层产生显存
1 x = self.proj(x) 产生显存的原因

在这里,self.proj(x) 是一个 线性投影 操作,通常是通过一个线性层(nn.Linear)实现的。这个操作会执行以下几步:

  • 矩阵乘法:假设 self.proj 是一个线性层(如 nn.Linear),它会对输入 x 执行矩阵乘法,计算 x @ W^T + b,其中 W 是权重矩阵,b 是偏置。
  • 新张量的分配:线性变换会生成一个新的张量,并且这个张量的形状通常会与输入 x 不同(例如,x 可能是 (B, L, D),而输出 x 可能是 (B, L, D'))。这个新的张量需要分配新的内存,因此它会产生显存。
2 x = self.proj_drop(x) 不产生显存的原因

self.proj_drop 通常是一个 Dropout 操作。Dropout 是一种正则化技术,在训练时随机地丢弃一部分神经元,以防止过拟合。它不会创建新的张量,而是直接在原有的张量上进行操作。具体来说,Dropout 会在每次前向传播时,对输入张量的部分元素乘以零,但它 不会改变张量的形状或大小

  • 内存共享Dropout 操作不会创建新的张量副本,而是就地修改原始张量。因此,显存消耗不会增加。它只是改变了张量的值(通过乘以0),但并不需要额外的内存。
  • 不产生新张量Dropout 仅仅是通过一个掩码(mask)将某些值屏蔽掉,操作是原地进行的,因此不需要为输出分配新的内存。
3 attn = attn.softmax(dim=-1)attn = self.attn_drop(attn)
  • Softmax 操作attn.softmax(dim=-1) 是对 attn 张量沿着最后一个维度(通常是特征维度)进行 softmax 操作。这个操作是通过对原张量进行逐元素的数值变换(softmax 归一化)来完成的,但它 不需要额外的内存。实际上,它会直接在原始张量上进行操作,因此不会创建新的张量。
  • Dropout 操作self.attn_drop(attn) 也是一个类似的 dropout 操作,它对 attn 张量进行处理,但不会改变张量的形状。和前面的 proj_drop 一样,dropout 不需要新的内存,它只在原始张量上执行修改(通过将一些值置为零)

3 执行结束后保留的激活值

可以看到前向激活值峰值和执行完前向保留的激活值大小不同,上述例子中峰值为34MB,而执行完前向后保留的激活值为22MB
分析哪些释放、哪些保存,需要结合模型的网络结构
总结:保存与释放的变量对比

变量名称状态原因
**一层后产生的激活(qkv) **保存用于反向传播时计算 q, k, v 的梯度。
重排后的 q, k, v释放在计算 attn 和输出后不再需要。
第二层产生的激活( x)保存用于回传梯度到上一层。
最终输出张量 x保存作为前向传播的输出,供后续层使用或反向传播。
q @ k^T保存用于计算梯度(链式法则的一部分)。
所以为12+4+4+2 = 22MB
1 为什么存储attn q@ k^T

经过Softmax,所以需要存储,属于激活值中的一部分

2 为什么不存储分割后的qkv

属于中间变量,不需要存储

4 查看计算图

1 tensorboard查看计算图
import torch
import torch.nn as nn
import einops
from torch.utils.tensorboard import SummaryWriter
import osclass Attention(nn.Module):def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):super().__init__()self.num_heads = num_headshead_dim = dim // num_headsself.scale = qk_scale or head_dim ** -0.5self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)self.attn_drop = nn.Dropout(attn_drop)self.proj = nn.Linear(dim, dim)self.proj_drop = nn.Dropout(proj_drop)def forward(self, x, writer=None, step=None):B, L, C = x.shapeqkv = self.qkv(x)qkv = einops.rearrange(qkv, 'B L (K H D) -> K B H L D', K=3, H=self.num_heads)q, k, v = qkv[0], qkv[1], qkv[2]attn = (q @ k.transpose(-2, -1)) * self.scaleattn = attn.softmax(dim=-1)attn = self.attn_drop(attn)x = (attn @ v).transpose(1, 2).reshape(B, L, C)x = self.proj(x)x = self.proj_drop(x)return x# 设置参数
B, L, C, H = 64, 32, 512, 8
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 确保路径存在
log_dir = "./tensorboard_writer"
if not os.path.exists(log_dir):os.makedirs(log_dir)# 初始化模型和输入张量
attention = Attention(dim=C, num_heads=H).to(device)
x = torch.randn(B, L, C).to(device)# 初始化 TensorBoard
writer = SummaryWriter(log_dir=log_dir)
print("TensorBoard writer initialized.")# 添加计算图和激活值
with torch.no_grad():writer.add_graph(attention, (x,))  # 修正计算图输入attention(x, writer=writer, step=0)  # 记录激活值# 确保数据写入文件
writer.flush()
writer.close()

运行

tensorboard --logdir=./tensorboard_writer --port=6007

查看计算图
在这里插入图片描述

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

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

相关文章

Spring系列之批处理Spring Batch介绍

概述 官网,GitHub A lightweight, comprehensive batch framework designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. 执行流程 实战 假设有个待处理的任务,如文件batch-tes…

[保姆式教程]使用labelimg2软件标注定向目标检测数据和格式转换

定向目标检测是一种在图像或视频中识别和定位对象的同时,还估计它们方向的技术。这种技术特别适用于处理有一定旋转或方向变化的对象,例如汽车、飞机或文本。定向目标检测器的输出是一组旋转的边界框,这些框精确地包围了图像中的对象&#xf…

Socket编程:UDP网络编程项目

目录 一、回显服务器 二、翻译器 三、聊天室 一、回显服务器 项目介绍:使用UDPIPv4协议进行Linux网络编程,实现回显服务器和客户端 功能介绍:客户端发送数据,经过服务端再返回到客户端,输出数据 源代码&#xff1…

HarmonyOS4+NEXT星河版入门与项目实战(24)------Stage模型

文章目录 1、概念2、配置文件1、全局配置文件2、模块配置文件3、UIAbility生命周期1、图文归纳2、生命周期方法入口4、页面生命周期1、图文描述1、概念 2、配置文件 1、全局配置文件 2、模块配置文件 统一修改配置文件技巧:点击任意json 文件,选择 Open editor ,在显示的列表…

torch_geometric使用手册-Heterogeneous Graph Learning(专题三)

大量的现实世界数据集以异构图(Heterogeneous Graph) 的形式存储,这促使了在PyG中引入专门的功能。例如,大多数推荐系统中的图(如社交图)都是异构图,它们存储着关于不同类型的实体及其不同类型关系的信息。本教程介绍了如何将异构图映射到PyG中,以及如何将其作为输入用于…

手机实时提取SIM卡打电话的信令声音-蓝牙电话如何适配eSIM卡的手机

手机实时提取SIM卡打电话的信令声音 --蓝牙电话如何适配eSIM卡的手机 一、前言 蓝牙电话的海外战略中,由于海外智能手机市场中政策的差异性,对内置eSIM卡的手机进行支持是非常合理的需求。Android系列手机中,无论是更换通信运营商&#xf…

QT6学习第五天 第一个QT Quick程序

QT6学习第五天 第一个QT Quick程序 概述创建Qt Quick程序使用Qt资源文件程序发布 概述 如果将程序的用户界面成为前端,程序的数据存储和逻辑业务成为后端,那么传统QT Widgets程序的前后端都是用C完成的。对于现代软件开发而言,前端演化速度远…

LabVIEW内燃机气道试验台测控系统

基于LabVIEW软件开发的内燃机气道试验台测控系统主要应用于内燃机气道的性能测试和数据分析,通过高精度的测控技术,有效提升内燃机的测试精度和数据处理能力。 项目背景 随着内燃机技术的发展,对其气道性能的精准测量需求日益增加。该系统通…

flutter底部导航栏中间按钮凸起,导航栏中间部分凹陷效果

关键代码: Scaffold中设置floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked和extendBody: true, BottomAppBar中设置shape: const CircularNotchedRectangle() Scaffold(extendBody: true,//body是否延伸脚手架底部,在底部导航…

108.【C语言】数据结构之二叉树查找值为x的节点

目录 1.题目 代码模板 2.分析 分类讨论各种情况 大概的框架 关键部分(继续递归)的详解 递归调用展开图 3.测试结果 其他写法 4.结论 5.注意事项 不推荐的写法 1.题目 查找值为x的节点并返回节点的地址 代码模板 typedef int BTDataType; typedef struct BinaryT…

十五、linux之搭建JavaEE环境

1 概述 如果需要在 Linux 下进行 JavaEE 的开发,我们需要安装如下软件 2 安装 JDK 安装步骤 mkdir /opt/jdk mkdir /opt/jdk通过 xftp6 上传到 /opt/jdk 下 cd /opt/jdk 解压 tar -zxvf jdk-8u261-linux-x64.tar.gz tar -zxvf jdk-8u261-linux-x64.tar.…

基于PHP的音乐网站的设计与实现

摘 要 本系统采用PHP编程语言和MySQL数据库技术搭载了Apache服务器,完成了基于PHP的音乐网站设计,通过此次毕 业论文的撰写我明白了对于论文的选题要精确,要明确,要有明确的见解,要有足够的论证和创意,必须…

mysql 触发器进入历史

一、触发器 MySQL 触发器(Triggers)是一种数据库对象,它与表关联,能在特定的事件(如插入、更新或删除)发生时自动执行一些指定的操作。使用触发器可以帮助我们自动维护数据库的完整性、一致性,…

Springboot项目搭建(8)-用户登出与个人中心修改

1.提要信息 1.1 catch和then方法 then和catch是JavaScript中Promise对象的两个方法,用于处理异步操作的成功(成功回调)和失败(失败回调)情况。这两个方法通常与async/await语法一起使用,但也可以单独使用…

【2024】使用Docker搭建redis sentinel哨兵模式集群全流程(包含部署、测试、错误点指正以及直接部署)

目录💻 前言**Docker Compose介绍**最终实现效果 一、搭建集群1、创建文件结构2、创建redis节点3、验证节点4、创建sentinel哨兵5、验证Sentinel功能 二、spring连接1、添加依赖2、添加配置3、启动测试 三、直接部署流程1、拉取配置2、修改端口创建 前言 本篇文章主…

Python毕业设计选题:基于django+vue的智慧社区可视化平台的设计与实现+spider

开发语言:Python框架:djangoPython版本:python3.7.7数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 管理员登录 管理员功能界面 养老机构管理 业主管理 社区安防管理 社区设施管理 车位…

webpack 项目访问静态资源

使用 webpack dev serve 启动 react 项目后,发现无法使用 http://localhost:8080/1.png 访问到项目的 /static 目录下的 1.png 文件。我的 webpack-dev.js 配置如下: const webpack require(webpack) const webpackMerge require(webpack-merge) cons…

【FAQ】使用Node.js 镜像 构建本地项目

在nodejs官方并没有提供使用node.js构建本地项目的方法,但是通过阅读官方文档,可以发现,官方在包管理器界面提供了如下语句 所以node.js容器是可以执行语句的 下面通过docker 的 -w 、-v 参数设置容器工作目录和目录映射(实现本…

MySQL技巧之跨服务器数据查询:进阶篇-从A服务器的MySQ数据库复制到B服务器的SQL Server数据库的表中

MySQL技巧之跨服务器数据查询:进阶篇-从A服务器的MySQ数据库复制到B服务器的SQL Server数据库的表中 基础篇已经描述:借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MyS…

Flutter 指纹识别

在这篇博客中,我们将介绍如何使用 Flutter 的 local_auth 插件在 Android 和 iOS 设备上实现指纹识别功能。通过这一步一步的实现,我们将学习如何检查设备是否支持生物识别、如何触发指纹验证,并处理可能出现的错误。 效果图(因为…