MixVpr重定位实战----onnx以及Tensorrt适配

0. 简介

对于深度学习而言,通过模型加速来嵌入进C++是非常有意义的,因为本身训练出来的pt文件其实效率比较低下,在讲完BEVDET和FastBEV后,这里我们将展开实战,从pt到onnx再到tensorrt,以MixVpr作为例子,来向读者展示如何去跑CUDA版本的MixVpr,并给出相关的Tensorrt推理代码。这里最近受到优刻得的使用邀请,正好解决了我在大模型和自动驾驶行业对GPU的使用需求。UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU,按时收费每卡1.88元,并附带200G的免费磁盘空间。暂时已经满足我的使用需求了,同时支持访问加速,独立IP等功能,能够更快的完成项目搭建。此外对于低性能的还有3080TI使用只需要0.88元,已经吊打市面上主流的云服务器了

在这里插入图片描述

对应的环境搭建已经在《如何使用共享GPU平台搭建LLAMA3环境(LLaMA-Factory)》、从BEVDET来学习如何生成trt以及如何去写这些C++内容介绍过了。对于自定义的无论是LibTorch还是CUDA这些都在《Ubuntu20.04安装LibTorch并完成高斯溅射环境搭建》这篇文章提到过了。这一章节我们来看一下怎么在平台上运行基于TensorRT的CUDA-FastBEV项目的。
在视觉定位任务中,重定位模块是至关重要的一环。尤其在深度学习中,重定位模块能够帮助系统精确地从庞大的数据库中快速找到与查询图像最匹配的图像。这个过程在自动驾驶、机器人导航以及增强现实等应用中尤为重要。通过使用深度学习模型,如 MixVPR,我们能够实现更加高效和精确的视觉重定位。

1. 安装环境

MIxVPR的环境安装还是比较简单的,只需要

pip intsall -r requirements.txt

在这里插入图片描述

MixVPR 使用的图像数据集可以从 GSV-Cities 仓库中获取。该数据集包含了来自多个城市的街景图像,适合作为视觉重定位任务的训练和测试数据集。

在本次实验中,我们选用了官方提供的预训练模型 resnet50_MixVPR_128_channels(64)_rows(2).ckpt。这是基于 ResNet50 架构的模型,并结合了 MixVPR 的特征聚合模块,能够高效地提取图像特征。

2. 导出 ONNX 模型

导出 ONNX 模型是实现跨平台推理的关键步骤。通过导出模型为 ONNX 格式,我们可以将模型应用于不同的硬件加速平台,如 TensorRT。

在这里插入图片描述

我们这里选用的是官方训练的resnet50_MixVPR_128_channels(64)_rows(2).ckpt ,然后导出对应的模型

from main import VPRModel
import torch
import onnxsim
import onnx
model = VPRModel(backbone_arch='resnet50',layers_to_crop=[4],agg_arch='MixVPR',agg_config={'in_channels': 1024,'in_h': 20,'in_w': 20,'out_channels': 64,'mix_depth': 4,'mlp_ratio': 1,'out_rows': 2},)state_dict = torch.load('resnet50_MixVPR_128_channels(64)_rows(2).ckpt')
model.load_state_dict(state_dict)
model.eval()
model.cpu()dummy_input = torch.randn(1, 3, 320, 320)
# 导出模型为ONNX格式
onnx_file_path = "vpr_model.onnx"
torch.onnx.export(model, dummy_input, # 模型输入的图片onnx_file_path, export_params=True, opset_version=12,  # 确保使用适合的ONNX opset版本do_constant_folding=True,  # 是否进行常量折叠优化input_names=['input'],  # 输入名称output_names=['output'],  # 输出名称dynamic_axes={'input': {0: 'batch_size'},  # 动态batch size'output': {0: 'batch_size'}})model_sim, flag = onnxsim.simplify(onnx_file_path)
if flag:onnx.save(model_sim, onnx_file_path)print("---------simplify onnx successfully---------")
else:print("---------simplify onnx failed-----------")

以上步骤中,我们完成了从 PyTorch 模型到 ONNX 格式的导出,并对 ONNX 模型进行了简化。简化后的 ONNX 模型不仅更加高效,还能够在部署时减少计算开销。

3. ONNX模块修改

这里值得一提的是,我们可以通过netron和onnx-graphsurgeon来完成模块的替换修改

在这里插入图片描述

在实际部署中,我们可能需要对模型的某些部分进行调整。例如,某些操作可能在某些硬件平台上表现不佳,或者我们需要替换特定的操作以实现更高效的推理。通过 Netron 可视化工具和 onnx-graphsurgeon 库,我们可以轻松地查看和修改 ONNX 模型中的节点。

以下是如何使用 onnx-graphsurgeon 替换一个 Sqrt 操作为 Log 操作的示例代码:

import onnx
import onnx_graphsurgeon as gs# 加载 ONNX 模型
onnx_model = onnx.load("vpr_model.onnx")# 将 ONNX 模型转换为 GraphSurgeon 的图
graph = gs.import_onnx(onnx_model)# 查找节点函数
def find_node_by_name(graph, node_name):for node in graph.nodes:if node.name == node_name:return nodereturn None# 查找 Sqrt 节点
sqrt_node = find_node_by_name(graph, "/aggregator/mix/mix.0/mix/mix.0/Sqrt")
sub_node = find_node_by_name(graph, "/aggregator/mix/mix.0/mix/mix.0/Sub")if sqrt_node:# 记录 `Sqrt` 节点的输入和输出sqrt_input = sqrt_node.inputs[0]  # 假设 Sqrt 节点有且仅有一个输入sqrt_output = sqrt_node.outputs[0]  # 假设 Sqrt 节点有且仅有一个输出print("Sqrt node found. Input:", sqrt_input, "Output:", sqrt_output)# 获取连接到 `Sqrt` 输出的节点(如 Log 节点)connected_nodes = [node for node in graph.nodes if sqrt_output in node.inputs]# 将 `Sqrt` 的输入直接连接到这些节点上for node in connected_nodes:node.inputs = [input if input != sqrt_output else sqrt_input for input in node.inputs]log_output = sqrt_output.copy()log_output.name = "new_Log_node"# 创建新的 Log 节点,连接 `Sqrt` 节点的输入和原来的输出new_log_node = gs.Node(op="Log", name="new_Log_node", inputs=[sqrt_input], outputs=[log_output])# sqrt_node.inputs[0] = "None"# 删除 `Sqrt` 节点graph.nodes.remove(sqrt_node)for node in graph.nodes:if node.name == "/aggregator/mix/mix.0/mix/mix.0/Div":node.inputs= [log_output,sub_node.outputs[0]]# 将新节点插入到图中breakgraph.nodes.append(new_log_node)# Cleanup 和重新排序graph.cleanup()graph.toposort()# # 打印修改后的图的节点# print("Graph after replacing the Sqrt node with Log node:")# for node in graph.nodes:#     print(node.name, node.op)# 将修改后的图导出为 ONNX 模型onnx_model_cut = gs.export_onnx(graph)onnx.save(onnx_model_cut, "vpr_model_cut.onnx")print("Model with Sqrt node replaced by Log node saved as vpr_model_cut.onnx")
else:print("Sqrt node could not be found, so the graph was not modified.")

通过这个例子,我们展示了如何在不影响模型整体结构的情况下,替换模型中的特定操作。这种灵活性使我们能够针对不同的硬件和应用需求,优化模型的性能。

4. TensorRT

为了加速模型推理,我们可以使用 NVIDIA 的 TensorRT。TensorRT 是一个用于深度学习推理优化的高性能库,它可以显著提高模型的推理速度。通过将 ONNX 模型转换为 TensorRT 引擎文件 (.engine),我们可以在 NVIDIA GPU 上高效地运行推理任务。

首先,我们需要确保安装了合适版本的 TensorRT。这里我们可以根据我们之前从BEVDET来学习如何生成trt以及如何去写这些C++内容这一篇文章提到如何来下载安装TensorRT。在本次实验中,我们使用的是 TensorRT-8.6.1.6 版本。安装好 TensorRT 后,可以通过以下命令将 ONNX 模型转换为 TensorRT 引擎:

trtexec --onnx='/xxx/MixVPR/vpr_model.onnx' --fp16 --saveEngine=mix1.engine --warmUp=500 --duration=10 

该命令将 vpr_model.onnx 模型转换为 FP16 精度的 TensorRT 引擎文件 mix1.engine。其中,–warmUp=500 和 --duration=10 用于测试引擎性能和稳定性。

到这一步,我们就已经完成了整个模型的生成,并且获得了一个 mix1.engine 的文件。接下来,我们就可以编写 TensorRT 的 C++ 代码来进行推理了。

在这里插入图片描述

到这一步,我们就已经完成了整个模型的生成。并且获得了一个mix1.engine的文件。然后就可以写Tensorrt的代码了

4.1 main.cpp

main.cpp 是实现 MixVPR 模型推理过程的核心文件。MixVPR 类封装了使用 TensorRT 进行推理、读取图像数据、运行图像检索算法并将结果可视化的整个过程。主要步骤包括加载 TensorRT 引擎、执行推理、使用 FAISS 进行相似度搜索,以及展示匹配结果。

#include "opencv2/opencv.hpp"#include "cuda_runtime_api.h"
#include "opencv2/core.hpp"
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include "opencv2/highgui.hpp"#include "faiss/IndexFlat.h"
#include "faiss/IndexHNSW.h"#include "tensorrt_utils.h"
#include <iostream>
class frame {
public:
//    std::string frame_id;
//    cv::String filename;
//    cv::Mat raw_image;std::vector<float> img_global_des_vec;std::vector<float> local_descriptor;std::vector<float> kpoints;std::vector<float> similarity;std::vector<std::pair<int,double>> top_k_ind;std::vector<std::pair<float, float>> landmarks;
};using idx_t = faiss::idx_t;class MixVPR {
public:MixVPR(const std::string& engine_path);void inference(const cv::String& filename, std::vector<frame>& frame_set, std::vector<float>& des_db);void test_in_datasets(const std::string filepath, std::vector<cv::String>& namearray, std::vector<frame>& frame_set, std::vector<float>& des_db);void sort_vec_faiss(std::vector<frame>& frame_set, std::vector<float>& des_db);void run(std::string& datapath);private:std::shared_ptr<nvinfer1::ICudaEngine> engine;std::shared_ptr<nvinfer1::IExecutionContext> execution_context;TRTLogger Logger;
};MixVPR::MixVPR(const std::string& engine_path) {static auto engine_data = load_file(engine_path);static auto runtime = make_nvshared(nvinfer1::createInferRuntime(Logger));engine = make_nvshared(runtime->deserializeCudaEngine(engine_data.data(), engine_data.size()));if (!engine) {printf("Deserialize cuda engine failed.\n");runtime->destroy();}execution_context = make_nvshared(engine->createExecutionContext());
}void MixVPR::inference(const cv::String& filename, std::vector<frame>& frame_set, std::vector<float>& des_db) {frame frame;cudaStream_t stream = nullptr;checkRuntime(cudaStreamCreate(&stream));int input_numel = 1 * 3 * 320 * 320;float* input_data_host = nullptr;float* input_data_device = nullptr;checkRuntime(cudaMallocHost(&input_data_host, input_numel * sizeof(float)));checkRuntime(cudaMalloc(&input_data_device, input_numel * sizeof(float)));cv::Mat image = cv::imread(filename);cv::resize(image, image, cv::Size(320, 320));unsigned char* pimage = image.data;for (int j = 0; j < image.cols * image.rows; ++j, pimage += 3) {input_data_host[j] = (pimage[2] / 255.0f - 0.485) / 0.229;input_data_host[j + image.cols * image.rows] = (pimage[1] / 255.0f - 0.456) / 0.224;input_data_host[j + 2 * image.cols * image.rows] = (pimage[0] / 255.0f - 0.406) / 0.225;}checkRuntime(cudaMemcpyAsync(input_data_device, input_data_host, input_numel * sizeof(float), cudaMemcpyHostToDevice, stream));float output_data_host[128];// 64*2float* output_data_device = nullptr;checkRuntime(cudaMalloc(&output_data_device, sizeof(output_data_host)));execution_context->setBindingDimensions(0, nvinfer1::Dims4{1, 3, 320, 320});float* bindings[] = {input_data_device, output_data_device};execution_context->enqueueV2((void**)bindings, stream, nullptr);checkRuntime(cudaMemcpyAsync(output_data_host, output_data_device, sizeof(output_data_host), cudaMemcpyDeviceToHost, stream));checkRuntime(cudaStreamSynchronize(stream));frame.img_global_des_vec.insert(frame.img_global_des_vec.begin(), output_data_host, output_data_host + 128);des_db.insert(des_db.end(), output_data_host, output_data_host + 128);cudaStreamDestroy(stream);cudaFreeHost(input_data_host);cudaFree(input_data_device);cudaFree(output_data_device);frame_set.push_back(frame);
}void MixVPR::test_in_datasets(const std::string filepath, std::vector<cv::String>& namearray, std::vector<frame>& frame_set, std::vector<float>& des_db) {cv::glob(filepath, namearray);std::sort(namearray.begin(), namearray.end());for (size_t i = 0; i < namearray.size(); i++) {inference(namearray[i], frame_set, des_db);
//        frame_set[i].raw_image = cv::imread(namearray[i]);
//        frame_set[i].frame_id = std::to_string(i);
//        frame_set[i].filename = namearray[i];}
}void MixVPR::sort_vec_faiss(std::vector<frame>& frame_set, std::vector<float>& des_db) {faiss::IndexFlatIP index(128);index.add(frame_set.size(), des_db.data());//按照索引添加数据for (auto& img : frame_set) {idx_t I[3];float D[3];index.search(1, img.img_global_des_vec.data(), 3, D, I);img.top_k_ind.push_back(std::make_pair(I[1], D[1]));}
}void MixVPR::run(std::string& datapath) {std::vector<cv::String> namearray;std::vector<frame> frame_set;std::vector<float> des_db;test_in_datasets(datapath, namearray, frame_set, des_db);sort_vec_faiss(frame_set, des_db);for (const auto& img : frame_set) {auto img0 = cv::imread(namearray[img.top_k_ind[0].first]);//输出结果auto img1 = cv::imread(namearray[&img - &frame_set[0]]);//对应图片cv::Mat img2;if (img0.empty() || img1.empty()) {std::cout << "Read image failed." << std::endl;continue;}cv::resize(img0, img0, cv::Size(720, 720));cv::resize(img1, img1, cv::Size(720, 720));if (img.top_k_ind[0].second > 0.75) {cv::hconcat(img1, img0, img2);cv::putText(img2, "Matched", cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);} else {cv::Mat empty_img = cv::Mat::zeros(img0.size(), img0.type());cv::hconcat(img1, empty_img, img2);cv::putText(img2, "Unmatched", cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);}cv::imshow("Result", img2);cv::waitKey(0);}
}int main(int argc, char** argv) {std::string datapath = "/xxx/MixVPR/Tensor/pic";MixVPR mixvpr("/xxxx/MixVPR/mix1.engine");mixvpr.run(datapath);return 0;
}

4.2 tensorrt_utils.cpp

这个文件实现了一些帮助函数和日志记录器,用于支持 TensorRT 推理流程。其中包括检查 CUDA 运行时错误的函数、日志记录器的实现(用于记录 TensorRT 的输出信息),以及从文件加载数据的函数。这些工具函数简化了在主程序中与 TensorRT 和 CUDA 进行交互的代码。

#include "tensorrt_utils.h"using namespace std;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\n", file, line, op, err_name, err_message);return false;}return true;}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 "unknow";}
}void TRTLogger::log(nvinfer1::ILogger::Severity severity, const nvinfer1::AsciiChar *msg) noexcept {if(severity <= Severity::kINFO){if(severity == Severity::kWARNING){printf("\033[33m%s: %s\033[0m\n", severity_string(severity),msg);}else if(severity <= Severity::kERROR){printf("\033[31m%s: %s\033[0m\n", severity_string(severity),msg);}else{printf("%s: %s\n", severity_string(severity), msg);}}
}vector<unsigned char> load_file(const string& file){ifstream in(file, ios::in | ios::binary);if (!in.is_open())return {};in.seekg(0, ios::end);size_t length = in.tellg();std::vector<uint8_t> data;if (length > 0){in.seekg(0, ios::beg);data.resize(length);in.read((char*)&data[0], length);}in.close();return data;}

4.3 tensorrt_utils.h

这是 tensorrt_utils.cpp 的头文件,声明了该文件中的函数和类。它定义了 TensorRT 日志记录器类 TRTLogger 以及一些帮助函数和宏(例如 checkRuntime 用于 CUDA 错误检查)。这些声明为主程序提供了所需的接口和工具函数支持

#ifndef VINS_FUSION_TENSORRT_UTILS_H
#define VINS_FUSION_TENSORRT_UTILS_H#pragma once
//tensorrt
#include "NvInfer.h"
#include "NvInferRuntime.h"//cuda
#include "cuda.h"
#include <cuda_runtime.h>//sys
#include "vector"
#include "fstream"
#include <memory>//tensorrt_tools
// #include "tensorrt_tools/trt_infer.hpp"
// #include "tensorrt_tools/trt_tensor.hpp"
// #include "tensorrt_tools/cuda_tools.hpp"
// #include "tensorrt_tools/ilogger.hpp"#define checkRuntime(op) __check_cuda_runtime((op), #op, __FILE__ ,__LINE__)using namespace std;bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line);inline const char* severity_string(nvinfer1::ILogger::Severity t);class TRTLogger : public nvinfer1::ILogger{
public:virtual void log(Severity severity, nvinfer1::AsciiChar const* msg)noexcept override;
};template<typename _T>
static shared_ptr<_T> make_nvshared(_T* ptr){//通过智能指针管理 nv 返回的指针参数,内存自动释放,避免泄漏return shared_ptr<_T>(ptr, [](_T* p){p->destroy();});
}vector<unsigned char> load_file(const string& file);
#endif //VINS_FUSION_TENSORRT_UTILS_H

4.4 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(MixVPR)set(CMAKE_CXX_STANDARD 14)# 找到 OpenCV
find_package(OpenCV REQUIRED)# 找到 CUDA
find_package(CUDA REQUIRED)
find_package(faiss REQUIRED)# 设置 TensorRT 的路径
set(TENSORRT_ROOT /xxxx/TensorRT-8.6.1.6.Linux.x86_64-gnu.cuda-11.8/TensorRT-8.6.1.6)
find_path(TENSORRT_INCLUDE_DIR NvInfer.hHINTS ${TENSORRT_ROOT}/include)
find_library(TENSORRT_LIBRARY nvinferHINTS ${TENSORRT_ROOT}/lib)
find_library(TENSORRT_RUNTIME_LIBRARY nvparsersHINTS ${TENSORRT_ROOT}/lib)# 包含 TensorRT 的头文件路径
include_directories(${TENSORRT_INCLUDE_DIR})# 包含 OpenCV 和 CUDA 的头文件路径
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(${CUDA_INCLUDE_DIRS})# 编译项目
add_executable(MixVPR main.cpp tensorrt_utils.cpp)# 链接 TensorRT, OpenCV 和 CUDA 的库
target_link_libraries(MixVPR ${TENSORRT_LIBRARY} ${TENSORRT_RUNTIME_LIBRARY} ${OpenCV_LIBS} ${CUDA_LIBRARIES} faiss)

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

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

相关文章

Java基于微信小程序的校园跑腿平台(V2.0)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Spring Boot图书馆管理系统:疫情中的管理利器

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了疫情下图书馆管理系统的开发全过程。通过分析疫情下图书馆管理系统管理的不足&#xff0c;创建了一个计算机管理疫情下图书馆管理系统的方案。文章介绍了疫情下图…

【CUDA】Branch Divergence and Unrolling Loop

目录 一、避免分支发散 1.1 并行规约问题 1.2 并行规约中的发散 二、UNrolling Loops 一、避免分支发散 控制流有时依赖于 thread 索引。同一个warp中&#xff0c;一个条件分支可能导致性能很差。通过重新组织数据获取模式可以减少或避免 warp divergence。具体问题查看下…

WIN系统解决小喇叭红色叉号的办法

WIN系统解决小喇叭红色叉号的办法 WIN系统提示无音频设备&#xff0c;无法播放声音&#xff0c;重装驱动无法解决 写在前面 前段时间搞了套6750GRE&#xff0c;用了两三个月&#xff0c;老是掉驱动&#xff0c;后面折腾了一下子&#xff0c;终于是不掉了。突然&#xff0c;某…

免费S3客户端工具大赏

首发地址&#xff08;欢迎大家访问&#xff09;&#xff1a;S3免费客户端工具大赏 1. S3 GUI GitHub地址&#xff1a;https://github.com/aminalaee/s3gui 简介&#xff1a;S3 GUI 是一款基于 Flutter 构建的免费开源 S3 桌面客户端&#xff0c;支持桌面、移动和网络平台。 特…

uniapp 购物弹窗组件 (微信小程序)

效果图&#xff0c;暂时只适应单规格&#xff0c;居中弹出和下方弹出&#xff0c;如需求不满足&#xff0c;请自行修改代码 &#xff08;更新于24/11/15) 居中显示效果 下方弹出效果 html <template><view class"" v-if"show":class"mod…

力扣-Mysql-1811 - 寻找面试候选人(中等)

一、题目来源 1811. 寻找面试候选人 - 力扣&#xff08;LeetCode&#xff09; 二、数据表结构 表: Contests -------------------- | Column Name | Type | -------------------- | contest_id | int | | gold_medal | int | | silver_medal | int | | bronze_medal | …

【C语言】volatile 防止编译的时候被优化

volatile 易变的 volatile是 C 和 C 中的一个类型修饰符&#xff0c;用于指示编译器该变量可能在程序之外被更改&#xff0c;因此不应对其进行优化。这在涉及硬件寄存器、信号处理或多线程编程时非常有用。 如果你做过单片机开发&#xff0c;你肯定写过这样的代码&#xff1a;…

makefile速通

makefile速通 文章目录 makefile速通1.基础显式规则隐含规则%*通配符 赋值 伪目标CFLAGS 2.函数wildcardpatsubst 3.项目实例 1.基础 显式规则 目标文件&#xff1a;依赖文件 [TAB] 指令隐含规则 % 任意* 所有通配符 符号含义$^所有依赖文件$所有目标文件$<所有依赖文…

面向服务的软件工程——巨详细讲解商务流程建模符号 (BPMN),一篇章带你入门BPMN!!!(week1)

文章目录 一、前言二、重点概念三、BPMN元素讲解流对象1.活动任务(Task)子流程(sub-process)多实例活动连接对象序列流消息流关联泳道Artifacts数据对象组(Group)事件(Events)启动事件中间事件结束事件边界事件边界事件1边界事件2小疑问?网关参考文献:一、前言 在我们…

模拟实现~简易通讯录

一.前言 今天给小伙伴们分享的是运用结构体以及指针等相关C语言知识实现一个简易版的通讯录。咱们写的通讯录的功能主要包括添加及删除联系人&#xff0c;修改联系人信息&#xff0c;显现所有联系人&#xff0c;查找已添加联系人&#xff0c;以及对联系人进行排序&#xff0c;…

0成本添加访问级监控

互联网的安全感这个概念源于阿里。顾名思义&#xff0c;让互联网的用户对于web产品能够产生足够的信任和依赖。特别是涉及到用户资金交易的站点&#xff0c;一次严重的用户资料泄露就可以彻底毁掉你的品牌。 然而当前阶段除了bat大部分互联网行业的企业对于网络安全给的重视都…

分布式系统稳定性建设-性能优化篇

分布式系统稳定性建设-性能优化篇 系统稳定性建设是系统工程的核心内容之一。以下是一些重要的方面: 架构设计: 采用模块化、松耦合的架构设计,以提高系统的可扩展性和可维护性。合理划分系统功能模块,降低单个模块的复杂度。定义清晰的接口和数据交换标准,确保各模块之间协调…

Web端高效BIM 3D可视化引擎HOOPS Communicator技术解析!

HOOPS Communicator是一款简单而强大的工业级高性能3D Web可视化开发包&#xff0c;专注于Web端工程图形渲染。采用了先进的流式加载方式&#xff0c;并支持服务端和客户端渲染&#xff0c;是可以在云端进行部署和无缝集成的新技术平台。 灵活且易于部署&#xff0c;可在以工程…

C/C++实现tcp客户端和服务端的实现(从零开始写自己的高性能服务器)

目录 tcp客户端通信流程 tcp客户端设计 1、创建通信对象 2、链接服务器 3、发送数据 / 读取数据 4、关闭通信 tcp服务端设计 1、创建通信对象 2、绑定服务器地址信息 3、设置服务器为监听模式 4、接收客户的链接请求 编写tcp客户端和服务端&#xff0c;实现双向通信…

C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程

引言 C 标准模板库&#xff08;STL&#xff09;提供了一组功能强大的容器类&#xff0c;用于存储和操作数据集合。不同的容器具有独特的特性和应用场景&#xff0c;因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C 的开发者来说&#xff0c;了解这些容…

快速上手并使用Muduo库

Muduo muduo库是基于主从reactor模型的高性能服务器&#xff08;高并发服务器&#xff09;框架 reactor模型&#xff1a;基于事件触发的模型&#xff08;基于epoll进行IP事件监控&#xff09; 主从reactor模型&#xff1a;将IO事件监控有进行进一步的层次划分 主reactor&#x…

深入解析【C++多态】:探索面向对象编程中的动态绑定与行为多样性和多态的核心概念与应用实践

&#x1f31f;个人主页&#xff1a;落叶 &#x1f31f;当前专栏: C专栏 目录 多态的概念 多态的定义及实现 实现多态还有两个必须重要条件 虚函数 虚函数的重写/覆盖 多态场景的⼀个选择题 虚函数重写的⼀些其他问题 协变(了解进行) 析构函数的重写 override 和 final关…

React Native Mac 环境搭建

下载 Mac 版Android Studio 下载 安装 JDK 环境 Flutter 项目实战-环境变量配置一 安装 Node.js 方式一 通过Node.js 官网下载 下载完成后点击安装包进行安装 安装完成

【Word】一键批量引用论文上标——将正文字体改为上标格式

【Word】一键批量引用论文上标——将正文字体改为上标格式 写在最前面Word一键批量引用论文上标技巧分享核心思路&#xff1a;Word 替换功能 通配符步骤详解1. 打开 Word 替换功能2. 输入通配符模式3. 设置替换格式为上标4. 批量替换 实际效果展示技巧扩展 &#x1f308;你好呀…