4.5.tensorRT基础(1)-onnx文件及其结构的学习,编辑修改onnx

news2024/11/25 22:37:28

目录

    • 前言
    • 1. onnx
      • 1.1 导出onnx
      • 1.2 读取onnx
      • 1.3 创建onnx
      • 1.4 编辑onnx
      • 1.5 onnx总结
      • 1.6 本节知识点
    • 2. 补充知识
      • 2.1 Protobuf
        • 2.1.1 Protobuf简介
        • 2.1.2 基本使用流程
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习 tensorRT 基础-onnx 文件及其结构的学习,编辑修改 onnx

课程大纲可看下面的思维导图

在这里插入图片描述

1. onnx

1.1 导出onnx

onnx 是什么?我们先来解决这个问题

onnx 是 Microsoft 开发的一种中间格式的模型

onnx 可以理解为一种通用货币,开发者可以把自己开发训练好的模型保存为 onnx 文件,比如 pytorch 训练的模型导出为 onnx,tensorflow 训练的模型也可以导出为 onnx,caffe 框架训练的模型同样也可以导出 onnx,onnx 类似一把万能钥匙,能打开不同训练框架的门

导出的 onnx 模型可以很方便的被部署工程师借助部署框架(如 tensorRT、openvino、ncnn 等)部署在不同的硬件平台上,而不必关心开发者使用的是哪一种框架,也就是说部署工程师不需要为不同的框架训练的模型做不同的部署,通过 onnx 这个桥梁可以把它们都统一起来,我只需要关注 onnx 部署就行了,而不去关注 pt、uff、caffe 模型的部署,毕竟你们都可以转化为 onnx,大大方便了部署工程师的工作😂

先执行 pytorch-gen-onnx.py 示例代码,内容如下:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.onnx
import os

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()

        self.conv = nn.Conv2d(1, 1, 3, padding=1)
        self.relu = nn.ReLU()
        self.conv.weight.data.fill_(1)
        self.conv.bias.data.fill_(0)
    
    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

# 这个包对应opset11的导出代码,如果想修改导出的细节,可以在这里修改代码
# import torch.onnx.symbolic_opset11
print("对应opset文件夹代码在这里:", os.path.dirname(torch.onnx.__file__))

model = Model()
dummy = torch.zeros(1, 1, 3, 3)
torch.onnx.export(
    model, 

    # 这里的args,是指输入给model的参数,需要传递tuple,因此用括号
    (dummy,), 

    # 储存的文件路径
    "demo.onnx", 

    # 打印详细信息
    verbose=True, 

    # 为输入和输出节点指定名称,方便后面查看或者操作
    input_names=["image"], 
    output_names=["output"], 

    # 这里的opset,指,各类算子以何种方式导出,对应于symbolic_opset11
    opset_version=11, 

    # 表示他有batch、height、width3个维度是动态的,在onnx中给其赋值为-1
    # 通常,我们只设置batch为动态,其他的避免动态
    dynamic_axes={
        "image": {0: "batch", 2: "height", 3: "width"},
        "output": {0: "batch", 2: "height", 3: "width"},
    }
)

print("Done.!")

上述代码展示了如何使用 PyTorch 将模型导出为 ONNX 格式。通过使用 torch.onnx.export 函数,可以指定模型、输入参数、输出文件路径等参数来进行导出。

我们的目的是将 pytorch 模型导出为 onnx,导出的 onnx 长什么样子呢?

在这里插入图片描述

图1-1 onnx结构

从图中可以看到它有个 image 作为输入,其大小为 [batch,1,height,width],后面有个 Conv 算子,还有个 ReLU 节点,最后是我们的输出。值得注意的是,在 onnx 里面,如果某个维度是用字母或者 -1 来表示则表明这个维度是动态的。

onnx 里面是储存了模型的整个结构,我们学习本节主要目的是了解 onnx 是如何储存模型的信息的,onnx 的格式实际上是由什么东西组成的。

关于 onnx 你需要了解:

1、 ONNX 的本质,是一种 Protobuf 格式的文件。我们上面看到的模型其实就是 Protobuf 序列化后储存的东西

2、 Protobuf 则通过 onnx-ml.proto 文件得到 onnx-ml.pb.h 和 onnx-ml.pb.cc 用于 C++ 调用或 onnx_ml_pb2.py 用于 python 调用。(如下图所示)

在这里插入图片描述

3、 编译工具 protoc 通过编译 onnx-ml.proto 得到 onnx-ml.pb.cc 在加上对应的代码就可以操作 onnx 模型文件,实现对应的增删改

4、 onnx-ml.proto 用于描述 onnx 文件是如何组成的,具有什么结构,它是 onnx 经常参照的东西,下面是 onnx-ml.proto 部分内容,参考自 https://github.com/onnx/onnx/blob/main/onnx/onnx-ml.proto

在这里插入图片描述

我们来简单理解下上图中的内容。NodeProto 用于描述 onnx 中的节点 node,它有 input 属性,是 repeated 即重复类型,数组;它有 output 属性,也是 repeated 即重复类型,数组;它有 name 属性是 string 类型。

对于 repeated 是数组,对于 optional 则无视它,对于 input = 1 后面的数字是 id 也无视

我们只用关心是否数组,类型是什么

onnx 文件组成如下图所示:

在这里插入图片描述

  • model:表示整个 onnx 模型,包括图结构和解析器版本、opset 版本、导出程序类型
    • opset 版本即 operator 版本号即 pytorch 的 op 版本(操作算子)
  • model.graph:表示图结构,通常是 Netron可视化工具 中看到的结构
  • model.graph.node:表示图结构中所有节点如 conv、bn、relu 等,存储如 kernel_size、padding、stride 等信息
  • model.graph.initializer:权重数据大都存储在这里
  • model.graph.input:模型的输入
  • model.graph.input:模型的输出

对于 anchor grid 类的常量数据,通常会储存在 model.graph.node 中,并指定类型为 Constant,该类型节点在 Netron 中可视化时不会显示出来

1.2 读取onnx

接下来我们来读下刚才导出的 onnx 模型,读取的代码如下:

import onnx
import onnx.helper as helper
import numpy as np

model = onnx.load("demo.change.onnx")

#打印信息
print("==============node信息")
# print(helper.printable_graph(model.graph))
print(model)

conv_weight = model.graph.initializer[0]
conv_bias = model.graph.initializer[1]

# 数据是以protobuf的格式存储的,因此当中的数值会以bytes的类型保存,通过np.frombuffer方法还原成类型为float32的ndarray
print(f"===================={conv_weight.name}==========================")
print(conv_weight.name, np.frombuffer(conv_weight.raw_data, dtype=np.float32))

print(f"===================={conv_bias.name}==========================")
print(conv_bias.name, np.frombuffer(conv_bias.raw_data, dtype=np.float32))

运行效果如下:

在这里插入图片描述

图1-2 读取onnx

以上示例代码展示了如何使用 onnx 库来加载和解析 ONNX 模型文件。通过使用 onnx.load 函数,可以加载 ONNX 文件,并通过 model.graph 属性访问模型的图结构和初始化参数。在示例中,打印了模型的节点信息以及卷积层的权重和偏置项。使用 np.frombuffer 方法将原始数据转换为 float32 类型的 ndarray。这样可以获取并查看模型的各个参数值。

1.3 创建onnx

接下来我们来学习如何创建 onnx,创建的代码如下:

import onnx # pip install onnx>=1.10.2
import onnx.helper as helper
import numpy as np

# https://github.com/onnx/onnx/blob/v1.2.1/onnx/onnx-ml.proto

nodes = [
    helper.make_node(
        name="Conv_0",   # 节点名字,不要和op_type搞混了
        op_type="Conv",  # 节点的算子类型, 比如'Conv'、'Relu'、'Add'这类,详细可以参考onnx给出的算子列表
        inputs=["image", "conv.weight", "conv.bias"],  # 各个输入的名字,结点的输入包含:输入和算子的权重。必有输入X和权重W,偏置B可以作为可选。
        outputs=["3"],  
        pads=[1, 1, 1, 1], # 其他字符串为节点的属性,attributes在官网被明确的给出了,标注了default的属性具备默认值。
        group=1,
        dilations=[1, 1],
        kernel_shape=[3, 3],
        strides=[1, 1]
    ),
    helper.make_node(
        name="ReLU_1",
        op_type="Relu",
        inputs=["3"],
        outputs=["output"]
    )
]

initializer = [
    helper.make_tensor(
        name="conv.weight",
        data_type=helper.TensorProto.DataType.FLOAT,
        dims=[1, 1, 3, 3],
        vals=np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], dtype=np.float32).tobytes(),
        raw=True
    ),
    helper.make_tensor(
        name="conv.bias",
        data_type=helper.TensorProto.DataType.FLOAT,
        dims=[1],
        vals=np.array([0.0], dtype=np.float32).tobytes(),
        raw=True
    )
]

inputs = [
    helper.make_value_info(
        name="image",
        type_proto=helper.make_tensor_type_proto(
            elem_type=helper.TensorProto.DataType.FLOAT,
            shape=["batch", 1, 3, 3]
        )
    )
]

outputs = [
    helper.make_value_info(
        name="output",
        type_proto=helper.make_tensor_type_proto(
            elem_type=helper.TensorProto.DataType.FLOAT,
            shape=["batch", 1, 3, 3]
        )
    )
]

graph = helper.make_graph(
    name="mymodel",
    inputs=inputs,
    outputs=outputs,
    nodes=nodes,
    initializer=initializer
)

# 如果名字不是ai.onnx,netron解析就不是太一样了
opset = [
    helper.make_operatorsetid("ai.onnx", 11)
]

# producer主要是保持和pytorch一致
model = helper.make_model(graph, opset_imports=opset, producer_name="pytorch", producer_version="1.9")
onnx.save_model(model, "my.onnx")

print(model)
print("Done.!")

创建的 onnx 如下图所示,符合我们的预期:

在这里插入图片描述

图1-3 创建onnx

以上示例代码演示了如何从头创建一个简单的 ONNX 模型。首先,定义了一个包含两个节点的图结构。每个节点都由 helper.make_node 函数创建,指定了节点的名称、算子类型、输入、输出和其他属性。在示例中,第一个节点是卷积层(Conv),第二个节点是ReLU激活函数层(Relu)。

接下来,定义了模型的初始化参数,使用 helper.make_tensor 函数创建了权重和偏置项的张量。这些张量包含了参数的名称、数据类型、维度和原始数据。

然后,定义了输入和输出的信息,使用 helper.make_value_info 函数创建了输入和输出的张量类型信息。

最后,使用 helper.make_graph 函数创建了图结构,并通过 helper.make_model 函数创建了完整的模型。模型中指定了使用的算子集合(opset)和制作工具的名称和版本。

最终,使用 onnx.save_model 函数将模型保存为 ONNX 文件。

整个过程是通过使用 onnx 库提供的 helper 函数和数据结构来构建模型的图结构、参数和元数据,从而创建一个完整的 ONNX 模型。

通过打印模型,可以查看模型的结构和元数据信息。

1.4 编辑onnx

接下来我们来学习如何编辑 onnx,编辑的代码如下:

import onnx
import onnx.helper as helper
import numpy as np

model = onnx.load("demo.onnx")

# 可以取出权重
conv_weight = model.graph.initializer[0]
conv_bias = model.graph.initializer[1]
# 修改权
conv_weight.raw_data = np.arange(9, dtype=np.float32).tobytes()

# 修改权重后储存
onnx.save_model(model, "demo.change.onnx")
print("Done.!")

以上示例代码演示了如何编辑已有的 ONNX 模型。首先,使用 onnx.load 函数加载了原始的 ONNX 模型。

然后,通过访问模型的 graph.initializer 属性,可以获取到模型的权重张量。在代码中,使用 model.graph.initializer[0] 获取到了第一个权重张量。

接下来,可以通过修改权重张量的 raw_data 属性来改变权重的数值。在示例中,使用 np.arange 函数生成了一个新的浮点数数组,然后将该数组通过 tobytes 方法转换为 bytes 类型,并赋值给了权重张量的 raw_data 属性。

最后,使用 onnx.save_model 函数将修改后的模型保存为新的 ONNX 文件。

通过这种方式,可以对已有的 ONNX 模型进行权重的修改,从而实现模型的编辑和调整。

1.5 onnx总结

ONNX 重点

1、 ONNX 的主要结构:graph、graph.node、graph.initializer、graph.input、graph.output

2、 ONNX 的节点创建方式:onnx.helper,各种 make 函数

3、 ONNX 的 proto 文件,https://github.com/onnx/onnx/blob/main/onnx/onnx-ml.proto

4、 理解模型结构的储存、权重的储存、常量的储存、netron 的解读对应到代码中的部分

5、 ONNX 的解析器的理解,包括如何使用 nv 发布的解析器源代码 https://github.com/onnx/onnx-tensorrt

1.6 本节知识点

关于本次课程的知识点有:(from 杜老师)

本节视频辅助讲解

1、为什么要编辑 onnx

2、pytorch 生成 onnx

3、读取 onnx

4、编辑和创建 onnx

本节主要讲解 onnx 的原理,文件较多,我们一个个看

1、pytorch-gen-onnx.py 是之前讲过的从 pytorch 转换 onnx 格式的代码

2、 通过 onnx-ml.protomake-onnx-pb.sh 了解 onnx 的结构

  • 2.1 onnx 是基于 protobuf 来做数据存储和传输,*.proto 后缀文件,其定义是 protobuf 语法,类似 json

  • 2.2 对于变量结构、类型等,我们可以参照 onnx-ml.proto 里面的定义,这个文件有 800 多行,我们只要搞清楚里面的核心部分就行:

  • ModelProto:当加载了一个 onnx 后,会获得一个 ModelProto。它包含一个 GraphProto 和一些版本,生产者的信息。

  • GraphProto:包含了四个 repeated 数组(可以用来存放 N 个相同类型的内容,key 值为数字序列类型)。这四个数组分别是 node(NodeProto 类型),input(ValueInfoProto 类型),output(ValueInfoProto 类型)和 initializer(TensorProto 类型)

  • NodeProto:存 node,放了模型中所有的计算节点,语法结构前面有说过

  • ValueInfoProto:存 input,放了模型的输入节点;存 output,放了模型中所有的输出节点

  • TensorProto:存 initializer,放了模型的所有权重参数

  • AttributeProto:每个计算节点中还包含了一个 AttributeProto 数组,用来描述该节点的属性,比如 Conv 节点或者说卷积层的属性包含 group,pad,strides 等等

  • 2.3 通过 protoc 编译 onnx-ml.proto,产生 onnx-ml.pb.cc 文件

bash make-onnx-pb.sh

3、create-onnx.py

  • 3.1 create-onnx.py 从零直接创建 onnx,不经过任何框架的转换。通过 import onnx 和 onnx.helper 提供的 make_node,make_graph,make_tensor 等等接口我们可以轻易的完成一个 ONNX 模型的搭建。

  • 3.2 需要完成对 node,initializer,input,output,graph,model 的填充

  • 3.3 读懂 create_onnx.py 以 make_node 为例:

nodes = [
    helper.make_node(
        name="Conv_0",   # 节点名字,不要和op_type搞混了
        op_type="Conv",  # 节点的算子类型, 比如'Conv'、'Relu'、'Add'这类,详细可以参考onnx给出的算子列表
        inputs=["image", "conv.weight", "conv.bias"],  # 各个输入的名字,结点的输入包含:输入和算子的权重。必有输入X和权重W,偏置B可以作为可选。
        outputs=["3"],  
        pads=[1, 1, 1, 1], # 其他字符串为节点的属性,attributes在官网被明确的给出了,标注了default的属性具备默认值。
        group=1,
        dilations=[1, 1],
        kernel_shape=[3, 3],
        strides=[1, 1]
    ),

4、editor-onnx.py

  • 4.1 由于 protobuf 支持任何的语言,我们可以使用 c/c++/python/java/c# 等等实现对 onnx 文件的读写操作

  • 4.2 掌握 onnx 和 helper 实现对 onnx 文件的各种编辑和修改

  • 增:一般伴随增加 node 和 tensor

    graph.initializer.append(xxx_tensor)
    graph.node.insert(0, xxx_node)
    
  • 删:

    graph.node.remove(xxx_node)
    
  • 改:

    input_node.name = 'data'
    

5、read-onnx.py

  • 5.1 通过 graph 可以访问参数,数据是以 protobuf 的格式存储的,因此当中的数值会以 bytes 的类型保存。需要用 np.frombuffer 方法还原成类型为 float32ndarray。注意还原出来的 ndarray 是只读的。

2. 补充知识

2.1 Protobuf

前面有提到说 onnx 本质是一种 protobuf 格式的文件,那 protobuf 又是个啥东东?🤔

关于 protubuf 的相关介绍 Copy 自 赵老师的百度Apollo智能驾驶课程,建议看原视频。

2.1.1 Protobuf简介

概念

Protobuf 全称 Protocol buffers,是 Google 研发的一种一种跨语言、跨平台的序列化结构的数据格式,是一个灵活的、高性的用于序列化数据的协议

特点

在序列化数据时常用的数据格式还有 XML、JSON 等,相比较而言,Protobuf 更小、效率更高且使用更为便捷,Protobuf 内置编译器 protoc,可以将 Protobuf 编译成 C++、Python、Java、C#、Go 等多种语言对应的代码,然后可以直接被对应语言使用,轻松实现对数据流的读或写操作而不需要再做特殊解析。

Protobuf 的优点如下:

  • 高效——序列化后字节占用空间少,序列化的时间效率高
  • 便捷——可以将结构化数据封装为类,使用方便
  • 跨语言——支持多种编程语言
  • 高兼容性——当数据交互的双方使用同一数据协议,如果一方修改了数据结构,不影响另一方的使用

Protobuf 也有缺点:

  • 二进制格式易读性差
  • 缺乏自描述

2.1.2 基本使用流程

需求如下

创建一个 protobuf 文件,在该文件中声明学生的姓名、身高、年龄…等信息,然后分别使用 C++ 和 Python 实现学生数据的读写操作。

实现流程如下

  • 1.编写 proto 文件
  • 2.编译生成对应的 C++ 或 Python 文件
  • 3.在 C++ 或 Python 中调用

1.编写 proto 文件,如下所示

// student.proto

// 使用的 proto 版本
syntax = "proto2"

// 包
package person;

//消息 ---message 是关键字,Student 消息名称
message Student{
    //字段
    //字段格式:字段规则 数据类型 字段名称 字段编号
    required string name   = 1;
    optional unit64 age    = 2;
    optional double height = 3;
    repeated string books  = 4;
}

2.编译,指令如下

$ protoc student.proto --cpp_out=./

执行完成后,在当前目录下 student.proto 文件会生成 student.pb.hstudent.pb.cc,将 .cc 后缀修改为 .cpp 可供C++调用

3.C++ 调用,调用 demo 如下

// test.cpp

#include <student.pb.h>

using namespace std;
using namespace person;

int main(int argc, char const *argv[])
{
    // 1. create object
    person::Student stu;
    
    // 2. wirte data
    stu.set_name("zhangsan");
    stu.set_age(18);
    stu.set_height(1.75);
    stu.add_books("c++");
    stu.add_books("python");

    // 3. read data
    std::string name = stu.name();
    uint64_t age = stu.age();
    double height = stu.height();
    std::cout << name << " == " << age << " == " << height << std::endl;
    for (int i = 0; i < stu.books_size(); i++)
    {
        std::cout << stu.books(i) << "-";
    }
    std::cout << std::endl;

    return 0;
}

CMakeLists.txt 内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pthread -std=c++11")
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)

set(PROTOBUF_DIR "/home/zhlab/protobuf")

include_directories(
    ${PROTOBUF_DIR}/include
    ${PROJECT_SOURCE_DIR}/src
)

link_directories(
    ${PROTOBUF_DIR}/lib
)

add_executable(main ${PROJECT_SOURCE_DIR}/src/test.cpp ${PROJECT_SOURCE_DIR}/src/student.pb.cpp)

# add protobuf
target_link_libraries(main protobuf)
target_link_libraries(main pthread)

编译 test.cpp 文件,在 workspace/ 文件夹下运行可执行文件,运行效果如下所示:

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

图2-1 protobuf案例运行效果

总结

本次课程主要学习了 onnx 文件及其结构,onnx 本质是一个 protobuf 文件,它存储了模型的网络结构、权重等信息,onnx 可以理解为一种通用货币,将各种训练框架的模型导出为 onnx 后可交给部署工程师部署在不同的硬件平台上,而不必关心开发者使用的是哪一个框架。

除此之外,我们还学习了 onnx 文件的导出、读取、创建、编辑,通过这些案例使得我们更加了解 onnx,同时也方便我们后续 TRT 的部署工作

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

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

相关文章

【Nacos】适配PostgreSQL数据库

目录 本文环境介绍修改application.properties下载PostgreSQL扩展插件初始化PostgreSQL启动测试 本文环境介绍 组件依赖版本Nacos2.2.3OpenJDK17.0.2PostgreSQL Server15 修改application.properties ### Deprecated configuration property, it is recommended to use spri…

手搓一台简单的网络损伤仪——弱网测试

1、介绍 支持对链路带宽、传输时延、丢包率和无码率的手动设置&#xff1b; 1.1、网络损伤仪在使用时&#xff0c;网络拓扑连接 1.2、网络损伤仪管理页面展示 2、使用的设备及相关技术栈 一台Intel 赛扬 J1900的迷你主机【拥有4个千兆网口】&#xff1b;ubuntu-18.04.5-live…

(数组与矩阵) 剑指 Offer 04. 二维数组中的查找 ——【Leetcode每日一题】

❓ 剑指 Offer 04. 二维数组中的查找 难度&#xff1a;中等 在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右 非递减 的顺序排序&#xff0c;每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#x…

使用Stable Diffusion和PyTorch创建艺术二维码

大家好&#xff0c;本文将介绍如何利用Stable Diffusion和PyTorch的能力来创建AI生成的QR码艺术。通过将这些技术相结合&#xff0c;可以生成独特的、具有视觉吸引力的艺术作品&#xff0c;其中包含QR码&#xff0c;为艺术作品增添了互动元素。 Stable Diffusion和PyTorch 稳…

赢得CSDN铁粉的秘诀!(记录写作三个月的点点滴滴)

在CSDN上写博客已经三个多月了&#xff0c;从开始的陌生到渐渐熟悉CSDN网站的每个角落&#xff0c;猛然发现还有这么一个活动&#xff0c;想了想还是写一篇博客吧&#xff0c;不仅是参加活动&#xff0c;也是记录这一路慢慢探索的过程。 1.CSDN铁粉介绍及说明 CSDN 是为中国 I…

C++进阶:map和set

map和set STL容器分为序列式容器和关联式容器。 序列式容器vector、list等底层为线性数据结构&#xff0c;数据元素之间没有联系&#xff0c;一般用来存储数据。关联式容器map、set等底层采用平衡搜索树&#xff0c;存储的是<key/value>式的键值对&#xff0c;数据检索效…

NOSQL简单实战

目录 实战一&#xff1a; 1、 string类型数据的命令操作&#xff1a; &#xff08;1&#xff09; 设置键值&#xff1a; &#xff08;2&#xff09; 读取键值&#xff1a; &#xff08;3&#xff09; 数值类型自增1&#xff1a; &#xff08;4&#xff09; 数值类型自减1&…

基于html2canvas和jspdf将document DOM节点转换为图片生成PDF文件,并下载到本地

这里要用到html2canvas将document DOM节点转换为图片&#xff0c;并下载到本地_你挚爱的强哥的博客-CSDN博客前端用原生js编辑文件内容→创建生成文件(格式可以自定义)→下载文件_你挚爱的强哥的博客-CSDN博客。会自动创建一个html文件。https://blog.csdn.net/qq_37860634/art…

业务中添加历史版本事务并发处理

业务背景&#xff1a;项目中包含历史版本的管理&#xff0c;每次保存历史版本都添加一条新的记录到数据库&#xff0c;且版本号加1。保存版本的时候1.要先查询历史版本数据表&#xff0c;获取当前最新的版本号&#xff1b;2.最新的版本号加1&#xff0c;插入数据历史版本记录表…

dp算法 力扣174地下城游戏

在学习编程时&#xff0c;算法是一道硬菜&#xff0c;而dp作为算法的一份子&#xff0c;它的地位在编程界举足轻重。 174. 地下城游戏 - 力扣&#xff08;LeetCode&#xff09; 本文是Java代码哦~ 一、题目详情 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地…

Vue列表渲染(v-for)以及key的作用与原理

索引值的两种写法&#xff1a; <!--index遍历时的默认索引值--> <ul><li v-for"(p,index) in persons":key"index">{{p.name}}--{{p.age}}</li></ul> 代码&#xff1a; v-for"p in persons" :key"p.id&quo…

【多维数组对象拍平处理为一维数组】

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

深谋远“绿”,数据中心未来之路在何方?

数据中心未来演进方向是什么&#xff1f; 在中国绿色算力大会-华为数据中心平行会议上&#xff0c;业界愈发形成共识&#xff1a;即在双碳目标的大背景下&#xff0c;数据中心走向绿色低碳化、智能化。 去年初&#xff0c;东数西算工程正式启动建设&#xff0c;标志着我国数据…

Ui自动化测试上传文件方法都在这里了

目录 前言 被测HTML代码 send_keys方法 实例------------------------------------ Time : 2019/7/17 19:03 Auth : linux超 File : upload_file_send_keys.py IDE : PyCharm Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! QQ : 2…

代码随想录第26天 | ● 332.重新安排行程 ● 51. N皇后 ● 37. 解数独

332. 重新安排行程 /*** param {string[][]} tickets* return {string[]}*/var findItinerary function (tickets) {let s "ZZZ";let start;for (let i in tickets) {if (tickets[i][0] "JFK" && tickets[i][1] < s) {start i;s tickets[i…

密码学学习笔记(十三):哈希函数 - Merkle–Damgård结构

Merkle–Damgrd是一种算法&#xff0c;由Ralph Merkle和Ivan Damgrd提出。它通过迭代调用压缩函数来计算消息的哈希值。 应用 拿SHA-2举例&#xff0c;首先我们需要对需要进行哈希运算的输入做填充&#xff0c;然后将填充后的输入划分为等长的分组&#xff0c;每个分组的长度…

uniapp 土法 瀑布流 - vue3

效果图 代码 <template><view><!-- 瀑布流展示 --><!-- 标签页 --><view class="rowStart flexNoLineBreaking paddingCol10 innerDomPaddingRow5 tinyShadow marginBottom10"><view @click="tabsCurrent = 0; run_waterfa…

计算机网络基础第六章

一、应用层概述 1.1 网络应用模型 1.1.1 客户/服务器(C/S)模型 1.1.2 P2P模型 二、域名解析系统——DNS系统 2.1 域名 2.2 域名服务器 2.3 域名解析过程 三、文件传输协议——FTP 3.1 FTP服务器和用户端 3.2 FTP工作原理 四、电子邮件 4.1 电子邮件系统概述 4.2 简单邮件传送…

C# Modbus通信从入门到精通(1)——Modbus RTU(虚拟串口调试工具的使用)

前言: 订阅本专栏后,加入vip专属的qq群,在群资料里面就能找到虚拟串口工具 1、安装虚拟串口工具 第1步、双击vspd.exe安装 第2步、 打开“Cracked”的文件夹,里面有一个vspdct.dll,如下图:把这个dll粘贴到C:\Program Files (x86)\Eltima Software\Virtual Serial Po…

Python垃圾回收

Python垃圾回收 文章目录 Python垃圾回收引用计数标记清除分代回收 GC作为现代编程语言的自动内存管理机制&#xff0c;专注于两件事&#xff1a; 找到内存中无用的垃圾资源清除这些垃圾并把内存让出来给其他对象使用。 GC彻底把程序员从资源管理的重担中解放出来&#xff0c;让…