TensorRT:自定义插件学习与实践 001

news2025/1/10 23:54:46

文章简述

      本文简单列出了编写Tensorrt插件所需要的关键方法,分为两个部分,一是插件类的具体实现方法,另外是插件工厂的调用方法,插件类最终将编译为.so文件,使用时在c++或python中调用,所以插件类的方法调用在其他部分,在本文中难以直观的体现调用流程,需编写并运行代码,体验各个方法在插件生命周期中的作用。关于插件工厂的构造与调用在本文进行了解释。本文参考如下:

  • 1.大牛的网络笔记

  • 实现TensorRT自定义插件(plugin)自由!

  • https://github.com/zerollzeng/tiny-tensorrt

  • EasyInference 3.3.1 plugin 例子和原理 2021-02-18

  • 2.官方文档

  • 插件的官方文档:TensorRT有一个插件接口,允许应用程序 提供 TensorRT 本身不支持的操作的实现。插件 在 TensorRT 的 PluginRegistry 中创建和注册的可以是 由 ONNX 解析器在转换网络时找到。

  • 3.官方提供的插件例子:

  • https://github.com/NVIDIA/TensorRT/tree/master/plugin

  • https://github.com/NVIDIA/TensorRT/tree/release/7.2/plugin

  • https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/instanceNormalizationPlugin/instanceNormalizationPlugin.cpp

  • https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/instanceNormalizationPlugin/instanceNormalizationPlugin.h

在这里插入图片描述

  • class InstanceNormalizationPlugin final : public nvinfer1::IPluginV2DynamicExt 继承IPluginV2DynamicExt,是插件类,用于写插件具体的实现
  • class InstanceNormalizationPluginCreator : public BaseCreator 继承BaseCreator,是插件工厂类,用于根据需求创建该插件
  • class LReLU : public BasePluginhttps://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/leakyReluPlugin/lReluPlugin.h#L32

流程简述

在这里插入图片描述

// https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/fcPlugin/fcPlugin.cpp#L570-L579
// IPluginV2 Methods  
const char* FCPluginDynamic::getPluginType() const
{
    return FC_NAME;
}

const char* FCPluginDynamic::getPluginVersion() const
{
    return FC_VERSION;
}

//https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/fcPlugin/fcPlugin.cpp#L646-L654
const char* FCPluginDynamicCreator::getPluginName() const
{
    return FC_NAME;
}

const char* FCPluginDynamicCreator::getPluginVersion() const
{
    return FC_VERSION;
}
//https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/fcPlugin/fcPlugin.cpp#L49
REGISTER_TENSORRT_PLUGIN(FCPluginDynamicCreator);

=============

// https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/include/NvInferRuntimeCommon.h#L1351-L1354
//!
//! \brief Return the plugin registry
//!
extern "C" TENSORRTAPI nvinfer1::IPluginRegistry* getPluginRegistry();

插件类

父类

  • class MyCustomPlugin final : public nvinfer1::IPluginV2DynamicExt Ext类 IPluginV2DynamicExt
    在这里插入图片描述

  • IPluginV2DynamicExt中有很多纯虚函数,描述了继承这个类的函数规范,继承时必须要重写。

注:

TensorRT版本混合精度动态大小输入Requires extended runtimeexample
IPluginV2Ext5.1LimitedNoNo
IPluginV2IOExt6.0.1GeneralNoNohttps://github1s.com/NVIDIA/TensorRT/blob/release/7.2/samples/opensource/sampleUffPluginV2Ext/sampleUffPluginV2Ext.cpp#L337
IPluginV2DynamicExt6.0.1GeneralYesYes

IPluginV2插件的工作流

parse phase/ parse阶段

  • 在模型的parse阶段会通过CustomPlugin(const Weights *weights, int nbWeights)创建模型中每一个自定义层的实例,

  • 在这个阶段还会调用到getNbOutputs()和getOutputDimensions()来获取自定义层的输出tensor个数和维度。这个步骤的目的是为了构建整一个模型的工作流.如果自定义层的输出个数和维度跟其他层匹配不上,parse就会失败.所以如果你的自定义层在parse阶段就parse失败了,可以先检查一下这两个函数的实现.

  • 这个阶段创建的CustomPlugin实例会在engine构建阶段(下一阶段)被析构掉.

build engine phase / engine构建阶段

  • engine构建阶段会再次通过CustomPlugin(const Weights *weights, int nbWeights)创建自定义层的实例.然后调用supportFormat()函数来检查自定义层的支持的Datatype和PluginFormat, 在build的过程中,会调用configureWithFormat,根据设定的类型(见参数)对插件进行配置.调用完这个函数之后,自定义层内部的状态和变量应该被配置好了.在这里也会调用getWorksapceSize(),但是这个函数不怎么重要.最后会调用initialize(),进行初始化.此时已经准备好所有准备的数据和参数可以进行执行了.构建结束后当调用builder, network或者 engine的destroy()函数时,会调用CustomPlugin的destroy()方法析构掉CustomPlugin对象.
InstanceNormalizationPlugin::InstanceNormalizationPlugin(
    float epsilon, nvinfer1::Weights const& scale, nvinfer1::Weights const& bias)
    : _epsilon(epsilon)
    , _nchan(scale.count)
    , _d_scale(nullptr)
    , _d_bias(nullptr)
    , _d_bytes(0)
{
    ASSERT(scale.count == bias.count);
    if (scale.type == nvinfer1::DataType::kFLOAT)
    {
        _h_scale.assign((float*) scale.values, (float*) scale.values + scale.count);
    }
    else if (scale.type == nvinfer1::DataType::kHALF)
    {
        _h_scale.reserve(_nchan);
        for (int c = 0; c < _nchan; ++c)
        {
            unsigned short value = ((unsigned short*) scale.values)[c];
            _h_scale.push_back(__internal_half2float(value));
        }
    }
    else
    {
        throw std::runtime_error("Unsupported scale dtype");
    }
    if (bias.type == nvinfer1::DataType::kFLOAT)
    {
        _h_bias.assign((float*) bias.values, (float*) bias.values + bias.count);
    }
    else if (bias.type == nvinfer1::DataType::kHALF)
    {
        _h_bias.reserve(_nchan);
        for (int c = 0; c < _nchan; ++c)
        {
            unsigned short value = ((unsigned short*) bias.values)[c];
            _h_bias.push_back(__internal_half2float(value));
        }
    }
    else
    {
        throw std::runtime_error("Unsupported bias dtype");
    }
}
size_t InstanceNormalizationPlugin::getWorkspaceSize(const nvinfer1::PluginTensorDesc* inputs, int nbInputs,
    const nvinfer1::PluginTensorDesc* outputs, int nbOutputs) const
{
    return 0;
}
int InstanceNormalizationPlugin::initialize()
{
    return 0;
}

  • 无bias矩阵乘法(fcPlugin)插件的getWorkspaceSize例子

这个函数需要返回这个插件op需要中间显存变量的实际数据大小(bytesize),这个是通过TensorRT的接口去获取,是比较规范的方式。

我们需要在这里确定这个op需要多大的显存空间去运行,在实际运行的时候就可以直接使用TensorRT开辟好的空间而不是自己去申请显存空间。

// https://zhuanlan.zhihu.com/p/297002406
size_t MyCustomPlugin::getWorkspaceSize(const nvinfer1::PluginTensorDesc* inputs, int nbInputs, const nvinfer1::PluginTensorDesc* outputs, int nbOutputs) const 
{ 
    // 计算这个op前向过程中你认为需要的中间显存数量
    size_t need_num;
    return need_num * sizeof(float);
}

save engine phase / 引擎保存阶段

保存引擎到序列化文件会调用getSerializationSize()函数来获取序列化所需要的空间,在保存的过程中会调用serialize()函数将自定义层的相关信息序列化到引擎文件.

// https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/instanceNormalizationPlugin/instanceNormalizationPlugin.cpp#L129
InstanceNormalizationPlugin::InstanceNormalizationPlugin(void const* serialData, size_t serialLength)
{
    deserialize_value(&serialData, &serialLength, &_epsilon);
    deserialize_value(&serialData, &serialLength, &_nchan);
    deserialize_value(&serialData, &serialLength, &_h_scale);
    deserialize_value(&serialData, &serialLength, &_h_bias);
}

engine running phase / 引擎推理阶段

  • 在这个阶段会调用用enqueue()进行模型推理

  • https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/instanceNormalizationPlugin/instanceNormalizationPlugin.cpp#L172

int InstanceNormalizationPlugin::enqueue(const nvinfer1::PluginTensorDesc* inputDesc,
    const nvinfer1::PluginTensorDesc* outputDesc, const void* const* inputs, void* const* outputs, void* workspace,
    cudaStream_t stream)
{
    nvinfer1::Dims input_dims = inputDesc[0].dims;
    int n = input_dims.d[0];
    int c = input_dims.d[1];
    int h = input_dims.d[2];
    int w = input_dims.d[3] > 0 ? input_dims.d[3] : 1;
    size_t nchan_bytes = c * sizeof(float);

    // Note: We repeat the data for each batch entry so that we can do the full
    //       computation in a single CUDNN call in enqueue().
    if (_d_bytes < n * nchan_bytes)
    {
        cudaFree(_d_bias);
        cudaFree(_d_scale);
        _d_bytes = n * nchan_bytes;
        CHECK_CUDA(cudaMalloc((void**) &_d_scale, _d_bytes));
        CHECK_CUDA(cudaMalloc((void**) &_d_bias, _d_bytes));
    }
    for (int i = 0; i < n; ++i)
    {
        CHECK_CUDA(cudaMemcpy(_d_scale + i * c, _h_scale.data(), nchan_bytes, cudaMemcpyHostToDevice));
        CHECK_CUDA(cudaMemcpy(_d_bias + i * c, _h_bias.data(), nchan_bytes, cudaMemcpyHostToDevice));
    }

    CHECK_CUDNN(cudnnSetTensor4dDescriptor(_b_desc, CUDNN_TENSOR_NCHW, CUDNN_DATA_FLOAT, 1, n * c, 1, 1));
    cudnnDataType_t cudnn_dtype{};
    CHECK_CUDNN(convert_trt2cudnn_dtype(inputDesc[0].type, &cudnn_dtype));
    CHECK_CUDNN(cudnnSetTensor4dDescriptor(_x_desc, CUDNN_TENSOR_NCHW, cudnn_dtype, 1, n * c, h, w));
    CHECK_CUDNN(cudnnSetTensor4dDescriptor(_y_desc, CUDNN_TENSOR_NCHW, cudnn_dtype, 1, n * c, h, w));
    float alpha = 1;
    float beta = 0;
    void const* x_ptr = inputs[0];
    void* y_ptr = outputs[0];
    CHECK_CUDNN(cudnnSetStream(_cudnn_handle, stream));
    // Note: Use of CUDNN_BATCHNORM_SPATIAL_PERSISTENT can cause numerical
    //       overflows (NaNs) for fp32 data in some circumstances. The lower-
    //       performance CUDNN_BATCHNORM_SPATIAL should be used if this is not
    //       acceptable.
    CHECK_CUDNN(cudnnBatchNormalizationForwardTraining(_cudnn_handle, CUDNN_BATCHNORM_SPATIAL_PERSISTENT, &alpha, &beta,
        _x_desc, x_ptr, _y_desc, y_ptr, _b_desc, _d_scale, _d_bias, 1., nullptr, nullptr, _epsilon, nullptr, nullptr));
    return 0;
}
  • https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/fcPlugin/fcPlugin.cpp#L515
  • static_cast是一个强制类型转换操作符。强制类型转换,也称为显式转换,C++中强制类型转换操作符有static_cast、dynamic_cast、const_cast、reinterpert_cast四个

inference with engine file / 使用引擎文件进行推理

在使用引擎文件进行推理的过程中,从序列化文件恢复权重和参数,所以会先调用SamplePlugins(const void *data, size_t length)读取自定义层的相关信息,然后调用initialize() 进行初始化.在推理的过程中调用enqueue()进行推理.推理结束后如果在调用engine的destroy方法的时候会调用terminate()函数,释放 掉initialize()申请的资源.

  • 三个构造函数
    在这里插入图片描述
InstanceNormalizationPlugin(float epsilon, nvinfer1::Weights const& scale, nvinfer1::Weights const& bias);
InstanceNormalizationPlugin(float epsilon, const std::vector<float>& scale, const std::vector<float>& bias);
InstanceNormalizationPlugin(void const* serialData, size_t serialLength);
  • 一个析构函数
InstanceNormalizationPlugin::~InstanceNormalizationPlugin()
{
    terminate();// terminate函数就是释放这个op之前开辟的一些显存空间:
}

插件工厂类

class InstanceNormalizationPluginCreator : public BaseCreator
{
public:
    InstanceNormalizationPluginCreator();

    ~InstanceNormalizationPluginCreator() override = default;

    const char* getPluginName() const override;

    const char* getPluginVersion() const override;

    const PluginFieldCollection* getFieldNames() override;

    IPluginV2DynamicExt* createPlugin(const char* name, const nvinfer1::PluginFieldCollection* fc) override;

    IPluginV2DynamicExt* deserializePlugin(const char* name, const void* serialData, size_t serialLength) override;

private:
    static PluginFieldCollection mFC;
    static std::vector<PluginField> mPluginAttributes;
    std::string mNamespace;
};

cpp中有关InstanceNormalizationPluginCreator的代码

createPlugin

这个成员函数作用是通过PluginFieldCollection去创建plugin,将op需要的权重和参数一个一个取出来,
然后调用上文提到的第一个构造函数(返回指向插件的指针)去创建plugin(这个函数可能在最后调用注册的时候才会用到):

  • MyCustomPlugin(int in_channel, nvinfer1::Weights const& weight, nvinfer1::Weights const& bias);

  • 对应于下面代码块的 InstanceNormalizationPlugin* obj = new InstanceNormalizationPlugin(epsilon, scaleWeights, biasWeights);

// \plugin\instanceNormalizationPlugin\instanceNormalizationPlugin.cpp
IPluginV2DynamicExt* InstanceNormalizationPluginCreator::createPlugin(
    const char* name, const nvinfer1::PluginFieldCollection* fc)
{
    std::vector<float> scaleValues;
    std::vector<float> biasValues;
    float epsilon{};
    const PluginField* fields = fc->fields;
    for (int i = 0; i < fc->nbFields; ++i)
    {
        const char* attrName = fields[i].name;
        if (!strcmp(attrName, "epsilon"))
        {
            ASSERT(fields[i].type == PluginFieldType::kFLOAT32);
            epsilon = *(static_cast<const float*>(fields[i].data));
        }
        else if (!strcmp(attrName, "scales"))
        {
            ASSERT(fields[i].type == PluginFieldType::kFLOAT32);
            int size = fields[i].length;
            scaleValues.reserve(size);
            const auto* w = static_cast<const float*>(fields[i].data);
            for (int j = 0; j < size; j++)
            {
                scaleValues.push_back(*w);
                w++;
            }
        }
        else if (!strcmp(attrName, "bias"))
        {
            ASSERT(fields[i].type == PluginFieldType::kFLOAT32);
            int size = fields[i].length;
            biasValues.reserve(size);
            const auto* w = static_cast<const float*>(fields[i].data);
            for (int j = 0; j < size; j++)
            {
                biasValues.push_back(*w);
                w++;
            }
        }
    }

    Weights scaleWeights{DataType::kFLOAT, scaleValues.data(), (int64_t) scaleValues.size()};
    Weights biasWeights{DataType::kFLOAT, biasValues.data(), (int64_t) biasValues.size()};

    InstanceNormalizationPlugin* obj = new InstanceNormalizationPlugin(epsilon, scaleWeights, biasWeights);
    obj->setPluginNamespace(mNamespace.c_str());
    return obj;
}

=========================================================================

\include\NvInferRuntimeCommon.h
struct PluginFieldCollection
{
    int32_t nbFields;          //!< Number of PluginField entries
    const PluginField* fields; //!< Pointer to PluginField entries
};

=========================================================================

//! \include\NvInferRuntimeCommon.h
//! \class PluginField
//!
//! \brief Structure containing plugin attribute field names and associated data
//! This information can be parsed to decode necessary plugin metadata
//!
//!
class PluginField
{
public:
    //!
    //! \brief Plugin field attribute name
    //!
    const char* name{nullptr};
    //!
    //! \brief Plugin field attribute data
    //!
    const void* data{nullptr};
    //!
    //! \brief Plugin field attribute type
    //! \see PluginFieldType
    //!
    PluginFieldType type{PluginFieldType::kUNKNOWN};
    //!
    //! \brief Number of data entries in the Plugin attribute
    //!
    int32_t length{0};

    PluginField(const char* name_ = nullptr, const void* data_ = nullptr, const PluginFieldType type_ = PluginFieldType::kUNKNOWN, int32_t length_ = 0)
        : name(name_)
        , data(data_)
        , type(type_)
        , length(length_)
    {
    }
};

=====================================================================================

#include "instanceNormalizationPlugin.h"
#include <cuda_fp16.h>
#include <stdexcept>

using namespace nvinfer1;
using nvinfer1::plugin::InstanceNormalizationPlugin;
using nvinfer1::plugin::InstanceNormalizationPluginCreator;

PluginFieldCollection InstanceNormalizationPluginCreator::mFC{};
std::vector<PluginField> InstanceNormalizationPluginCreator::mPluginAttributes;


//同一文件中的所有代码都可以看到未命名命名空间中的标识符,但标识符以及命名空间本身在该文件外部不可见   https://learn.microsoft.com/en-us/cpp/cpp/namespaces-cpp?view=msvc-170
namespace
{
constexpr const char* INSTANCE_PLUGIN_VERSION{"1"};
constexpr const char* INSTANCE_PLUGIN_NAME{"InstanceNormalization_TRT"};
} // namespace

PluginFieldCollection InstanceNormalizationPluginCreator::mFC{};
std::vector<PluginField> InstanceNormalizationPluginCreator::mPluginAttributes;
// InstanceNormalizationPluginCreator methods
InstanceNormalizationPluginCreator::InstanceNormalizationPluginCreator()
{
    mPluginAttributes.emplace_back(PluginField("epsilon", nullptr, PluginFieldType::kFLOAT32, 1));
    mPluginAttributes.emplace_back(PluginField("scales", nullptr, PluginFieldType::kFLOAT32, 1));
    mPluginAttributes.emplace_back(PluginField("bias", nullptr, PluginFieldType::kFLOAT32, 1));

    mFC.nbFields = mPluginAttributes.size();
    mFC.fields = mPluginAttributes.data();
}


const char* InstanceNormalizationPluginCreator::getPluginName() const
{
    return INSTANCE_PLUGIN_NAME;
}

const char* InstanceNormalizationPluginCreator::getPluginVersion() const
{
    return INSTANCE_PLUGIN_VERSION;
}

const PluginFieldCollection* InstanceNormalizationPluginCreator::getFieldNames()
{
    return &mFC;
}

deserializePlugin

这个函数会被onnx-tensorrt的一个叫做TRT_PluginV2的转换op调用,这个op会读取onnx模型的data数据将其反序列化到network中。

IPluginV2DynamicExt* InstanceNormalizationPluginCreator::deserializePlugin(
    const char* name, const void* serialData, size_t serialLength)
{
    InstanceNormalizationPlugin* obj = new InstanceNormalizationPlugin{serialData, serialLength};
    obj->setPluginNamespace(mNamespace.c_str());
    return obj;
}

关于plugin的注册

简单说下plugin的注册流程。

注册

关于plugin的注册

在这里插入图片描述

//https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/include/NvInferRuntimeCommon.h#L1374-L1377
#define REGISTER_TENSORRT_PLUGIN(name) \
    static nvinfer1::PluginRegistrar<name> pluginRegistrar##name {}

} // namespace nvinfer1
  • 在 bool initLibNvInferPlugins(void* logger, const char* libNamespace)加入initializePlugin<***>(logger, libNamespace);即可
// \plugin\InferPlugin.cpp 
// 参考https://github1s.com/NVIDIA/TensorRT/blob/release/7.2/plugin/InferPlugin.cpp#L175
extern "C"
{
    bool initLibNvInferPlugins(void* logger, const char* libNamespace)
    {
    	******
		initializePlugin<nvinfer1::plugin::InstanceNormalizationPluginCreator>(logger, libNamespace);
        ******
        return true;
    }
} // extern "C"
  • 注册过程会“将creater对象放到stack和list的存储结构”中

注册过程

template <typename CreatorType>
void initializePlugin(void* logger, const char* libNamespace)
{
    PluginCreatorRegistry::getInstance().addPluginCreator<CreatorType>(logger, libNamespace);
}
template <typename CreatorType>
    void addPluginCreator(void* logger, const char* libNamespace)
    {
        // Make accesses to the plugin creator registry thread safe
        std::lock_guard<std::mutex> lock(mRegistryLock);

        std::string errorMsg;
        std::string verboseMsg;

        std::unique_ptr<CreatorType> pluginCreator{new CreatorType{}}; //TODO 在这里创建了对象
        pluginCreator->setPluginNamespace(libNamespace); //应该会调用BaseCreator的方法

        nvinfer1::plugin::gLogger = static_cast<nvinfer1::ILogger*>(logger);
        std::string pluginType = std::string{pluginCreator->getPluginNamespace()}
            + "::" + std::string{pluginCreator->getPluginName()} + " version "
            + std::string{pluginCreator->getPluginVersion()};

        if (mRegistryList.find(pluginType) == mRegistryList.end())
        {
            bool status = getPluginRegistry()->registerCreator(*pluginCreator, libNamespace);
            if (status)
            {
                mRegistry.push(std::move(pluginCreator)); // 栈 mRegistry: std::stack<std::unique_ptr<IPluginCreator>> mRegistry;   
					// IPluginCreator 是BaseCreator的父类
	                                                                 // 移动构造 https://en.cppreference.com/w/cpp/utility/move
                mRegistryList.insert(pluginType);
                verboseMsg = "Registered plugin creator - " + pluginType;
            }
            else
            {
                errorMsg = "Could not register plugin creator -  " + pluginType;
            }
        }
        else
        {
            verboseMsg = "Plugin creator already registered - " + pluginType;
        }

        if (logger)
        {
            if (!errorMsg.empty())
            {
                nvinfer1::plugin::gLogger->log(ILogger::Severity::kERROR, errorMsg.c_str());
            }
            if (!verboseMsg.empty())
            {
                nvinfer1::plugin::gLogger->log(ILogger::Severity::kVERBOSE, verboseMsg.c_str());
            }
        }
    }

调用注册

在加载NvInferRuntimeCommon.h头文件的时候会得到一个getPluginRegistry,这里类中包含了所有已经注册了的IPluginCreator,在使用的时候我们通过getPluginCreator函数得到相应的IPluginCreator。

  • https://zhuanlan.zhihu.com/p/460901713

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

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

相关文章

PyQt5

最近在学习pyqt5&#xff0c; 使用pyqt5的时候出现了一些莫名奇妙的问题&#xff0c;解决之后决定把它记录下来&#xff0c;方面pyqt5的初学者使用。 每个问题会按照如下方式进行描述 1、问题描述&#xff1a; 2、解决方法&#xff1a; 问题1&#xff1a; 使用pyinstaller打…

计算机网络笔记:TCP三次握手和四次挥手过程

TCP是面向连接的协议&#xff0c;连接的建立和释放是每一次面向连接的通信中必不可少的过程。TCP连接的管理就是使连接的建立和释放都能正常地进行。 三次握手 TCP连接的建立—三次握手建立TCP连接 ① 若主机A中运行了一个客户进程&#xff0c;当它需要主机B的服务时&#xff0…

迁移学习

迁移学习 什么是迁移学习 迁移学习【斯坦福21秋季&#xff1a;实用机器学习中文版】 迁移学习&#xff08;Transfer Learning&#xff09;是一种机器学习方法&#xff0c;它通过将一个领域中的知识和经验迁移到另一个相关领域中&#xff0c;来加速和改进新领域的学习和解决问…

OS开源项目周报0105

由OpenDigg 出品的iOS开源项目周报第四期来啦。iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目&#xff0c;方便iOS开发人员便捷的找到自己需要的项目工具等。 Hero 酷炫的iOS动画引擎 Traits 实时修改原生iOS 应用属性 JSDBanTangHomeDemo 仿半糖首页…

【Git】‘git‘ 不是内部或外部命令,也不是可运行的程序

一、问题 我想利用git clone命令从github上下载项目源代码&#xff0c;发现报错&#xff1a; git 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。我用cmd跑一下git命令&#xff0c;发现报错&#xff1a; 二、问题分析 这个错误提示表明您的系统中没有安装…

Illustrator如何使用基础功能?

文章目录 0.引言1.菜单栏2.工具箱 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对软件界面基本功能进行阐述。    1…

第四章 数据关联分析方法

基本概念和方法 关联规则和算法应用 基本概念和术语 关联规则算法应用&#xff1a; 一个关联规则分析的例子—————超市购物篮分析 不要看 后面数字看不懂 项集&#xff1a;是指项的集合。包含k个项的项集称为k-项集 支持度&#xff1a;若A是一个项集&#xff0c;则A的…

Vue3 +TypeScript 引入 BabylonJs(Vue3实现3D)【一篇文章精通系列】

本文主要介绍如何使用Vue3和TypeScript引入BabylonJs技术实现3D效果。结合实际案例&#xff0c;详细讲解了如何在Vue3项目中引入BabylonJs&#xff0c;并了解其相关知识。通过本文的学习&#xff0c;相信读者可以轻松掌握Vue3实现3D效果以及BabylonJs的相关知识。 Vue3 TypeS…

天梯赛L1-001 ~ 010

&#x1f442; White Lie - Jhameel - 单曲 - 网易云音乐 &#x1f442; 丁丁猫儿 - 施鑫文月 - 单曲 - 网易云音乐 今年蓝桥 / 天梯都陪跑&#xff0c;希望明年&#xff0c;蓝桥杯省一&#xff08;CA组60分&#xff09;&#xff0c;天梯赛国三&#xff08;180分&#xff09;…

详细的实用技巧,让你轻松成为WEB自动化测试大师

目录 一、什么是WEB自动化测试 二、WEB自动化测试工具 三、SeleniumPython环境搭建 1. 安装Python解释器 2. 安装Selenium库 3. 下载浏览器驱动程序 4. 配置环境变量 四、WEB自动化测试实战 1. 编写测试脚本 2. 使用Page Object模式 3. 使用数据驱动测试 五、总结 …

【PowerDesigner】一款超好用的E-R图工具,快速构建出高质量的数据库结构,提高开发效率和代码质量

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: Mysql从入门到精通 近期目标&#xff1a;写好专栏的每一篇文章 文章目录…

IPsec IKE第一阶段主模式和野蛮模式

国密标准GMT 0022-2014 IPSec VPN 技术规范&#xff0c;IPsec IKE过程中交换类型的定义将主模式Main mode分配值为2&#xff0c;快速模式-quick mode分配值为32。标准中并没有提现分配值为4的交换类型。在实际应用中&#xff0c;IKE第一阶段经常会出现交换类型为4的情况&#x…

留守儿童爱心网站

摘要 随着留守儿童爱心管理的不断发展&#xff0c;留守儿童爱心网站在现实生活中的使用和普及&#xff0c;留守儿童爱心管理成为近年内出现的一个热门话题&#xff0c;并且能够成为大众广为认可和接受的行为和选择。设计留守儿童爱心网站的目的就是借助计算机让复杂的管理操作…

【C++】-关于类和对象的默认成员函数(中)-拷贝构造函数和赋值运算符重载函数

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树 ❤️‍&#x1fa79;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a8;作者gitee:gitee &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点…

带你玩转单向链表(学习必备)

本文概要 本篇文章主要介绍数据结构中单向链表各种操作&#xff0c;适合有C语言基础的同学&#xff0c;文中描述和代码示例很详细&#xff0c;干货满满&#xff0c;感兴趣的小伙伴快来一起学习吧&#xff01; &#x1f31f;&#x1f31f;&#x1f31f;个人简介&#x1f31f;&…

Redis入门到实战(实战篇)

Redis基础篇 实战篇Redis 开篇导读 亲爱的小伙伴们大家好&#xff0c;马上咱们就开始实战篇的内容了&#xff0c;相信通过本章的学习&#xff0c;小伙伴们就能理解各种redis的使用啦&#xff0c;接下来咱们来一起看看实战篇我们要学习一些什么样的内容 短信登录 这一块我们会使…

携程:一个被严重低估了的在线旅游平台?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 携程和旅游行业面临的不利因素依然存在 在疫情期间&#xff0c;由于全球范围内的旅行受到了限制&#xff0c;所以整个旅游行业都受到了巨大打击。休闲旅游和商务旅行也陷入了停顿&#xff0c;此后一直在缓慢恢复。 而当疫情…

Java8流式操作——最终操作

什么是最终操作&#xff1f; 当我们通过最终方法对流对象进行操作&#xff0c;说明stream流操作也完成&#xff0c;最后我们将对象汇总成一个结果&#xff08;总数、对象、集合……&#xff09; 方法 collect&#xff1a;将Stream中的元素汇总&#xff08;转化&#xff09;成…

探索三维世界【2】:Three.js 的 Texture 纹理

缤纷三维世界大揭秘&#xff1a;探索 Three.js 的 Texture 纹理 1、Texture纹理2、TextureLoader 纹理加载器2.1、创建纹理加载器2.2、纹理属性设置2.3、设置纹理渲染2.4、打光 3、完整代码与展示 1、Texture纹理 Texture 是 three.js 中的“纹理”概念。纹理是指将一张图像映…

ESP8266基于Lua开发使用U8g2模块驱动 i2c ssd1306 OLED显示

ESP8266基于Lua开发使用U8g2模块驱动 i2c ssd1306 OLED显示 &#x1f4cd;相关篇《ESP8266基于Lua开发点灯示例》 &#x1f4d6;U8g2对应的API接口函数&#xff1a;https://nodemcu.readthedocs.io/en/release/modules/u8g2/ &#x1f4fa;驱动显示效果&#xff1a; &#…