Torch 模型 model => .onnx => .trt 及利用 TensorTR 在 C++ 下的模型部署教程

news2025/3/12 15:00:56

一、模型训练环境搭建和模型训练

模型训练环境搭建主要牵扯 Nvidia driver、Cuda、Cudnn、Anaconda、Torch 的安装,相关安装教程可以参考【StarCoder 微调《个人编程助手: 训练你自己的编码助手》】中 5.1 之前的章节。

模型训练的相关知识可以参考 Torch的编程方式示例以及必备知识,可以直接使用第一部分的手写字体分类模型作为当前教程的示例模型,只需要在脚本最后加入如下代码便可将模型保存为 .pth 模型。注意在上载 model 的代码里依然还是要定义一遍模型,或者 import 之前的模型定义 py 文件,否则报错。

torch.save(model, './model.pth')
# model = torch.load('./model.pth')
# 虽然保存了模型结构和模型参数,但是在load之前还是要定义一遍model才行,或者直接import model

二、model 转换 .onnx 并在 GPU 上利用 python 代码推理

Torch model 模型通过如下代码可以转换为 .onnx 模型。参考链接。

torch.onnx.export(model, input, onnx_path, verbose=True, opset_version=12, input_names=input_names, output_names=output_names) 

# model 不是 .pth 路径,而是读取内存中的模型,需要先引入模型定义的类,再 load .pth 文件得到 model
# input 是 model 的 forward 函数的输入,如果输入只有一个图片,那 input 可以是一个张量或者字典,但如果输入有多个,则 imput 只能是字典,键名与 forward 形参名称对应
# onnx_path 是输出 onnx 的路径
# verbose 是转换过程中是否打印详情
# input_names 定义 onnx 中输入的名称列表
# output_names 定义 onnx 中输出的名称列表

如果想要在 GPU 上利用 python 代码进行 onnx 模型推理,那首先需要安装 onnxruntime-gpu,安装时需要版本匹配,需要与 Cuda 和 Cudnn 版本匹配,官方版本匹配说明参考链接。安装命令如下:

pip install onnxruntime-gpu==1.18.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

在完成 onnxruntime-gpu 的安装后,可以通过如下代码完成模型推理。 但推理性能一般,其在 Cuda 下的推理时间似乎大于在 Torch 下的推理时间。参考链接。

providers = ['CUDAExecutionProvider']
m = rt.InferenceSession(onnx_path, providers=providers)

# 推理 onnx 模型
# output_names 为导出 onnx 时定义的输出名称列表,例如 output_names = ['hm', 'vaf', 'haf','curb_hm', 'curb_vaf', 'curb_haf']
# {"input": image} 为导出 onnx 时定义的输入名称列表
onnx_pred = m.run(output_names, {"input": image})

三、TensorRT 安装

TensorRT 存在 Python 版本和 C++ 版本。

Python 版本 TensorRT 的安装很简单。但一般不太会用 Python 版本 TensorRT。

// TensorRT 8.5 以上的版本用以下命令
pip install tensorrt 

// 测试一下
python
>>> import tensorrt
>>> print(tensorrt.__version__)
>>> assert tensorrt.Builder(tensorrt.Logger())

C++ 版本 TensorRT 的安装分为.deb 和 . tar 的方式,一般选用 .tar 的方式。

首先去官网下载 TensorRT,下载时版本即需要与 Cuda 版本匹配,也需要与 Cudnn 匹配。官网中只写了 TensorRT 与 Cuda 如何匹配,所以与 Cudnn 的匹配需要自己试出来,当前成功安装的版本是 Cuda12.1 + Cudnn 9.7.1 + TensorRT 10.0.0.6,测试发现 TensorRT 8.* 要求 Cudnn 8.*。

tar -xzvf TensorRT-8.4.3.1.Linux.x86_64-gnu.cuda-11.6.cudnn8.4.tar.gz
cp -r TensorRT-8.4.3.1 /usr/local/include/TensorRT-8.4.3.1
// 路径无所谓,只要知道在哪里就可以,下面 bashrc 中写的就是这个路径,
// cp 完后 home 目录下可删除,或者直接不 cp 而是直接使用 home 下这个路径也可以

sudo vim .bashrc

// 添加如下内容,注意版本号和路径的对应
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/include/TensorRT-8.4.3.1/lib
alias trtexec="/usr/local/include/TensorRT-8.4.3.1/bin/trtexec"

# 如果是 deb 安装的,则可能是:
alias trtexec="/usr/src/tensorrt/bin/trtexec"
# or 
alias trtexec="/usr/local/tensorrt/bin/trtexec"


source ~/.bashrc

安装完 TensorRT 后,在 vscode 里直接 #include <NvInfer.h> 会报错找不到,解决办法是在 vscode 设置 (.vscode/c_cpp_properties.json) 里添加如下 TensorRT 和 cuda 两个路径,vscode 添加 include 路径的办法参考链接。

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/local/include/TensorRT-10.0.0.6/include",
                "/usr/local/cuda-12.1/include"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

四、.onnx 模型转 .trt 模型

.onnx 模型转 .trt 模型有两种方法,第一种方法是使用现成的 trtexec 命令,第二种方法是使用自己写的 C++ 代码,第二种方式可以参考 链接 1 链接 2 链接3。这里主要使用第一种方法,关于第一种方法的详细介绍可以参考 链接。大部分情况只需要用到下面这两句命令。

trtexec --onnx=2Dmodel.onnx --saveEngine=2Dmodel.trt

// 转换为 fp16 模型
trtexec --onnx=2Dmodel.onnx --saveEngine=2Dmodel.trt --fp16

五、.trt 模型在 C++ 下进行推理

首先,介绍两个概念:

CUDA 流:可以将一个流看做是 GPU 上的一个任务,不同任务可以并行执行。利用三个流,同一个流上的任务顺序执行,不同流上的任务可以同时执行,从而实现并发操作。

运行时库:就是在运行时提供基础支持的库。例如:C/C++ 运行时库,就是 C/C++ 程序在运行时依赖的基础库。

然后,说明一下官方资料:

Nvidia TensorRT 官方文档 (最新版本 历史版本)、Quick Start Guide (最新版本 历史版本可以从官方文档进入)、API 说明 (最新版本 历史版本可以从官方文档进入)、版本变化 API 变化说明 和代码 (最新版本 历史版本)。

这里存在一个情况,TensorRT 从 8.x 版本变为 10.x 版本后,API 发生了一些变化,例如:模型推理函数从 enqueueV2 变为 enqueueV3,且形参列表也变了;还有新版本似乎不需要再主动调用 destroy() 函数进行对象销毁。但官方文档、Quick Start Guide 和代码等,更新并不及时,到了最新版才完成更新,所以如果你实际安装的是 TensorRT 10.0.0.6,然后你参考对应版本的官方资料,会无法运行。

最后,说明一下第三方资料:

当前我搜索到的所有第三方 CSDN 和 知乎 资料全是针对 8.x 版本的。但也很值得参考

Pytorch 导出 onnx 模型,C++ 转化为 TensorRT 并实现推理过程   对应 Git 代码

torch 转 ONNX 模型转 TensorRT C++ 推理

TensorRT 的使用流程 c++ 和 python

下面是我针对包含三个 head (语义分割、关键点检测、旋转框 FCOS 检测) 的 trt 模型的推理代码,针对版本是 Cuda12.1 + Cudnn 9.7.1 + TensorRT 10.0.0.6。

FP16 的推理与 FP32 的推理代码没区别,唯一区别就是上一步转 .trt 时多了个配置参数,所以在模型推理时,输入依然是 FP32,进入模型后,模型会自动转换为 FP16,输出也一样,模型会自动转换为 FP32。实测在 24G A10 下,100 次推理,512 × 256 尺寸输入图片,FP32 推理耗时 394ms,FP32 推理耗时 253ms。

IPMdet.h

#pragma once
// 系统头文件
#include <string>
#include <vector>
#include <fstream>
#include <memory>
// TensorRT 相关头文件
#include <NvInfer.h>
#include <NvInferRuntime.h>
// CUDA 相关头文件
#include <cuda_runtime.h>
// 测试用头文件
#include <iostream>
using namespace std;


// 用于打印 CUDA 报错 - 宏定义
#define checkRuntime(op) __check_cuda_runtime((op), #op, __FILE__, __LINE__)


// 用于打印 TensorRT 报错,准备一个 logger 类,打印构建 TensorRT 推理模型过程中的一些错误或警告,按照指定的严重性程度 (severity) 打印信息
// 内联函数可以放在头文件,因为内联函数不会产生独立的符号,不会引起多重定义的问题‌
inline const char* severity_string(nvinfer1::ILogger::Severity t) {
	switch (t) {
		case nvinfer1::ILogger::Severity::kINTERNAL_ERROR: return "internal_error";
		case nvinfer1::ILogger::Severity::kERROR: return "error";
		case nvinfer1::ILogger::Severity::kWARNING: return "warning";
		case nvinfer1::ILogger::Severity::kINFO: return "info";
		case nvinfer1::ILogger::Severity::kVERBOSE: return "verbose";
		default: return "unknown";
	}
}

class TRTLogger : public nvinfer1::ILogger {
	public:
		virtual void log(Severity severity, nvinfer1::AsciiChar const* msg) noexcept override {
			if (severity <= Severity::kWARNING) {
			if (severity == Severity::kWARNING) printf("\033[33m%s: %s\033[0m\n", severity_string(severity), msg);
			else if (severity == Severity::kERROR) printf("\031[33m%s: %s\033[0m\n", severity_string(severity), msg);
			else printf("%s: %s\n", severity_string(severity), msg);
		}
	}
};

// 定义一个 share_ptr 的智能指针
// 模板的定义必须放在头文件中,因为模板的实例化需要在编译时进行
template<typename _T>
shared_ptr<_T> make_nvshared(_T *ptr) {
	return shared_ptr<_T>(ptr);
}

// IPM 图检测类
class IPMdet {
	public:
		IPMdet() = default;
		IPMdet(const string& file);
		~IPMdet();
		// 模型推理
		void infer_trtmodel(float* pimage);

	private:
		// 读取 .trt 文件
		vector<unsigned char> load_file(const string& file);
		// 预处理输入图片
		void preprocess_image(float* pimage);

	public:
		// 输出结果

    private:
		// TensorRT 推理用的工具
		vector<unsigned char> _engine_data;                           // 记录 .trt 模型的二进制序列化格式数据
		TRTLogger logger;                                             // 打印 TensorRT 的错误信息
		shared_ptr<nvinfer1::IRuntime> _runtime = nullptr;            // 运行时,即推理引擎的支持库和函数等
		shared_ptr<nvinfer1::ICudaEngine> _engine = nullptr;          // 推理引擎,包含反序列化的 .trt 模型数据
		shared_ptr<nvinfer1::IExecutionContext> _context = nullptr;   // 上下文执行器,用于做模型推理
		cudaStream_t _stream = nullptr;

		// 定义模型输入输出尺寸
		int input_batch = 1;
		int input_channel = 3;
		int input_height = 512;
		int input_width = 256;
		int output_height1 = input_height / 4;
		int output_width1 = input_width / 4;
		int output_height2 = input_height / 8 ;
		int output_width2 = input_width / 8;
	
		// 准备好 **_host 和 **_device,分别表示内存中的数据指针和显存中的数据指针
		// input 数据
		int input_numel = input_batch * input_channel * input_height * input_width;
		float* input_data_host = nullptr;
		float* input_data_device = nullptr;

		// output 数据 - keypoints
		int keypoints_numel = input_batch * 1 * output_height1 * output_width1;
		void* output_data_keypoints_host = nullptr;
		void* output_data_keypoints_device = nullptr;

		// output 数据 - classification
		int classification_numel = input_batch * 1 * output_height2 * output_width2;
		void* output_data_classification_host = nullptr;
		void* output_data_classification_device = nullptr;

		// output 数据 - bbox_OBB
		int bbox_OBB_numel = input_batch * 4 * output_height2 * output_width2;
		void* output_data_bbox_OBB_host = nullptr;
		void* output_data_bbox_OBB_device = nullptr;

		// output 数据 - centerness
		int centerness_numel = input_batch * 1 * output_height2 * output_width2;
		void* output_data_centerness_host = nullptr;
		void* output_data_centerness_device = nullptr;

		// output 数据 - bbox_wh
		int bbox_wh_numel = input_batch * 2 * output_height2 * output_width2;
		void* output_data_bbox_wh_host = nullptr;
		void* output_data_bbox_wh_device = nullptr;

		// output 数据 - segmentation
		int segmentation_numel = input_batch * 8 * input_height * input_width;
		void* output_data_segmentation_host = nullptr;
		void* output_data_segmentation_device = nullptr;
};

IPMdet.cpp

#include "IPMdet.h"


// 打印 Cuda 报错
// 函数定义不应放在头文件里,否则当头文件被多个 cpp 调用时,会出现重复定义的问题
bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line) {
	if (code != cudaSuccess) {
		const char* err_name = cudaGetErrorName(code);
		const char* err_message = cudaGetErrorString(code);
		printf("runtime error %s: %d  %s failed.\n  code = %s, message = %s", file, line, op, err_name, err_message);
		return false;
	}
	return true;
}

// 构造函数
IPMdet::IPMdet(const string& file){
    // 1. TensorRT 相关操作
    ///
    // 1.1 读取 .trt 文件
    _engine_data = load_file(file);

    // 1.2 创建运行时,需要日志记录器
    _runtime = make_nvshared(nvinfer1::createInferRuntime(logger));

    // 1.3 创建推理引擎,需要运行时和序列化 trt 文件,包含反序列化的 .trt 模型数据
    _engine = make_nvshared(_runtime->deserializeCudaEngine(_engine_data.data(), _engine_data.size()));
    if (_engine == nullptr) {
        printf("Deserialize cuda engine failed.\n");
        return;
    }

    // 1.4 创建上下文执行器,需要推理引擎
    _context = make_nvshared(_engine->createExecutionContext());

    // 打印 .trt 模型的输入输出张量的名称和维度,这里与 onnx 中的名称和维度一致
    // for (int i=0, e=_engine->getNbIOTensors(); i<e; i++){
    //     auto const name = _engine->getIOTensorName(i);
    //     auto const size = _engine->getTensorShape(name);
    //     cout << "Tensor Name: " << name << endl;
    //     for (int i = 0; i < size.nbDims; ++i) {
    //         std::cout << "Dimension " << i << ": " << size.d[i] << std::endl;
    //     }
    //     cout << endl;
    // }

    // 2. CUDA 相关操作
    ///
    // 2.1 创建 CUDA 流,CUDA 流类似于线程,每个任务都必须有一个 CUDA 流,不同的 CUDA 流可以在 GPU 中并行执行任务
	checkRuntime(cudaStreamCreate(&_stream));

    // 2.2 申请 CPU 内存和 GPU 内存,准备好 **_host 和 **_device,分别表示内存中的数据指针和显存中的数据指针
	// input 数据
    checkRuntime(cudaMallocHost(&input_data_host, input_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&input_data_device, input_numel * sizeof(float)));

    // output 数据 - keypoints
    checkRuntime(cudaMallocHost(&output_data_keypoints_host, keypoints_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&output_data_keypoints_device, keypoints_numel * sizeof(float)));

    // output 数据 - classification
    checkRuntime(cudaMallocHost(&output_data_classification_host, classification_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&output_data_classification_device, classification_numel * sizeof(float)));

    // output 数据 - bbox_OBB
    checkRuntime(cudaMallocHost(&output_data_bbox_OBB_host, bbox_OBB_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&output_data_bbox_OBB_device, bbox_OBB_numel * sizeof(float)));

    // output 数据 - centerness
    checkRuntime(cudaMallocHost(&output_data_centerness_host, centerness_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&output_data_centerness_device, centerness_numel * sizeof(float)));

    // output 数据 - bbox_wh
    checkRuntime(cudaMallocHost(&output_data_bbox_wh_host, bbox_wh_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&output_data_bbox_wh_device, bbox_wh_numel * sizeof(float)));

    // output 数据 - segmentation
    checkRuntime(cudaMallocHost(&output_data_segmentation_host, segmentation_numel * sizeof(float)));
	checkRuntime(cudaMalloc(&output_data_segmentation_device, segmentation_numel * sizeof(float)));

    // 3. TensorRT 内存绑定
    ///
    _context->setTensorAddress("image", input_data_device);
    _context->setTensorAddress("keypoints", output_data_keypoints_device);
    _context->setTensorAddress("classification", output_data_classification_device);
    _context->setTensorAddress("bbox_OBB", output_data_bbox_OBB_device);
    _context->setTensorAddress("centerness", output_data_centerness_device);
    _context->setTensorAddress("bbox_wh", output_data_bbox_wh_device);
    _context->setTensorAddress("segmentation", output_data_segmentation_device);

    cout << "init OK !" << endl;
}

// 析构函数
IPMdet::~IPMdet(){
    // 释放 CPU 内存,看 TensorRT 10 以上版本,似乎不再需要主动释放 CPU 内存
    checkRuntime(cudaFreeHost(input_data_host));
    checkRuntime(cudaFreeHost(output_data_keypoints_host));
    checkRuntime(cudaFreeHost(output_data_classification_host));
    checkRuntime(cudaFreeHost(output_data_bbox_OBB_host));
    checkRuntime(cudaFreeHost(output_data_centerness_host));
    checkRuntime(cudaFreeHost(output_data_bbox_wh_host));
    checkRuntime(cudaFreeHost(output_data_segmentation_host));

    // 释放 GPU 内存
	checkRuntime(cudaFree(input_data_device));
	checkRuntime(cudaFree(output_data_keypoints_device));
    checkRuntime(cudaFree(output_data_classification_device));
    checkRuntime(cudaFree(output_data_bbox_OBB_device));
    checkRuntime(cudaFree(output_data_centerness_device));
    checkRuntime(cudaFree(output_data_bbox_wh_device));
    checkRuntime(cudaFree(output_data_segmentation_device));

    // 释放 CUDA 流,看 TensorRT 10 以上版本,似乎不再需要主动释放 CUDA 流
    checkRuntime(cudaStreamDestroy(_stream));

    cout << "Detroy OK !" << endl;
}

// 读取 .trt 文件
vector<unsigned char> IPMdet::load_file(const string& file) {  // 返回结果为无符号字符的vector,其数据存储是连成片的
    ifstream in(file, ios::in | ios::binary);          // 定义一个数据读取对象,以二进制读取数据
    if (!in.is_open()) return {};                      // 如果没有可读数据则返回空

    in.seekg(0, ios::end);                             // seekg函数作用是将指针指向文件终止处
    size_t length = in.tellg();                        // tellg函数作用是返回指针当前位置,此时即为数据长度

    vector<uint8_t> data;                              // 定义一个vector用于存储读取数据,仅仅是类头,其数据存储区还是char型data指针
    if (length > 0) {
         in.seekg(0, ios::beg);                        // seekg函数作用是将指针指向文件起始处
         data.resize(length);                          // 为vector申请长度为length的数据存储区,默认全部填充 0
         in.read((char*)&data[0], length);             // 为vector的数据存储区读取长度为length的数据
    }
    in.close();                                        // 关闭数据流
    return data;
}

// 预处理输入图片
void IPMdet::preprocess_image(float* pimage){

  float mean[] = {128, 128, 128};
  float std[] = {128, 128, 128};

  int image_area = 512 * 256;
  float* phost_b = input_data_host + image_area * 0;
  float* phost_g = input_data_host + image_area * 1;
  float* phost_r = input_data_host + image_area * 2;
  for (int i=0; i<image_area; ++i, pimage += 3) {
       *phost_r++ = (pimage[0] - mean[0]) / std[0];
       *phost_g++ = (pimage[1] - mean[1]) / std[1];
       *phost_b++ = (pimage[2] - mean[2]) / std[2];
   }
}

void IPMdet::infer_trtmodel(float* pimage){
    // 预处理输入图片
    preprocess_image(pimage);
    // 将输入图片从 CPU 内存拷贝至 GPU 内存
    checkRuntime(cudaMemcpyAsync(input_data_device, input_data_host, input_numel *sizeof(float), cudaMemcpyHostToDevice, _stream));
    // 模型推理
    bool success = _context->enqueueV3(_stream);
    // 将输出结果从 GPU 内存拷贝至 CPU 内存
    checkRuntime(cudaMemcpyAsync(output_data_keypoints_host, output_data_keypoints_device, sizeof(output_data_keypoints_host), cudaMemcpyDeviceToHost, _stream));
    checkRuntime(cudaMemcpyAsync(output_data_classification_host, output_data_classification_device, sizeof(output_data_classification_host), cudaMemcpyDeviceToHost, _stream));
    checkRuntime(cudaMemcpyAsync(output_data_bbox_OBB_host, output_data_bbox_OBB_device, sizeof(output_data_bbox_OBB_host), cudaMemcpyDeviceToHost, _stream));
    checkRuntime(cudaMemcpyAsync(output_data_centerness_host, output_data_centerness_device, sizeof(output_data_centerness_host), cudaMemcpyDeviceToHost, _stream));
    checkRuntime(cudaMemcpyAsync(output_data_bbox_wh_host, output_data_bbox_wh_device, sizeof(output_data_bbox_wh_host), cudaMemcpyDeviceToHost, _stream));
    checkRuntime(cudaMemcpyAsync(output_data_segmentation_host, output_data_segmentation_device, sizeof(output_data_segmentation_host), cudaMemcpyDeviceToHost, _stream));
    // 等待直到 _stream 流的工作完成
    checkRuntime(cudaStreamSynchronize(_stream));
}

main.cpp

#include <iostream>
#include <string>
#include <chrono>
#include <opencv2/opencv.hpp>
#include "IPMdet.h"
using namespace std;

int main() {
    cv::Mat image = cv::imread("/mnt/sdb/ipm_sample_datas/detection/train/apa_det_train/ADAS_20200113-132415_216_1212__00006732_28x14_03_ipm.jpg");
    cout << image.size << endl;

    cv::Mat input;
    image.convertTo(input, CV_32FC3);

    string onnx_path = "/mnt/sdb/HAT-feature-ipm-multitask-release/output_model/model_best_22.trt";
    IPMdet ipmdet = IPMdet(onnx_path);
    auto start0 = chrono::high_resolution_clock::now();
    ipmdet.infer_trtmodel((float*)input.data);
    
    for(int i=0; i<100; i++){
        ipmdet.infer_trtmodel((float*)input.data);
    }
    auto end0 = chrono::high_resolution_clock::now();

    string onnx_path_fp16 = "/mnt/sdb/HAT-feature-ipm-multitask-release/output_model/model_best_22_fp16.trt";
    IPMdet ipmdet_fp16 = IPMdet(onnx_path_fp16);
    ipmdet_fp16.infer_trtmodel((float*)input.data);

    auto start1 = chrono::high_resolution_clock::now();
    for(int j=0; j<100; j++){
        ipmdet_fp16.infer_trtmodel((float*)input.data);
    }
    auto end1 = chrono::high_resolution_clock::now();

    chrono::duration<double, std::milli> elapsed0 = end0 - start0;
    chrono::duration<double, std::milli> elapsed1 = end1 - start1;
    cout << "FP32 time taken: " << elapsed0.count() << " ms" << endl;
    cout << "FP16 time taken: " << elapsed1.count() << " ms" << endl << endl;
}

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

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

相关文章

爱普生可编程晶振SG-8200CJ特性与应用

在高速发展的电子技术领域&#xff0c;时钟源作为电子系统的“心脏”&#xff0c;其性能直接影响设备的稳定性与可靠性。爱普生SG-8200CJ可编程晶振凭借其优秀的频率精度、低抖动性能及广泛的环境适应性&#xff0c;正成为众多领域的得力之选&#xff0c;为各类设备的高效运行与…

ubuntu中用docker下载opengauss

1.安装docker sudo apt install docker.io2.拉取opengauss镜像 sudo docker pull enmotech/opengauss3.创建容器 sudo docker run --name opengauss --privilegedtrue -d -e GS_PASSWORDEnmo123 enmotech/opengauss:latest3.5.如果容器停止运行&#xff08;比如关机了&#…

tslib

使用tslib来读取触摸屏的数据&#xff0c;可以得到原始数据&#xff0c;也可以在原始数据的基础上进行一些处理。比如有些触摸屏比较不稳定&#xff0c;跳动比较大&#xff0c;我们可以将跳动比较大的数据给删除掉 plugins里面的每个文件都会被编译成一个动态库&#xff0c;这些…

MoonSharp 文档三

MoonSharp 文档一-CSDN博客 MoonSharp 文档二-CSDN博客 MoonSharp 文档四-CSDN博客 MoonSharp 文档五-CSDN博客 7.Proxy objects&#xff08;代理对象&#xff09; 如何封装你的实现&#xff0c;同时又为脚本提供一个有意义的对象模型 官方文档&#xff1a;MoonSharp 在实际…

linux和windows之间的复制

第一步 sudo apt-get autoremove open-vm-tools第二步 sudo apt-get update第三步 sudo apt-get install open-vm-tools-desktop按y 第四步 重启虚拟机&#xff0c;终端下输入 rebootLinux下 按“ CtrlShiftC V ”复制粘贴 Windows下按“ Ctrl C V ”复制粘贴

在资源有限中逆势突围:从抗战智谋到寒门高考的破局智慧

目录 引言 一、历史中的非对称作战&#xff1a;从李牧到八路军的智谋传承 李牧戍边&#xff1a;古代军事博弈中的资源重构 八路军的游击战&#xff1a;现代战争中的智慧延续 二、创业界的逆袭之道&#xff1a;小米与拼多多的资源重构 从MVP到杠杆解 社交裂变与资源错配 …

javascript-es6 (六)

编程思想 面向过程 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候再一个一个的依次 调用就可以了 就是按照我们分析好了的步骤&#xff0c;按照步骤解决问题 面向对象 面向对象是把事务分解成为一个个对象&…

Spring AI 1.0.0 M6新特性MCP

Spring AI 1.0.0 M6新特性MCP 前言一、MCP是什么&#xff1f;&#xff08;Model Context Protocol&#xff09;二、它的发展历程三、核心架构四、MCP Java SDK的核心能力Java MCP实现遵循三层架构&#xff1a;MCP客户端MCP服务器总结MCP 的核心能力总结多种传输选项 搭建服务端…

【性能测试入门_01性能测试jmeter基础实操场景详解】

一、应用项目如何部署在服务器上 可以将项目进行打jar包 双击install&#xff0c;控制台就会打印打包的过程 最终打的包&#xff0c;会存放在打印的那个路径下 这个jar包&#xff0c;就是开发人员开发好&#xff0c;直接可以部署的 可以通过命令&#xff0c;在终端直接启动这…

跨越时空的对话:图灵与GPT-4聊AI的前世今生

&#xff08;背景&#xff1a;虚拟咖啡厅&#xff0c;图灵身着1950年代西装&#xff0c;端着一杯热茶&#xff0c;GPT-4以全息投影形态坐在对面&#xff09; 图灵&#xff08;喝了口茶&#xff09;&#xff1a;“听说你能写诗&#xff1f;我当年在布莱切利园破解Enigma时&…

如何通过 Seatunnel 实现 MySQL 到 OceanBase的数据迁移同步

1. 准备传输工具 本方案采用 Apache Seatunnel&#xff08;简称seatunnel&#xff09;进行MySQL 到 OceanBase 的数据迁移和同步&#xff0c;出于对方案轻量性的考量&#xff0c;我们采用其内置的Zeta引擎来实现&#xff0c;包括全量同步、离线增量同步&#xff0c;以及CDC方案…

软件设计模式之简单工厂模式

目录 一.类图&#xff08;手机生产&#xff09; 二.代码实现 Iphone类&#xff1a; Vivo类&#xff1a; Mobile类&#xff1a; MobileFactory类&#xff1a; Client类&#xff1a; 一.类图&#xff08;手机生产&#xff09; 补充&#xff1a;MobileFactory也可以直接指向…

LiveGBS流媒体平台GB/T28181常见问题-视频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验

LiveGBS流媒体平台GB/T28181常见问题频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验&#xff1f; 1、安全控制1.1、HTTP接口鉴权1.2、流地址鉴权 2、401 Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.…

Java 大视界 -- 区块链赋能 Java 大数据:数据可信与价值流转(84)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

接口自动化入门 —— Http的请求头,请求体,响应码解析!

在接口自动化测试中&#xff0c;HTTP请求头、请求体和响应码是核心组成部分。理解它们的作用、格式和解析方法对于进行有效的接口测试至关重要。以下是详细解析&#xff1a; 1. HTTP 请求头&#xff08;Request Header&#xff09; 1.1 作用 请求头是客户端向服务器发送的附加…

upload-labs(1-20)详解(专业版)

目录 第1关 第2关 第3关 第4题 第5题 第6题 第7题 第8题 第9题 第10题 第11题 第12题 第13题 第1关 查看源码 在第一关是一个前端js的一个后缀识别&#xff1a;当不为jpg、png、gif时候出现弹窗 检查源码 将return checkFile() 改为 return ture 就可以将php顺利…

Linux 生成静态库

文章目录 前提小知识生成和使用.a库操作步骤 在应用程序中&#xff0c;有一些公共的代码需要反复使用的&#xff0c;可以把这些代码制作成“库文件”&#xff1b;在链接的步骤中&#xff0c;可以让链接器在“库文件”提取到我们需要使用到的代码&#xff0c;复制到生成的可执行…

ARMV8的64位指令

一、介绍 ARMv8 体系结构最大的改变是增加了一个新的 64 位的指令集&#xff0c;这是早前 ARM 指令集 的有益补充和增强。它可以处理 64 位宽的寄存器和数据并且使用 64 位的指针来访问内存。这 个新的指令集称为 A64 指令集&#xff0c;运行在 AArch64 状态。 ARMv8 兼容旧的…

PawSQL for TDSQL:腾讯云TDSQL数据库性能优化全攻略

TDSQL 作为腾讯云推出的分布式数据库&#xff0c;凭借其高扩展性、高可用性和高性能等优势&#xff0c;广泛应用于金融、互联网、政务等领域。随着业务的不断增长和数据量的爆炸式增长&#xff0c;如何优化 TDSQL 数据库的性能&#xff0c;成为众多企业和开发者面临的挑战。本文…

T-SQL 语言基础:表运算符与联接

目录 介绍表运算符概述交叉联接内联接外联接联接实例总结引用 1. 介绍 在这篇博客中&#xff0c;主要涉及 T-SQL 中的表运算符与联接。联接操作是 SQL 查询中最常用的操作之一&#xff0c;它允许我们在多个表之间进行数据关联。通过了解不同类型的联接及其应用场景&#xff…