当前位置: 首页 > news >正文

llvm后端之指令选择源码分析

llvm后端之指令选择源码分析

  • 引言
  • 1 主要流程
    • 1.1 参数降级
    • 1.2 构建DAG
    • 1.3 类型合法化
    • 1.4 向量合法化
    • 1.5 DAG合法化
    • 1.6 DAG合并
  • 2 目标实现
    • 2.1 TargetLowering
    • 2.2 SelectionDAGISel

引言

llvm后端指令选择主要是class SelectionDAGISel的子类实现。整个过程将llvm IR转为有向无环图节点,通过系列替换合并,最终生成目标相关的DAG。最后再将目标DAG通过td规则匹配成目标指令MachineInstr。

llvm后端支持三种指令选择:

  1. SelectionDagISel : SDAG指令选择
  2. FastISel : 快速指令选择
  3. GlobalISel :全局指令选择

注:本文重点介绍SDAG实现,参考源码路径为 https://github.com/llvm/llvm-project/tree/release/10.x

1 主要流程

1.1 参数降级

参数降级,由SelectionDAGISel::LowerArguments(const Function &F)实现。其主要过程如下:

  1. 首先,构建TargetLowering::LowerFormalArguments方法的Ins参数;
  2. 调用由TargetLowering子类重写的LowerFormalArguments方法;
  3. 将Ins参数中打散成寄存器粒度的节点合并为参数分拆类型节点,由ISD::BUILD_PAIR节点合并;

构建Ins参数

  • 当函数不能降级return,则会插入一个存返回地址的额外参数。
  • 对于每个参数通过ComputeValueVTs函数计算每个参数的IR类型转换后的EVT类型;再对每个EVT类型,通过TargetLowering子类可重写的getRegisterTypeForCallingConv和getNumRegistersForCallingConv方法计算分配的需要分配的寄存器类型和个数,并构建ISD::InputArg放入Ins参数中;

注1 :ComputeValueVTs是根据IR阶段的Type类型生成EVT类型,过程中会对结构体和数组展开为基本类型,所以是一对多生成EVT类型;
注2 :getRegisterTypeForCallingConv和getNumRegistersForCallingConv是TargetLowering子类可重写方法,用于计算每个参数需要寄存器类型和个数。每个基本类型占用一个或多个寄存器。

调用LowerFormalArguments方法

  • 该方法由TargetLowering子类重写,用于计算参数拆分到寄存器粒度后,对应的SDValue节点;
  • 以RISCV为例,其实现主要是借助class CCState实现参数寄存器分配或参数栈分配;

注:一般地,在参数寄存器还可以分配的时候,会优先使用CCState::AllocateReg分配参数寄存器;否则才会通过CCState::AllocateStack分配栈空间。

合并参数寄存器

  • 对LowerFormalArguments返回的SDValue节点,通过ISD::BUILD_PAIR合并成更大类型,也就是ComputeValueVTs分拆的EVT类型;
  • 然后,通过ISD::MERGE_VALUES将ComputeValueVTs分拆的EVT类型以参数为粒度合并为一个节点;

1.2 构建DAG

在对于每个基本块执行SelectionDAGISel::SelectBasicBlock方法,在该方法内对每个指令调用SelectionDAGBuilder::visit构建DAG。其主要流程如下:

  1. 如果IR指令是终结指令,通过SelectionDAGBuilder::HandlePHINodesInSuccessorBlocks对后继节点使用该基本块为输入值的PHI指令转为寄存器拷贝;
  2. 通过SelectionDAGBuilder::visit(unsigned Opcode, const User &I)方法对指令进行生成DAG操作,该方法通过switch将不同的IR指令XXInst,转为调用visitXXInst方法。例如CallBrInst则调用SelectionDAGBuilder::visitCallBr方法;
  3. 最后,对一些特殊的IR指令做一些后处理。

处理每个PHI后继节点

  • 对每个后继节点使用了该基本块作为输入的PHI指令,取其为PHI单独分配的寄存器;
  • 记录PHI替换指令与对应拆分的源寄存器号的对应关系。

注1:为PHI指令分配寄存器是在SelectionDAGISel::runOnMachineFunction调用FuncInfo->set方法完成的。在该方法内部会FunctionLoweringInfo的InitializeRegForValue为PHI指令分配目的寄存器;最后再调用BuildMI创建一个目标的PHI指令,并将目的寄存器添加进去。
注2 :PHI指令的源寄存器是在SelectionDAGISel::FinishBasicBlock方法中通过建立的映射关系添加的。

AllocaInst转DAG

  • 首先,通过指令的数组维度乘以类型大小,最后加上对其长度;
  • 最后,通过生成ISD::DYNAMIC_STACKALLOC节点,即栈分配节点;

ReturnInst转DAG
CallInst转DAG

1.3 类型合法化

类型合法化是在DAGTypeLegalizer::run中完成的,它在向量合法化前后都会执行。主要有如下步骤:

  1. 初始化Worklist,将叶子节点加入,并setNodeId为ReadyToProcess,即0; 非叶子节点设置为Unanalyzed,即-2;然后进入while循环处理Worklist中的节点;
  2. 在while开始处,于是先合法化节点输出类型,跳转到NodeDone处理;
  3. 在NodeDone段中,将处理节点的使用节点的NodeId设置为操作数个数减1,最后等所有操作数的输出值都合法化后,便将其加入到Worklist中;最后回到while开始处继续;
  4. 大多数节点不需要处理操作数,因为操作数依赖的节点输出类型已经先行类型合法化。对于一些特殊节点(例如输出本身合法、没有输出值、register/TargetConstant节点),则会进入ScanOperands段对操作数类型合法化;

注:整个类型合法化依赖TargetLoweringBase::computeRegisterProperties初始化设置

TargetLowering::TypePromoteInteger

  • 该枚举会将int类型上提到合法的长度类型;
  • 处理输出值合法化时,通过DAGTypeLegalizer::PromotedIntegers成员记录原输出SDValue与转换后的输出SDValue的映射;
  • 后续节点合法化时,通过前面的映射找到转换后的SDValue替换;

TargetLowering::TypeExpandInteger

  • 该枚举会将不支持的过长int类型分拆为两个更小的长度类型;
  • 处理输出值合法化时,通过DAGTypeLegalizer::ExpandedIntegers成员记录原输出SDValue与转换后的两个分拆输出SDValue的映射;
  • 后续节点合法化时,通过前面的映射找到转换后的SDValue进行替换;

注:TypeExpandInteger与TypePromoteInteger不同的时,经过一次TypeExpandInteger可能还不是合法类型

TargetLowering::TypeSoftenFloat

  • 该枚举是当硬件不支持某类型浮点运算时,先将其转同长度int,然后由软件实现模拟浮点运算。即调用软件实现的libcall;
  • 处理输出值合法化时,通过DAGTypeLegalizer::SoftenedFloats成员记录原输出SDValue与转换后输出的SDValue的映射;
  • 后续节点合法化时,通过前面的映射找到转换后的SDValue进行替换;

TargetLowering::TypeExpandFloat

  • 该枚举是将较大类型拆分为两个较短类型,与TypeSoftenFloat一样,两个较短类型的运算也是由软件模拟实现;
  • 处理输出值合法化时,通过DAGTypeLegalizer::ExpandedFloats成员记录原输出SDValue与转换后的两个分拆节点SDValue的映射;
  • 后续节点合法化时,通过前面的映射找到转换后的SDValue进行替换;

TargetLowering::TypePromoteFloat

  • 该枚举将较小的不合法浮点类型上提到较大的浮点类型,它只是进行类型转换,不会软件模拟;
  • 处理输出值合法化时,通过DAGTypeLegalizer::PromotedFloats成员记录原输出SDValue与转换后输出SDValue的映射
  • 后续节点合法化时,通过前面的映射找到转换后的SDValue进行替换;

TargetLowering::TypeScalarizeVector

  • 该枚举是当向量只有一个元素时,直接使用元素类型操作;
  • 它通过DAGTypeLegalizer::ScalarizedVectors记录转换映射;

TargetLowering::TypeSplitVector

  • 该枚举是将一个较长向量拆分为两个维度较短向量;
  • 它通过DAGTypeLegalizer::SplitVectors记录转换映射;

TargetLowering::TypeWidenVector

  • 该枚举将一个较短向量扩展为维度较大的向量。扩展的元素用undef初始化,通过ISD::CONCAT_VECTORS合并为一个较大向量;
  • 它通过DAGTypeLegalizer::WidenedVectors记录转换映射;

1.4 向量合法化

类型合法化是在VectorLegalizer::Run中完成的;在Run中如果DAG中至少有一个节点使用了向量类型,则会对每个DAG节点调用VectorLegalizer::LegalizeOp。LegalizeOp方法的主要有如下步骤:

  1. 当节点已经合法化后,则直接返回VectorLegalizer::LegalizedNodes成员缓存的合法化后的节点。否则,继续;
  2. 递归调用LegalizeOp对节点的操作数合法化,并通过DAG.UpdateNodeOperands替换掉当前节点的操作数。后续处理节点输出值转换;
  3. 对load和store节点做特别处理。如果load节点是向量类型且为扩展load类型、或store节点是向量类型且是截断存储,则会根据TargetLowering::getLoadExtAction返回值作不同处理(Custom则会调用TargetLowering子类重写的LowerOperation方法、Expand则会调用VectorLegalizer::ExpandLoad/ExpandStore);
  4. 对于其他节点类型,只要操作数或输出值类型只要有一个向量类型,则进行通用处理。通用节点处理共分为三步:
    通过TargetLowering::getOperationAction获取节点的action(有些是根据输出值,有些是根据输入值);
    根据action作不同的处理:为Promote枚举调用VectorLegalizer::Promote方法、为Custom枚举调用TargetLowering子类重写的LowerOperation方法、为Expand枚举调用VectorLegalizer::Expand方法;
    在VectorLegalizer::LegalizedNodes成员中,建立节点的输出值到转换后的输出节点的映射缓存(如果没有转换则缓存当前节点)。

注:整个向量合法化依赖TargetLowering::setOperationAction和TargetLowering::setLoadExtAction初始化设置

VectorLegalizer::ExpandLoad

  • 对于Load的内存源类型是字节对齐(存储对齐)的、或其向量长度为1,则会通过TargetLowering::scalarizeVectorLoad处理Load节点。它会将对向量的Load分拆为单个元素的Load,并通过ISD::BUILD_VECTOR将Load值合并为一个向量;
  • 对于Load的内存源类型不是字节对齐的、且其向量长度大于1,则会分拆为目标指针类型大小Load(不够分拆则按幂退大小),然后经过系列位操作组合元素,最后再将元素通过ISD::BUILD_VECTOR合并为一个向量

VectorLegalizer::ExpandStore

  • 通过TargetLowering::scalarizeVectorStore处理;
  • 如果store节点的内存类型不是字节对齐的,则将每个向量元素截断为单个元素内存类型,再零扩展为与整个向量内存类型等bit长度的int类型,然后通过移位和ISD::OR合并为一个值,最后生成新的store节点存储合并的值;
  • 如果store节点的内存类型是字节对齐的,则对每个元素截断存储为单个元素的内存类型,最后通过ISD::TokenFactor组合为一个节点。

VectorLegalizer::Promote

  • 除了少数节点需要单独处理,大多数节点根据TargetLowering::getTypeToPromoteTo获取节点输出值的上提类型,然后重新生成相关操作节点;最后,通过ISD::BITCAST或ISD::FP_ROUND上提前的原类型;

VectorLegalizer::Expand

  • 一部分节点需要单独处理,例如ISD::MERGE_VALUES,将合并的值分拆返回便是;
  • 其他节点则通过SelectionDAG::UnrollVectorOp展开向量,本质上对向量的操作展开为相应位置的元素操作,最后再通过ISD::BUILD_VECTOR合并成一个向量。

1.5 DAG合法化

DAG合法化是最后一个合法化阶段,它在SelectionDAG::Legalize中完成,它会不断通过SelectionDAGLegalize::LegalizeOp对每个节点合法化,直到所有节点都不再需要合法化结束。SelectionDAGLegalize::LegalizeOp的主要流程如下:

  1. 在LegalizeOp中,同样会对Load和Store单独处理,会分别调用SelectionDAGLegalize::LegalizeLoadOps / LegalizeStoreOps进行处理;
  2. 对其他节点,先通过TargetLowering::getOperationAction获取LegalizeAction;再通过action不同做不同处理:
    为Legal,不处理;
    为Custom,调用TargetLowering子类重写的LowerOperation方法;
    为Promote,则调用SelectionDAGLegalize::PromoteNode处理;
    为Expand,则调用SelectionDAGLegalize::ExpandNode处理;
    为LibCall,则调用SelectionDAGLegalize::ConvertNodeToLibcall处理;

SelectionDAGLegalize::LegalizeLoadOps

  • 对于非扩展的Load节点,根据输出值类型调用TargetLowering::getOperationAction,根据返回值不同处理不同。
  • 对于扩展的Load节点,如果内存源类型没有字节对齐,且内存源类型不为MVT::i1或i1的getLoadExtAction返回为Promote行为,则将内存源类型上提到字节对齐类型生成新扩展Load,最后按未上提的内存源类型扩展输出值。
    后续则是内存源类型字节对齐、或内存源类型为MVT::i1且i1的getLoadExtAction不为Promote行为;
  • 对于扩展的Load节点,如果内存源类型位宽为2的幂次方,则根据大小端分拆为两个扩展Load,最后再通过移位操作合并为大类型;
  • 对于扩展的Load节点,如果内存源类型位宽不是2的幂次方(隐含条件字节对齐),调用TargetLowering::getLoadExtAction,根据返回值不同处理方式不同;

注:TargetLowering::expandUnalignedLoad用于处理不支持对齐的Load。对浮点或向量的不合法内存源类型,会拆分为寄存器类型粒度分别从栈上load;对于其他类型(例如int)拆分为两个更小的长度类型load,再通过位操作合并为一个节点

SelectionDAGLegalize::LegalizeStoreOps

  • 非截断store节点,根据待存储值类型调用TargetLowering::getOperationAction,根据返回值不同处理不同。
  • 截断store节点,如果内存类型为非字节对齐,则将内存类型上提到字节对齐类型;同时,对存储值超过的位截断为0;再重新生成新截断store节点;
  • 截断store节点,如果内存类型字节对齐且位宽为2的幂次方,则根据大小端分拆为两个较小类型存储;最后通过ISD::TokenFactor合并为一个节点;
  • 截断store节点,如果内存类型字节对齐但位宽不是2的幂次方,根据内存类型调用TargetLowering::getTruncStoreAction,根据返回值不同处理方式不同;

SelectionDAGLegalize::PromoteNode

  • 根据节点第0个输出类型获取上提类型,特别地,一些特殊节点是根据操作数的类型获取上提类型;
  • 根据节点类型不同作不同处理,以ISD::MUL为例,对操作数全部扩展为上提类型,再对输出值截断为上提前的类型;
  • 最后,通过ReplaceNode函数将引用原节点的use关系替换为Results(此外还会将新节点添加到更新列表),一般Results个数为1;

SelectionDAGLegalize::ExpandNode

  • 根据节点类型不同,作不同处理:以ISD::MERGE_VALUES为例,直接将合并的值取出来放到Results中;
  • 最后,通过ReplaceNode函数将引用原节点的use关系替换为Results(此外还会将新节点添加到更新列表)。其内部实现是调用SelectionDAG::ReplaceAllUsesWith实现的,Results数组代表的是每个原节点的输出值;

SelectionDAGLegalize::ConvertNodeToLibcall

  • 根据节点类型不同,调用不同的Libcall转换,并将节点加入到Results列表;
  • 最后与PromoteNode和ExpandNode一样,通过ReplaceNode替换原节点的输出值;

1.6 DAG合并

从构建DAG开始每个阶段完成后都要进行一次DAG合并,通过调用DAGCombiner::Run实现DAG合并。其主要流程如下:

  1. 首先将所有节点加入Worklist中,然后进入while循环处理;
  2. 如果当前节点没有被引用,则通过recursivelyDeleteUnusedNodes函数向上检索并删除没有使用的节点(其实前面的几个阶段也有无用节点的删除);
  3. 如果AtLevel大于等于阶段AfterLegalizeDAG(即合法化DAG之后),则再次通过SelectionDAGLegalize::LegalizeOp对当前节点进行DAG合法化,并将全部新转换的节点添加到Worklist中;
  4. 将当前节点加入CombinedNodes缓存中,并把当前节点的每个还没加入到CombinedNodes缓存的操作数节点加入到Worklist中,最后调用DAGCombiner::combine执行真正的节点合并;
  5. 如果DAG合并后返回的节点发生了变化(即不等于合并前的节点),将引用原节点的引用关系替换为引用新节点,并将新节点和引用它的节点添加到Worklist;
  6. 通过recursivelyDeleteUnusedNodes从原节点向上检索并删除没有使用的节点,然后继续下一轮循环

函数DAGCombiner::combine实现真正的DAG合并,主要流程如下:

  1. 首先,调用DAGCombiner::visit执行标准合并,会根据节点类型不同,作不同的合并策略;
  2. 如果visit后没有返回新节点(即没有合并处理)、且原节点的操作类型为自定义类型或TargetLowering::hasTargetDAGCombine返回目标可以合并,则调用TargetLowering子类重写的PerformDAGCombine方法;
  3. 如果至此还没有合并处理,则对特定节点类型执行合并操作。具体由DAGCombiner的四个方法处理:PromoteIntBinOp、PromoteIntShiftOp、PromoteExtend、PromoteLoad;
  4. 如果至此还没有合并处理、且TargetLowering可重写的isCommutativeBinOp方法返回该节点是可以交换的操作(例如加法可交换、减法不可交换)、且原节点输出值个数为1,那么对于两个操作数不同、且第0个操作数是常量或第1个不是常量,那么就试图从缓存中找到交换两个操作数后的节点返回。这种替换的隐含条件是llvm除了load/store外都是ssa形式;

DAGCombiner::visit

  • 在visit中根据不同节点操作类型,调用不同函数;
  • 以ISD::ADD为例,它会操作尽量合并或者说折叠,例如a + 3 + 6 -> a + 9、add x, undef -> undef、(add Z, C & sub C, Z -> Z等;

调用PerformDAGCombine方法

  • 该方法由TargetLowering子类重写,实现目标平台对节点的特殊合并处理;
  • 以RISCV处理RISCVISD::SplitF64为例:
    当节点第0个操作数为RISCVISD::BuildPairF64,直接调用DCI.CombineTo将BuildPairF64节点的两个操作数替换SplitF64节点;
    当节点第0个操作数为ConstantFPSDNode,将浮点类型转int,再拆分为两个32位int常量,最后调用DCI.CombineTo将两个32位常量替换当前SplitF64节点;
    如果节点第0个操作数节点的类型为ISD::FNEG、且该节点只有当前SplitF64节点一个引用,则先新生成输出int32类型的SplitF64,然后取出两个输出值(第0个对应f64低字节部分、第1个对应f64高字节部分), 再通过APInt::getSignMask取出32位符号数置1、其余位置0的整数SignBit,并且将SignBit与f64高字节部分通过ISD::XOR节点将符号位取反、其余位保持不变,最后将f64低字节部分和符号位取反的高字节部分替换原节点;
    如果节点第0个操作数节点的类型为ISD::FABS、且该节点只有当前SplitF64节点一个引用,与FNEG类似操作,只是转换逻辑不一样;

注:DCI.CombineTo最终调用DAGCombiner::CombineTo,内部会用新节点引用替换旧节点引用。并将新引用节点及其使用者节点添加到DAGCombiner::Worklist成员中

DAGCombiner::PromoteIntBinOp

  • 如果当前阶段是在向量合法化之前(即AtLevel< AfterLegalizeVectorOps),则不合并处理;
  • 如果当前节点输出值为向量类型或非int类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的isTypeDesirableForOp得出平台可以支持该节点对应的输出类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的IsDesirableToPromoteOp得出平台希望对该节点的输出该类型上提,则进行如下处理:
    将该节点两个操作数上提为输出类型的上提类型;
    用两个操作数重新生成该节点,并通过ISD::TRUNCATE截断为上提前的输出类型
    把旧节点操作数节点和新节点加入到DAGCombiner::Worklist中;
    同时从DAGCombiner::Worklist种移除旧节点;

DAGCombiner::PromoteIntShiftOp

  • 如果当前阶段是在向量合法化之前(即AtLevel< AfterLegalizeVectorOps),则不合并处理;
  • 如果当前节点输出值为向量类型或非int类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的isTypeDesirableForOp得出平台可以支持该节点对应的输出类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的IsDesirableToPromoteOp得出平台希望对该节点的输出该类型上提,则进行如下处理:
    将该节点第0个操作数(移位操作的值),上提为输出类型的上提类型;
    重新生成移位操作节点,并通过ISD::TRUNCATE截断为上提前的输出类型;
    把旧节点操作数节点和新节点加入到DAGCombiner::Worklist中;
    同时从DAGCombiner::Worklist种移除旧节点;

DAGCombiner::PromoteExtend

  • 如果当前阶段是在向量合法化之前(即AtLevel< AfterLegalizeVectorOps),则不合并处理;
  • 如果当前节点输出值为向量类型或非int类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的isTypeDesirableForOp得出平台可以支持该节点对应的输出类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的IsDesirableToPromoteOp得出平台希望对该节点的输出该类型上提,则进行如下处理:折叠多次扩展为一次扩展(不过llvm 10中该处应该有BUG,代码与注释不一致)

DAGCombiner::PromoteLoad

  • 如果当前阶段是在向量合法化之前(即AtLevel< AfterLegalizeVectorOps),则不合并处理;
  • 与前几个Promote不同的是:PromoteLoad还不会处理地址索引模式MemIndexedMode不为UNINDEXED的Load节点;
  • 如果当前节点输出值为向量类型或非int类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的isTypeDesirableForOp得出平台可以支持该节点对应的输出类型,则不合并处理;
  • 通过调用TargetLowering子类可重写的IsDesirableToPromoteOp得出平台希望对该节点的输出该类型上提,则进行如下处理:
    用上提类型重新生成ExtLoad,再截断为原类型;
    用新生成的节点输出值替换旧节点的被引用关系;
    把旧节点操作数节点和新节点加入到DAGCombiner::Worklist中;
    同时从DAGCombiner::Worklist种移除旧节点;

2 目标实现

实现SDAG指令选择

  • 实现TargetLowering子类:将其实例化注册到Subtarget的子类中通过重写的getTargetLowering方法返回TargetLowering引用。重点是实现TargetLowering子类
  • 实现SelectionDAGISel子类:先实现TargetPassConfig子类,并重写addInstSelector方法;在addInstSelector中添加SelectionDAGISel子类实现。

实现快速指令选择

实现全局指令选择

2.1 TargetLowering

2.2 SelectionDAGISel

相关文章:

llvm后端之指令选择源码分析

llvm后端之指令选择源码分析 引言1 主要流程1.1 参数降级1.2 构建DAG1.3 类型合法化1.4 向量合法化1.5 DAG合法化1.6 DAG合并 2 目标实现2.1 TargetLowering2.2 SelectionDAGISel 引言 llvm后端指令选择主要是class SelectionDAGISel的子类实现。整个过程将llvm IR转为有向无环…...

【消息中间件】Rabbitmq消息可靠性、持久化机制、各种消费

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 前言一、常见用法1.消息可靠性2.持久化机制3.消息积压批量消费&#xff1a;增加 prefetch 的数量,提高单次连接的消息数并发消费&#xff1a;…...

aws-sdk-cpp通过bazel构建的S3_client轮子

感觉时间过得很快&#xff0c;又是很久没有更新了 哎&#xff0c;主要原因还是很久都没有学什么东西了&#xff0c;进入社会后不知不觉间倦怠了许多 没什么办法&#xff0c;上班了之后做的很多东西都是调用api&#xff0c;越来越像一个工具人了&#xff0c;虽然说本身也大差不…...

关于WPF MVVM 的详细使用过程以及注意的问题

WPF MVVM 是一种常用的设计模式&#xff0c;在 WPF 应用程序中使用它可以更好地分离界面逻辑和业务逻辑&#xff0c;并且更容易进行单元测试和重构。下面是深入理解 WPF MVVM 的详细使用过程以及注意的问题。 一、MVVM 的基本概念 MVVM 是 Model-View-ViewModel 的缩写&#…...

计算机视觉 全教程目录

1、OpenCV 图像处理框架 实战系列 总目录 OpenCV 图像处理框架 实战系列 总目录 2、现代卷积网络实战系列 总目录 现代卷积网络实战系列 总目录 3、YOLO 物体检测 系列教程 总目录 YOLO 物体检测 系列教程 总目录 4、图像分割实战-系列教程 总目录 图像分割实战-系列教程 总目录…...

油猴脚本开发,之如何添加html和css

简介 油猴是一个脚本管理器,让我们能够方便的使用js脚本&#xff0c;以实现对页面内容的修改、功能增强或其他定制化操作。 常见脚本管理器 Tampermonkey 应该是各位见得最多的也是最知名的&#xff0c;好用又稳定&#xff0c;多浏览器支持Greasemonkey 用户脚本始祖&#x…...

【MATLAB】BiGRU神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 BiGRU神经网络时序预测算法是一种基于双向门控循环单元&#xff08;GRU&#xff09;的多变量时间序列预测方法。该方法结合了双向模型和门控机制&#xff0c;旨在有效地捕捉时间序列数据中…...

57.0/初识 PhotoShopCS4(详细版)

目录 57.1 PhotoShop 概要 57.2.1 像素和分辨率 57.2.2 色彩模式 57.2.3 位图和矢量图 57.3 PhotoShop 基本操作 57.3.1 PhotoShop 界面的认识 57.3.2 PhotoShop 基本界面工具 57.3.3 移动选择工具(V) 57.3.4 选框工具(M)​编辑 ​编辑57.3.5 套索工具(L) 57.3…...

[C#]opencvsharp进行图像拼接普通拼接stitch算法拼接

介绍&#xff1a; opencvsharp进行图像拼一般有2种方式&#xff1a;一种是传统方法将2个图片上下或者左右拼接&#xff0c;还有一个方法就是融合拼接&#xff0c;stitch拼接就是一种非常好的算法。opencv里面已经有stitch拼接算法因此我们很容易进行拼接。 效果&#xff1a; …...

《妙趣横生的算法》(C语言实现)-第10章算法设计与数据结构面试题精粹

【10-1】输入一个字符串并将它输出&#xff0c;以ctrlz组合键表示输入完毕&#xff0c;要求将输入的字符串中多于1个的连续空格符合并为1个。 //10-1 2023年12月30日17点11分-17点18分 # include <stdio.h> int main() {char c;c getchar();//scanf("%c", &a…...

(JAVA)-(网络编程)-初始网络编程

网络编程就是在通信协议下&#xff0c;不同的计算机上运行的程序&#xff0c;进行的数据传输。 讲的通俗一点&#xff0c;就是以前我们写的代码是单机版的&#xff0c;网络编程就是联机版的。 应用场景&#xff1a;即时通信&#xff0c;网游对战&#xff0c;金融证券&#xf…...

Observer观察者模式(组件协作)

观察者模式&#xff08;组件协作&#xff09; 链接&#xff1a;观察者模式实例代码 解析 目的 在软件构建过程中&#xff0c;我们需要为某些对象建立一种“通知依赖关系” ——一个对象&#xff08;目标对象&#xff09;的状态发生改变&#xff0c;所有的依赖对象&#xff0…...

数据挖掘 聚类度量

格式化之前的代码&#xff1a; import numpy as np#计算 import pandas as pd#处理结构化表格 import matplotlib.pyplot as plt#绘制图表和可视化数据的函数&#xff0c;通常与numpy和pandas一起使用。 from sklearn import metrics#聚类算法的评估指标。 from sklearn.clust…...

[Angular] 笔记 24:ngContainer vs. ngTemplate vs. ngContent

请说明 Angular 中 ngContainer&#xff0c; ngTemplate 和 ngContent 这三者之间的区别。 chatgpt 回答&#xff1a; 这三个在 Angular 中的概念是关于处理和组织视图的。 1. ngContainer&#xff1a; ngContainer 是一个虚拟的 HTML 容器&#xff0c;它本身不会在最终渲染…...

❀My排序算法学习之插入排序❀

目录 插入排序(Insertion Sort):) 一、定义 二、基本思想 三、示例 时间复杂度 空间复杂度 bash C++ 四、稳定性分析...

【算法题】30. 串联所有单词的子串

题目 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff0c; 那么 "…...

SAP-FI模块 处理自动生成会计凭证增强

ENHANCEMENT 2 ZEHENC_SAPMF05A. "active version * FI 20221215&#xff1a;固定资产业务过渡科目摘要增强功能 WAIT UP TO 1 SECONDS.READ TABLE xbseg WITH KEY hkont 1601990001. IF sy-subrc 0.DATA: lt_bkdf TYPE TABLE OF bkdf,lt_bkpf TYPE TABLE OF bkpf,…...

Shell脚本-bin/bash: 解释器错误: 没有那个文件或目录-完整路径执行-“/”引发的脑裂

引起该不适的一种可能以及解决方案&#xff0c;网上较多&#xff0c;比如&#xff1a; 但按以上方式操作&#xff0c;并经过查看&#xff0c;发现仍然未能解决问题。 因为两种方式执行&#xff0c;有一种能成功&#xff0c;有一种不能&#xff0c;刚开始未怀疑是文件问题&…...

React MUI(版本v5.15.2)详细使用

使用React MUI&#xff08;版本v5.15.2&#xff09;的详细示例。请注意&#xff0c;由于版本可能会有所不同&#xff0c;因此建议您查阅官方文档以获取最新的信息和示例。但是&#xff0c;我将根据我的知识库为您提供一些基本示例。 首先&#xff0c;确保您已经按照之前的说明…...

用CSS中的动画效果做一个转动的表

<!DOCTYPE html> <html lang"en"><head><meta charset"utf-8"><title></title><style>*{margin:0;padding:0;} /*制作表的样式*/.clock{width: 500px;height: 500px;margin:0 auto;margin-top:100px;border-rad…...

【linux】Linux管道的原理与使用场景

Linux管道是Linux命令行界面中一种强大的工具&#xff0c;它允许用户将多个命令链接起来&#xff0c;使得一个命令的输出可以作为另一个命令的输入。这种机制使得我们可以创建复杂的命令链&#xff0c;并在处理数据时提供了极大的灵活性。在本文中&#xff0c;我们将详细介绍Li…...

nvidia jetson xavier nx developer kit version emmc版重装系统

一、将开发板上的外置硬盘取下来格式化 二、在双系统ubuntu安装SDK Manager&#xff08;.deb文件&#xff09; SDK Manager | NVIDIA Developer sudo apt install ./sdkmanager_1.9.2-10884_amd64.deb 报错直接百度错误&#xff0c;执行相应命令即可 三、 运行SDK Manager …...

命令模式-实例使用

未使用命令模式的UML 使用命令模式后的UML public abstract class Command {public abstract void execute(); }public class Invoker {private Command command;/*** 为功能键注入命令* param command*/public void setCommand(Command command) {this.command command;}/***…...

将网页变身移动应用:网址封装成App的完全指南

什么是网址封装&#xff1f; 网址封装是一个将你的网站或网页直接嵌入到一个原生应用容器中的过程。用户可以通过下载你的App来访问网站&#xff0c;而无需通过浏览器。这种方式不仅提升了用户体验&#xff0c;还可利用移动设备的功能&#xff0c;如推送通知和硬件集成。 小猪…...

探讨kernel32.dll文件是什么,有效解决kernel32.dll丢失

在使用电脑时&#xff0c;你是否遇到过kernel32.dll丢失的困扰&#xff1f;面对这个问题&#xff0c;我们需要及时去解决kernel32.dll丢失的问题。接下来&#xff0c;我们将深入探讨kernel32.dll的功能以及其在操作系统和应用程序中的具体应用领域&#xff0c;相信这将对你解决…...

LOAM: Lidar Odometry and Mapping in Real-time 论文阅读

论文链接 LOAM: Lidar Odometry and Mapping in Real-time 0. Abstract 提出了一种使用二维激光雷达在6自由度运动中的距离测量进行即时测距和建图的方法 距离测量是在不同的时间接收到的&#xff0c;并且运动估计中的误差可能导致生成的点云的错误配准 本文的方法在不需要高…...

如何使用Docker将.Net6项目部署到Linux服务器(三)

目录 四 安装nginx 4.1 官网下载nginx 4.2 下载解压安装nginx 4.3 进行configure 4.4 执行make 4.5 查看nginx是否安装成功 4.6 nginx的一些常用命令 4.6.1 启动nginx 4.6.2 通过命令查看nginx是否启动成功 4.6.3 关闭Nginx 4.6.5 重启Nginx 4.6.6 杀掉所有Nginx进程 4.…...

《Spring Cloud学习笔记:分布式事务Seata》

解决分布式事务的方案有很多&#xff0c;但实现起来都比较复杂&#xff0c;因此我们一般会使用开源的框架来解决分布式事务问题。 在众多的开源分布式事务框架中&#xff0c;功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。 1. 初识Seata Seata是 2019 年 1 月…...

MySQL:权限控制

要授予用户帐户权限&#xff0c;可以用GRANT命令。有撤销用户的权限&#xff0c;可以用REVOKE命令。这里以 MySQl 为例&#xff0c;介绍权限控制实际应用。 GRANT授予权限语法&#xff1a; GRANT privilege,[privilege],.. ON privilege_level TO user [IDENTIFIED BY passwo…...

安全生产知识竞赛活动方案

为进一步普及安全生产法律法规知识&#xff0c;增强安全意识&#xff0c;提高安全技能&#xff0c;经研究&#xff0c;决定举办以“加强安全法治、保障安全生产”为主题的新修订《安全生产法》知识竞赛活动&#xff0c;现将有关事项通知如下&#xff1a; 一、活动时间&#xf…...

2023 IoTDB Summit:天谋科技 CTO 乔嘉林《IoTDB 企业版 V1.3: 时序数据管理一站式解决方案》...

12 月 3 日&#xff0c;2023 IoTDB 用户大会在北京成功举行&#xff0c;收获强烈反响。本次峰会汇集了超 20 位大咖嘉宾带来工业互联网行业、技术、应用方向的精彩议题&#xff0c;多位学术泰斗、企业代表、开发者&#xff0c;深度分享了工业物联网时序数据库 IoTDB 的技术创新…...

LangChain.js 实战系列:如何统计大模型使用的 token 使用量和花费

&#x1f4dd; LangChain.js 是一个快速开发大模型应用的框架&#xff0c;它提供了一系列强大的功能和工具&#xff0c;使得开发者能够更加高效地构建复杂的应用程序。LangChain.js 实战系列文章将介绍在实际项目中使用 LangChain.js 时的一些方法和技巧。 统计调用大模型的 to…...

基于多反应堆的高并发服务器【C/C++/Reactor】(中)EventLoop初始化

这个Dispatcher是一个事件分发模型&#xff0c;通过这个模型,就能够检测对应的文件描述符的事件的时候,可以使用epoll/poll/select,前面说过三选一。另外不管是哪一个底层的检测模型,它们都需要使用一个数据块,这个数据块就叫做DispatcherData。除此之外,还有另外一个部分,因为…...

OpenCV(Python)基础—9小时入门版

OpenCV(Python)基础—9小时入门版 # # Author : Mikigo # Time : 2021/12/1 # 一、一句话简介 OpenCV (Open Source Computer Vision Library) 是用 C 语言编写&#xff0c;提供 Python、Java 等语言 API的一个开源计算机视觉库。 二、安装 1、Debian 系使用 apt 安装 O…...

SpringBoot整合Canal

一 linux docker compose版本 1.第一步&#xff1a;基础环境 &#xff08;1&#xff09;第1步&#xff1a;安装jak、maven、git、nodejs、npm yum install maven mvn -v 安装maven时会帮安装jdkyum install git git --version 2.27.0yum in…...

用 Python 提取某一个公众号下的所有文章

当我们想要提取某一个公众号下的所有文章时&#xff0c;我们可以借助微信公众平台的开放接口&#xff0c;通过Python编写一个爬虫程序来实现。下面是一个示例代码&#xff0c;以及如何将其转化为一篇详细的微信公众号推文文章。 1. 导入所需库 首先&#xff0c;我们需要导入所…...

鸿蒙4.0实战教学—基础ArkTS(简易视频播放器)

构建主界面 主界面由视频轮播模块和多个视频列表模块组成&#xff0c;效果图如图&#xff1a; VideoData.ets中定义的视频轮播图数组SWIPER_VIDEOS和视频列表图片数组HORIZONTAL_VIDEOS。 // VideoData.ets import { HorizontalVideoItem } from ./HorizontalVideoItem; impo…...

4. 深入 Python 流程控制

​​​​​​ 4. 深入 Python 流程控制 除了前面介绍的 while 语句&#xff0c;Python 还从其它语言借鉴了一些流程控制功能&#xff0c;并有所改变。 4.1. if 语句 也许最有名的是 if 语句。例如: >>> x int(raw_input("Please enter an integer: "))…...

2000-2022年上市公司股票流动性指标数据/股票流动性Amihud(原始数据+计算代码+计算结果)

2000-2022年上市公司股票流动性指标数据/股票流动性Amihud&#xff08;原始数据计算代码计算结果&#xff09; 1、时间&#xff1a;2000-2022年 3、指标&#xff1a;证券代码_没有单位、交易日期_没有单位、日个股交易金额_元、考虑现金红利再投资的日个股回报率_没有单位、交…...

Unity 数据存储PlayerPrefs管理类

Unity 数据存储PlayerPrefs管理类 Unity 数据存储PlayerPrefs管理类实现存取实体类对象存储格式为Json格式Singleton.csInventoryEntity.csDataManager.cs用法如下 Unity 数据存储PlayerPrefs管理类 实现存取实体类对象 存储格式为Json格式 源码如下&#xff1a; Singleton…...

一篇文章学会如何使用 NestJS 过滤器处理系统全局异常情况

前言 在实际的应用开发中&#xff0c;你或许遇到过异常处理机制不统一或错误信息展示混乱的现象。为了解决这些问题&#xff0c;NestJS提供了一个优雅的解决方案&#xff1a;过滤器&#xff08;Filter&#xff09;。本文将从实际出发&#xff0c;向你介绍NestJS过滤器的基本概…...

ubuntu 守护进程 supervisor

# 安装 apt-get install supervisor# 检查 echo_supervisord_conf# 查看配置文件所在位置 # [include] # files /etc/supervisor/conf.d/*.conf ps -ef | grep supervisorcd /etc/supervisor/conf.d/lscat frp.conf[program:frp] command /data/work/frp/frpc -c /data/work/…...

SparkStreaming_window_sparksql_reids

1.5 window 滚动窗口滑动窗口 window操作就是窗口函数。Spark Streaming提供了滑动窗口操作的支持&#xff0c;从而让我们可以对一个滑动窗口内的数据执行计算操作。每次掉落在窗口内的RDD的数据&#xff0c;会被聚合起来执行计算操作&#xff0c;然后生成的RDD&#xff0c;会…...

爬虫工作量由小到大的思维转变---<第二十四章 Scrapy的`统计数据`收集stats collection ---12月26日补>

前言: 前两篇是讲的数据诊断分析,还有一篇深挖解决内存泄漏的文章,目前我还没整理汇编出来;但是,想到分析问题的时候,忽然觉得爬虫的数据统计好像也挺重要;于是,心血来潮准备来插一篇这个------让大家对日常scrapy爬的数据,做到心里有数!不必自己去搅破脑汁捣腾日志,敲计算器了…...

Kafka:本地设置

这是设置 Kafka 将数据从 Elasticsearch 发布到 Kafka 主题的三部分系列的第一部分;该主题将被 Neo4j 使用。第一部分帮助您在本地设置 Kafka。第二部分将讨论如何设置Elasticsearch将数据发布到Kafka主题。最后 将详细介绍如何使用连接器订阅主题并使用数据。 Kafka Kafka 是…...

.NetCore NPOI 读取excel内容及单元格内图片

由于数据方提供的数据在excel文件中不止有文字内容还包含图片信息&#xff0c;于是编写相关测试代码&#xff0c;读取excel文件内容及图片信息. 本文使用的是 NPOI-2.6.2 版本&#xff0c;此版本持.Net4.7.2;.NetStandard2.0;.NetStandard2.1;.Net6.0。 测试文档内容&#xf…...

TCP/UDP协议

1. 请解释TCP和UDP的主要区别。 TCP和UDP都是位于传输层的协议&#xff0c;具有不同的特点和应用场景。以下是它们的主要区别&#xff1a; 连接方式&#xff1a;TCP是面向连接的协议&#xff0c;这意味着在数据传输之前需要先建立连接。这通常通过三次握手来建立连接&#xff…...

3D 渲染如何帮助电商促进销售?

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 3D 渲染图像因其高转化率而成为亚马逊卖家的最新趋势。它是电子商务平…...

使用栈求表达式的值【数据结构】

中缀表达式转后缀表达式 转换流程&#xff1a; 初始化一个运算符栈。自左向右扫描中缀表达式&#xff0c;当扫描到操作数时直接连接到后缀表达式上。当扫描到操作符时&#xff0c;和运算符栈栈顶的操作符进行比较。如果比栈顶运算符高&#xff0c;则入栈。如果比栈顶运算符低…...

{MySQL}索引事务和JDBC

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、索引1.1索引是什么1.2作用1.3代码 二、事务2.1什么是事务2.2使用 三.JDBC总结 前言 接着上次&#xff0c;继续讲下MySQL 提示&#xff1a;以下是本篇文章正…...