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

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

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

相关文章

逆矩阵:解开线性代数之谜的魔法钥匙

逆矩阵&#xff1a;解开线性代数之谜的魔法钥匙 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;让我们一同深入探讨线性代数中的重要主题——逆矩阵…

Resolume Arena(VJ音视频软件):创意无限,视听艺术的新境界

Resolume Arena是一款领先的VJ音视频软件&#xff0c;为创意人士提供了丰富的视觉效果和音频处理功能。无论是在舞台演出、音乐会还是派对活动中&#xff0c;Resolume Arena能够将音乐、视频和图像无缝地结合&#xff0c;创造出引人入胜的视听体验。 Resolume Arena具备强大的…

Nginx快速入门:nginx实现正向代理|反向代理和正向代理的区别(八)

0. 引言 我们之前讲解的一直是nginx的反向代理配置&#xff0c;关于正向代理的实现一直没有涉及&#xff0c;但在实际生产中正向代理也有非常广泛的应用场景&#xff0c;因此&#xff0c;今天我们将针对正向代理来深入学习。 1. 相关概念 1.1 什么是反向代理 所谓反向代理&…

如何利用多开软件在Windows电脑上优化游戏效果?

当今&#xff0c;许多玩家都希望在Windows电脑上通过多开软件来优化游戏效果。多开软件可以让玩家同时在一台电脑上打开多个游戏实例&#xff0c;从而提升游戏体验和效率。接下来&#xff0c;我将为大家介绍如何在Windows电脑上利用多开软件来优化游戏效果。 首先&#xff0c;…

读算法霸权笔记08_反馈

1. 关开门 1.1. clopening 1.2. 指的是一个职员工作到很晚&#xff0c;关闭门店或者咖啡店&#xff0c;几小时后又在天亮之前回来开门 1.3. 极度不规律的工作时间安排越来越常见 1.3.1. 首当其冲的是星巴克、麦当劳和沃尔玛等企业的低薪职工 1.3.2. 不规律的工作时间安排是…

云安全指的是什么,云安全产品有哪些,有什么作用

云安全是什么意思呢&#xff1f;简单来说云安全是指基于云计算商业模式应用的安全软件&#xff0c;专门保护云计算系统的网络安全学科。那么云安全和传统安全有什么区别呢&#xff1f;云安全包括在基于在线的基础架构、应用程序和平台上保持数据的私密和安全比起传统的安全要求…

Java加密算法工具类(AES、DES、MD5、RSA)

整理了有关加密算法工具类&#xff0c;结合了几个博客以及自己改良后可直接使用&#xff0c;主要介绍以下四种加密方式&#xff1a;AES、DES、MD5、RSA&#xff0c;详细介绍都在注释里面有讲。 一、AES import com.alibaba.fastjson.JSONObject; import java.nio.charset.Sta…

信号与线性系统翻转课堂笔记17——z变换及其性质

信号与线性系统翻转课堂笔记17——z变换及其性质 The Flipped Classroom17 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff09;序列的z变换…

Serverless架构:无服务器应用与AWS Lambda-读书笔记

Serverless架构&#xff1a;无服务器应用与AWS Lambda-读书笔记 好的架构可以成就软件&#xff0c;缺乏架构则会破坏软件。 一、Serverless 架构的来龙去脉 在典型的Web应用程序中&#xff0c;服务器接受前端的HTTP请求并处理请求。在保存到数据库之前&#xff0c;数据可能会…

vue、react、angular 区别

vue、react、angular 区别 vuereactangular vue Vue.js 是一套渐进式 JavaScript 框架&#xff0c;专注于视图层&#xff0c;易于上手。采用双向数据绑定机制&#xff0c;使得数据驱动视图更新&#xff0c;同时也支持单向数据流。提供了简洁的模板语法和灵活的组件化开发方式&a…

使用 SSH 方式实现 Git 远程连接GitHub

git是目前世界上最先进的分布式版本控制系统&#xff0c;相比于SVN&#xff0c;分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在&#xff0c;也就是有没有联网都可以正常工作&#xff01;当有网络的时候&#xff0c;再把本地提交推送一下就完成了同步&…

C:Huffman编码a

【问题描述】 给定一组字符的Huffman编码表&#xff08;从标准输入读取&#xff09;&#xff0c;以及一个用该编码表进行编码的Huffman编码文件&#xff08;存在当前目录下的in.txt中&#xff09;&#xff0c;编写程序实现对Huffman编码文件的解码&#xff0c;并按照后序遍历序…

【Java开发岗面试】八股文—Java基础集合多线程

声明&#xff1a; 背景&#xff1a;本人为24届双非硕校招生&#xff0c;已经完整经历了一次秋招&#xff0c;拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验&#xff08;主要是校招&#xff09;&#xff0c;包括我自己总结的八股文、算法、项目介绍、HR面和面试…

Python编程-面向对象基础与入门到实践一书的内容拓展

Python编程-面向对象基础与入门到实践一书的内容拓展 通过编程&#xff0c;模拟现实生活中的事物编程&#xff0c;叫做面向对象编程&#xff0c;此过程也叫做实例化编程 简单类的创建 class Test():def __init__ (self,id):self.id iddef print_id(self):print(self.id)这里建…

云原生Kubernetes系列 | Liveness和Readiness使用

云原生Kubernetes系列 | Liveness和Readiness使用 1. 为什么需要 Liveness和Readiness2. liveness probe2.1. command方式2.2. http方式2.3. tcpSocket方式3. readiness probe1. 为什么需要 Liveness和Readiness 通过deployment创建Pod非常方便。也有了高可用。    但是存在一…

minio命令行详解

客户端命令–mc NAME:mc - MinIO Client for object storage and filesystems.USAGE:mc [FLAGS] COMMAND [COMMAND FLAGS | -h] [ARGUMENTS...]COMMANDS:alias manage server credentials in configuration filels list buckets and objectsmb make a bu…

c++简易AI

今天小编一时雅兴大发&#xff0c;做了一个c的简易AI&#xff0c;还是很垃圾的&#xff01; 题外话&#xff08;每期都会有&#xff09;&#xff1a;我的蛋仔名叫酷影kuying&#xff0c;大家能加我好友吗&#xff1f; 上代码咯&#xff01; #include<bits/stdc.h> #in…

文件描述符

文件描述符 2.1 文件描述符 文件描述符&#xff08;File Descriptor&#xff09;是在Unix-like操作系统中用于标识和访问文件或I/O设备的抽象概念。它是一个非负整数&#xff0c;用于**唯一标识一个打开的文件、套接字&#xff08;socket&#xff09;或其他类型的I/O资源。**…

在简历中展示的专业技能

您的下一个角色无疑将与您之前的工作经历有所不同。因此&#xff0c;讲述您的成就的故事会暗示您的潜力&#xff0c;分享您更广泛的技能也同样会产生影响。 当您在简历中谈论一项技能时&#xff0c;它不需要与最近的成就联系起来。当然&#xff0c;这是理想的情况&#xff0c;…

Elasticsearch-8.11.1 (2+1)HA(高可用)集群部署

目录 一、环境描述 二、安装 ES 2.1 下载Elasticsearch 2.2 解压Elasticsearch 2.3 创建es服务账号/密码 2.3 修改服务器配置 2.4 配置节点 2.4.1 配置说明 2.4.2 配置高可用集群 2.4.2.1 maser节点服务配置 2.4.2.2 node1 节点服务配置 2.4.2.3 node2 节点服务配置…