Ascend的aclgraph(二)_npu_backend中还有些什么秘密?

news2025/5/13 19:51:30

1 _npu_backend

文章还是从代码开始

import torch_npu, torchair
config = torchair.CompilerConfig()
# 设置图下沉执行模式
config.mode = "reduce-overhead"
npu_backend = torchair.get_npu_backend(compiler_config=config)
opt_model = torch.compile(model, backend=npu_backend)

get_npu_backend调用的是_npu_backend函数。整体流程图如下:
在这里插入图片描述
上文Ascend的aclgraph(一)aclgraph是什么?torchair又是怎么成图的?对get_compiler函数进行了分析。本章接着分析剩余的4个部分。

1.1 _wrap_compiler

_wrap_compiler是一个装饰器,先看代码:

def _wrap_compiler(compiler: Callable, compiler_config: CompilerConfig):
    @functools.wraps(compiler)
    def wrapped_compiler(
            gm: torch.fx.GraphModule,
            example_inputs: List[torch.Tensor],
            is_inference: bool
    ):
        if is_inference and compiler_config.experimental_config.npu_fx_pass:
            _npu_joint_graph_passes(gm)
        return compiler(gm, example_inputs)

    @functools.wraps(compiler)
    def joint_compiler(
            gm: torch.fx.GraphModule,
            example_inputs: List[torch.Tensor]
    ):
        if compiler_config.experimental_config.npu_fx_pass:
            _npu_joint_graph_passes(gm)
        return compiler(gm, example_inputs)

    fw_compiler = functools.partial(wrapped_compiler, is_inference=False)
    inference_compiler = functools.partial(wrapped_compiler, is_inference=True)
    return fw_compiler, inference_compiler, joint_compiler

依旧使用了python的偏函数的功能:functools.partial。该函数中主要返回了3个compiler对象。
首先是wrapped_compiler,该函数的功能是主要功能就是调用compiler(gm, example_inputs),但根据是否是推理场景,加入了_npu_joint_graph_passes(torch._inductor.fx_passes.joint_graph模块提供)。

joint_graph在torch.compile中一般表示的前向和反向混在一起的一张联合图。

  • wrapped_compiler函数进行了2次封装,区别在于is_inference参数。从代码上推测,_npu_joint_graph_passes在推理的时候,需要对图进行_npu_joint_graph_passes优化。在is_inference = True的时候,返回inference_compiler。在is_inference = False的时候,返回fw_compiler ,可以认为该compile是带有反向图功能的。这里也可以看出,torchair也是可以用在训练里面的
  • 另外根据compiler_config.experimental_config.npu_fx_pass参数,专门返回了一个joint_compiler函数。

通过该装饰器,总共返回3个compile执行函数。

return fw_compiler, inference_compiler, joint_compiler

1.2 _set_gear_to_compiler

接着看_set_gear_to_compiler函数。
在这里插入图片描述
该函数依旧是个装饰器。先理解下guard是什么,这也是torch.compile中的概念。

在 PyTorch 的 torch.compile 功能中,“guard” 是一个关键概念,它用于确保编译后的代码仅在满足特定条件时执行。Guard 本质上是编译器为了保证优化后的模型行为与原始未优化模型一致而设置的断言或检查点。
Guard 的组成
Guards 主要由以下几部分组成:

  • 类型检查:确保输入张量的数据类型保持不变。
  • 形状检查:验证输入张量的形状是否符合预期。这对于动态形状的模型尤为重要。
  • 设备检查:确认所有张量都在正确的设备(如 CPU 或 GPU)上。
  • 值检查:在某些情况下,可能还需要对特定值进行检查以确保逻辑正确性。例如,对于依赖于某个具体值的控制流。

Guard 的作用

  • 稳定性:Guards 确保了即使在不同的运行条件下,编译后的模型也能稳定、正确地运行。如果任何 guard 条件不满足,则会触发回退机制,即重新追踪或编译该部分计算图。
  • 性能优化:通过设置 guards,编译器能够在假设这些条件始终成立的情况下应用更激进的优化策略,因为它们不需要考虑所有可能的变化情况。这可以显著提高执行效率和资源利用率。
  • 动态适应性:尽管静态编译能够带来性能提升,但深度学习模型往往需要处理多样化的输入。Guards 提供了一种方式来平衡静态编译带来的性能优势与动态适应不同输入需求之间的矛盾。

举例来说,在使用 torch.compile 对模型进行编译时,如果你有一个接受可变批次大小的模型,那么编译器会在生成的代码中加入关于输入张量形状的 guards。这意味着,只要输入的批次大小不超过预设的最大值并且不低于最小值,优化后的代码就会被执行;否则,将触发重新编译或者采用默认执行路径。

重点关注函数中的guard_gears_shape。该函数是对图输入的情况进行判断,输入的tensor的shape需要在指定的范围内变化,也就说,torchair当前使用torch.compile支持有限个shape是变化的场景。如果超过了指定的范围,程序就会就会报错推出

def _set_gear_to_compiler(compiler: Callable, compiler_config: CompilerConfig, input_dim_gears: Dict[int, List[int]]):
    @functools.wraps(compiler)
    def gear_compiler(
            gm: torch.fx.GraphModule,
            example_inputs: List[torch.Tensor],
    ):
        for i, dim_gears in input_dim_gears.items():
            set_dim_gears(example_inputs[i], dim_gears)
        guard_gears_shape(example_inputs)
        return compiler(gm, example_inputs)
    return gear_compiler

注意,_npu_backend中对上述的fw_compilerinference_compiler都调用了_set_gear_to_compiler函数。这里也好也好理解,训练和推理都是要确认图的功能是正确的,对图是否改变要有感知。

fw_compiler = _set_gear_to_compiler(fw_compiler, compiler_config, input_dim_gears)
inference_compiler = _set_gear_to_compiler(inference_compiler, compiler_config, input_dim_gears)

1.3 _get_partition_fn

接着看_get_partition_fn函数。
在这里插入图片描述

def _get_partition_fn(compiler_config: CompilerConfig):

    def partition_fn(graph: torch.fx.GraphModule, joint_inputs, **kwargs):
        _npu_joint_graph_passes(graph)
        return default_partition(graph, joint_inputs, **kwargs)

    if compiler_config.experimental_config.npu_fx_pass:
        return partition_fn
    return default_partition

partition是torch.compile中计算图拆分的概念,也就是将joint graph的图拆分为前向图和反向图,此处先给出default_partition的解释。

default_partition:模拟了PyTorch的默认行为,找出从forward的输入到输出的所有算子输出,剩余部分都视为backward部分,从而分割出正反向graph,forward的所有中间结果都保存用于backward。

default_partitiontorch._functorch.partitioners中的函数。_get_partition_fn返回的partition函数类型与compiler_config.experimental_config.npu_fx_pass参数相关。在配置compiler_config.experimental_config.npu_fx_pass情况下,返回与输入参数joint_inputs相关的partition函数。

return default_partition(graph, joint_inputs, **kwargs)

1.4 aot_module_simplified

aot_module_simplified_npu_backend中的最后一个调用函数。
在这里插入图片描述

return aot_module_simplified(gm, example_inputs, fw_compiler=fw_compiler, bw_compiler=compiler,
                             decompositions=decompositions, partition_fn=partition_fn,
                             keep_inference_input_mutations=keep_inference_input_mutations,
                             inference_compiler=inference_compiler)

aot_module_simplifiedtorch._functorch.aot_autograd中的函数,看如下注释。

aot_module_simplified
是 PyTorch 中与 Ahead-of-Time (AOT) 编译相关的函数,特别是在使用 TorchInductor 后端时。AOT 编译是指在模型运行之前预先编译模型的计算图,以期达到加速执行和优化资源使用的目的。
在 PyTorch 中,TorchInductor 是 torch.compile 的一个后端,它旨在为 GPU 和 CPU 生成高度优化的代码。aot_module_simplified 函数通常用于简化将一个 PyTorch 模型准备好进行 AOT 编译的过程,简单理解就是AOT编译前的预操作。其主要作用包括:

  • 准备模型进行AOT编译:该函数帮助用户方便地指定需要进行 AOT 编译的模型部分。通过处理模型的输入输出、设置必要的编译参数等,使得模型可以直接进入编译流程。
  • 优化计算图:在编译阶段,aot_module_simplified 可能会应用一系列优化措施,如算子融合、内存优化等,目的是提高最终编译产物的执行效率。
  • 简化流程:正如其名“simplified”所暗示的,这个函数简化了 AOT 编译的准备工作,允许用户以较少的代码实现模型的编译和优化,而不需要深入了解编译过程中的复杂细节。
  • 跨设备支持:它可能还提供对不同硬件平台的支持,确保编译后的模型可以在指定的目标设备(如GPU或CPU)上高效运行。

总的来说,aot_module_simplified 提供了一种简化的方式,让开发者可以更容易地利用 PyTorch 的 AOT 编译功能来优化他们的模型性能,尤其是在使用 TorchInductor 后端时。这有助于降低高性能模型部署的门槛,使得更多开发者能够从先进的编译技术和硬件优化中受益。需要注意的是,具体的实现细节可能会随着 PyTorch 版本的更新而有所变化。

2 torch.compile

回到最开始的例子中,可以看到传入到torch.compile中的backend是get_npu_backend返回的,也就是

npu_backend = torchair.get_npu_backend(compiler_config=config)
opt_model = torch.compile(model, backend=npu_backend)

从Ascend的aclgraph(一)aclgraph是什么?torchair又是怎么成图的?开始,反反复复提到了torch.compile概念,看来想了解aclgraph,必须对torch.compile做一个基本的了解。
但由于其代码内容比较复杂,小编认知有限,接下来的内容,对其涉及到的基本概念做个介绍。

2.1 torch.compile背景

参考:https://zhuanlan.zhihu.com/p/9418379234

为了改善模型训练推理性能,Pytorch推出torch.compile,并从torch 1.xx更为torch 2.xx,改变并增强了Pytorch在编译器级别的运行方式,同时开始将pytorch的部分代码从C++迁移回python,主要有以下特点:

  • 将动态的 PyTorch 模型转换为静态的、优化过的执行图,从而减少运行时的开销,提高计算效率。
  • 用户无需手动修改模型代码或选择特定的优化策略,torch.compile可以自动识别和应用最佳的优化路径,使得模型加速更加便捷。
  • 支持广泛的 PyTorch API 和操作,确保大部分现有模型在使用 torch.compile后无需进行大的调整即可获益于性能提升。

torch.compile的出现给我的感觉就是,torch.eager模式性能基本优化到头了,从python->c++->cuda(npu),流程上已经没有可以优化的空间。而如果将模型整形成一张图,图的优化空间比较大,有可为。可能有的小伙伴会问:tensorflow的图模式不是已经很好了吗?tensorflow为什么会日落西山了
这里允许小编胡诌一下。
1、tensorflow在小模型时代,或者说模型输入shape,dtype或format如果保持不变的话,那么使用tensorflow成图的性能依旧不输torch的eager模式,甚至是现在的torch.compile模式;
2、时代成就英雄。tensorflow以图模式(性能)起家,torch以eager模式(易用性)起家,而时代的浪潮是:模型的输入是动态shape的

  • 图模式的性能来源于:模型结构不变(也即是没有if,else等能改变图结构)、输入shape不变(显存效率)等。这样的情况下,模型能做各种算子融合、ir融合的pass,内存利用率也能达到极致。
  • 而在动态shape下,图模式的这些优势荡然无存,反而因为图的反复编译,造成的性能的劣化。而torch以eager模式不存在图编译的概念,每次都是单算子下发,时代的浪潮并没有掀翻它,反而是在给它助力。

mindspore(昇思),起步也是从图模式开始,没有赶上时代的步伐,现在还在时代浪潮中被拉扯,而回头看pytorch已然成为了AI领域的的基础设施。

留个问题:大语言模型(LLM)时代,模型的输入也是变化的,那么做图模式的收益来自于哪里呢?
想想看,哪些地方的输入shape是不变的。回答留在后面的文章中。

2.2 torch.compile的组成

torch.compile主要包含四个基础组件:

  • TorchDynamo:从python bytecode中解析构建计算图,是一个动态的、Python级别的编译器,旨在捕捉 PyTorch 模型的动态执行路径,将其转换为优化代码,实现bytecode-to-bytecode编译。
  • AOTAutograd:是 PyTorch 引入的一种自动求导机制,旨在在模型执行之前预先生成梯度计算的代码。这种方法通过静态分析模型的前向计算图,提前生成反向传播所需的梯度计算逻辑,从而减少运行时的开销,提升训练效率。
  • PrimTorch:将2000+ Pytorch op规范化为约250个原始op封闭集合,是 PyTorch 的一个中间表示(Intermediate Representation, IR)层,负责将高层的 PyTorch 操作转换为更低层次的、适合进一步优化和编译的基础操作。它通过简化和标准化操作,提高编译器和后端优化器处理计算图的效率。
  • TorchInductor:深度学习编译器,可为多种加速器和后端生成代码,生成OpenAI Triton(Nvidia/AMD GPU)和OpenMP/C++(CPU)代码,它利用多种优化技术,包括内存优化、并行化和低层次的代码生成,以最大化计算性能。

以上四个组件都是用Python编写的,支持动态shape(即能够发送不同大小的张量而无需重新编译),实现灵活并减低开发人员和供应商开发门槛的效果。
在这里插入图片描述

2.3 torch.compile支持的后端

当前支持的后端有:

>>> import torch
>>> import torch._dynamo as dynamo
>>> dynamo.list_backends()
['cudagraphs', 'inductor', 'onnxrt', 'openxla', 'tvm']

一般默认的是inductor,使用的方式是:

import torch
import torchvision.models as models

model = models.resnet18().cuda(
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
compiled_model = torch.compile(model, backend="inductor")    # 通过backend参数指定后端,默认为inductor
# compiled_model = torch._dynamo.optimize("inductor")(fn)    # 也可以通过torch._dynamo.optmize函数进行编译

x = torch.randn(16, 3, 224, 224).cuda()
optimizer.zero_grad()
out = compiled_model(x)
out.sum().backward()
optimizer.step()

请注意代码中注释的部分

compiled_model = torch.compile(model, backend="inductor")    # 通过backend参数指定后端,默认为inductor
# compiled_model = torch._dynamo.optimize("inductor")(fn)    # 也可以通过torch._dynamo.optmize函数进行编译

也就是说torch._dynamo.optimize("inductor")(fn)torch.compile(model, backend="inductor")是等价调用的。

2.3.1 torch.compile调用

torch.compile函数源码解析:torch.compile只是对torch._dynamo.optmize函数的简单封装

def compile(model: Optional[Callable] = None, *,        # Module/function to optimize
            fullgraph: builtins.bool = False,            #If False (default), torch.compile attempts to discover compileable regions in the function that it will optimize. If True, then we require that the entire function be capturable into a single graph. If this is not possible (that is, if there are graph breaks), then this will raise an error.
            dynamic: Optional[builtins.bool] = None,    # dynamic shape
            backend: Union[str, Callable] = "inductor",    # backend to be used
            mode: Union[str, None] = None,                # Can be either "default", "reduce-overhead", "max-autotune" or "max-autotune-no-cudagraphs"
            options: Optional[Dict[str, Union[str, builtins.int, builtins.bool]]] = None,    #  A dictionary of options to pass to the backend. Some notable ones to try out are
            disable: builtins.bool = False)             # Turn torch.compile() into a no-op for testing
            -> Callable:
    # 中间代码省略...         
    return torch._dynamo.optimize(backend=backend, nopython=fullgraph, dynamic=dynamic, disable=disable)(model)   

torch._dynamo.optimize函数解析:进一步分析torch._dynamo.optimize的函数调用栈,会发现torch._dynamo.optimize只是通过_optimize_catch_errors函数返回了一个OptimizeContext对象。

def _optimize(
    rebuild_ctx: Callable[[], Union[OptimizeContext, _NullDecorator]],
    backend="inductor",
    *,
    nopython=False,
    guard_export_fn=None,
    guard_fail_fn=None,
    disable=False,
    dynamic=None,
) -> Union[OptimizeContext, _NullDecorator]:
    # 中间代码省略...
    
    return _optimize_catch_errors(
        convert_frame.convert_frame(backend, hooks=hooks), // backend,回调函数
        hooks,
        backend_ctx_ctor,
        dynamic=dynamic,
        compiler_config=backend.get_compiler_config()
        if hasattr(backend, "get_compiler_config")
        else None,
        rebuild_ctx=rebuild_ctx,
    )
    
 # ---------------------------------------------------------------------------------------------------------------------------------------  
def _optimize_catch_errors(
    compile_fn,
    hooks: Hooks,
    backend_ctx_ctor=null_context,
    export=False,
    dynamic=None,
    compiler_config=None,
    rebuild_ctx=None,
):
    return OptimizeContext(
        convert_frame.catch_errors_wrapper(compile_fn, hooks), // 回调函数
        backend_ctx_ctor=backend_ctx_ctor,
        first_ctx=True,
        export=export,
        dynamic=dynamic,
        compiler_config=compiler_config,
        rebuild_ctx=rebuild_ctx,
    )

因此,经过torch.compile装饰过的函数/模型成为一个OptimizeContext,其中convert_frame.catch_errors_wrapper成为了OptimizeContext中的一个回调函数 (callback),而这个回调函数包含编译函数入口,如inductor或自定义编译函数。由此可见,torch.compile并没有进行实际的编译,只是做一些简单的初始化工作,只有第一次正式执行代码前才会进行编译。

torch.compile优化流程:基于TorchDynamo和AOTAutograd构建Pytorch的前向和反向计算图,通过PrimTorch将op拆解转化为更低层次的、适合进一步优化和编译的基础op,最后Inductor进行算子融合等图优化并针对特定硬件生成triton(GPU)或OpenMP/C++(CPU)优化代码。

2.3.2 实现自定义的backend后端

从如上TorchInductor的定义:深度学习编译器,可为多种加速器和后端生成代码,生成OpenAI Triton(Nvidia/AMD GPU)和OpenMP/C++(CPU)代码。也就是说,这种后端的作用,是为了生成能够执行的代码。那是否可以自己自定义后端实现?来来来,结合例子试一下。

import torch
from typing import List
import torch._dynamo as dynamo

def my_compiler(gm: torch.fx.GraphModule, example_inputs_: List[torch.Tensor]):
    print("===============my compiler=================")
    gm.graph.print_tabular()    # 格式化输出FX Graph
    print("code is:",gm.code)      # 对应的python代码
    return gm
 
# 调用torch._dynamo.optimize函数
def my_func(x, y):
    if x.sum() > y.sum():
        loss = torch.cos(torch.cos(x))
    else:
        loss = torch.cos(torch.cos(y))
    return loss

def test():
    func = dynamo.optimize(my_compiler)(my_func) // my_compiler就是自定义的后端,my_func就是模型
    x, y  = torch.randn(10,requires_grad=True), torch.randn(10,requires_grad=True)
    func(x,y)

test()

如上,my_compiler就是自定义的而后端。
看到这里也就明白了。_npu_backend的后端也是自定义的,生成的或者优化后的代码是能在npu上执行的代码。

下一篇对TorchDynamo做个一个简单的了解。

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

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

相关文章

ventoy安全启动怎么选_ventoy安全启动支持是开还是关

ventoy安全启动怎么选?Ventoy新一代多系统启动U盘解决方案。国产开源U盘启动制作工具,支持Legacy BIOS和UEFI模式,理论上几乎支持任何ISO镜像文件,支持加载多个不同类型的ISO文件启动,无需反复地格式化U盘,…

CC53.【C++ Cont】二分查找的普通模版

目录 1.知识回顾 2.关键点 特点 三个模版 普通的模版(有局限) 以LeetCode上的一道题为例:704. 二分查找 分析 引入二段性:分两段,舍一段,操作另一段(这个是二分查找的本质!) 代码 提交结果 当然也可以使用随机数来分两段 普通模版总结 1.知识回顾 之前在C语言专栏…

【优选算法 | 链表】链表操作技巧:常见算法

算法相关知识点可以通过点击以下链接进行学习一起加油!双指针滑动窗口二分查找前缀和位运算模拟 链表是一种灵活的数据结构,广泛用于需要频繁插入和删除的场景。掌握链表的常见操作技巧,如插入、删除、翻转和合并等,能帮助开发者更…

w~大模型~合集30

我自己的原文哦~ https://blog.51cto.com/whaosoft/13284996 #VideoMamba 视频理解因大量时空冗余和复杂时空依赖,同时克服两个问题难度巨大,CNN 和 Transformer 及 Uniformer 都难以胜任,Mamba 是个好思路,让我们看看本文是…

PBR材质-Unity/Blender/UE

目录 前言: 一、Unity: 二、Blender: 三、UE: 四、全家福: 五、后记: 前言: PBR流程作为表达物理效果的经典方式,很值得一学。纹理贴图使用的是上一期的Textures | cgbookcas…

websocketpp 安装及使用

介绍 WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的消息推送机制。 传统的 web 程序都是属于 "一问一答" 的形式,即客户端给服务器发送了一个 HTTP 请求,服务器给客户端返回一个 HTTP 响应。这种情况下服务器是属于被动…

第8章-2 查询执行的基础

上一篇:《第8章-1 查询性能优化-优化数据访问》,接着来了解查询执行的过程,这个对sql执行有个更直观的了解。 查询执行的基础 当希望MySQL能够以更高的性能运行查询时,最好的办法就是弄清楚MySQL是如何优化和执行查询的。一旦理解…

java面试OOM汇总

在正式 Minor GC 前,JVM 会先检查新生代中对象,是比老年代中剩余空间大还是小。假如 Minor GC之后 Survivor 区放不下剩余对象,这些对象就要进入老年代 老年代剩余空间大于新生代中的对象大小,那就直接 Minor GC, GC 完…

react-diff-viewer 如何实现语法高亮

前言 react-diff-viewer 是一个很好的 diff 展示库,但是也有一些坑点和不完善的地方,本文旨在描述如何在这个库中实现自定义语法高亮。 Syntax highlighting is a bit tricky when combined with diff. Here, React Diff Viewer provides a simple rend…

自定义prometheus exporter实现监控阿里云RDS

# 自定义 Prometheus Exporter 实现多 RDS 数据采集## 背景1. Prometheus 官网提供的 MySQL Exporter 对于 MySQL 实例只能一个进程监控一个实例,数据库实例很多的情况下,不方便管理。 2. 内部有定制化监控需求,RDS 默认无法实现,…

【计算机网络】--tcp三次握手

文章目录 示意图:抓包结果:第一次握手(Client → Server)第二次握手(Server → Client)第三次握手(Client → Server)为什么是三次握手 不是两次或者四次 示意图: 抓包结…

UI-TARS: 基于视觉语言模型的多模式代理

GitHub:https://github.com/bytedance/UI-TARS 更多AI开源软件:发现分享好用的AI工具、AI开源软件、AI模型、AI变现 - 小众AI 基于视觉语言模型(Vision-Language Model)的 GUI 代理应用,允许用户通过自然语言控制电脑操…

Spark SQL 运行架构详解(专业解释+番茄炒蛋例子解读)

1. 整体架构概览 Spark SQL的运行过程可以想象成一个"SQL查询的加工流水线",从原始SQL语句开始,经过多个阶段的处理和优化,最终变成分布式计算任务执行。主要流程如下: SQL Query → 解析 → 逻辑计划 → 优化 → 物理…

【计算机网络】网络IP层

📚 博主的专栏 🐧 Linux | 🖥️ C | 📊 数据结构 | 💡C 算法 | 🅒 C 语言 | 🌐 计算机网络 上篇文章:传输层协议TCP 下篇文章:数据链路层 文章摘要&#xff1…

一天学会Maven

一、Maven简介和快速入门 1.1 Maven介绍 Maven 是一款为 Java 项目构建管理、依赖管理的工具(软件),使用 Maven 可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。 总结:Maven就是一个软件&#xf…

变量函数实战:高保真APP原型“发票页面”动态交互教程

变量函数是高保真交互原型设计中常见的高级交互功能,能够避免重复复制与手动修改页面元素和逻辑标注,让演示更有真实体验感。本文分享一个高保真APP交互原型页面的实操案例,结合原型设计工具中的变量函数与逻辑判断功能,手把手教你…

Spring Boot 3 + Undertow 服务器优化配置

优化背景 当你的application需要支持瞬时高并发的时候,tomcat已经不在是最优的选择,我们可以改为Undertow,并对其进行优化。 Undertow 是一个轻量级的、高性能的Java Web 服务器,由JBoss 开发并开源。它是基于非阻塞(…

7系列 之 OSERDESE2

背景 《ug471_7Series_SelectIO.pdf》介绍了Xilinx 7 系列 SelectIO 的输入/输出特性及逻辑资源的相关内容。 第 1 章《SelectIO Resources》介绍了输出驱动器和输入接收器的电气特性,并通过大量实例解析了各类标准接口的实现。 第 2 章《SelectIO Logic Resource…

vue3+flask+sqlite前后端项目实战

基础环境安装 pycharm 下载地址: https://www.jetbrains.com/zh-cn/pycharm/download/?sectionwindows vscode 下载地址 https://code.visualstudio.com/docs/?dvwin64user python 下载地址 https://www.python.org/downloads/windows/ Node.js(含npm…

Java 线程的堆栈跟踪信息

Java 线程的堆栈跟踪信息,展示了线程的当前状态和执行位置。以下是详细解释: 线程基本信息 "Thread-0" #16 prio5 os_prio0 cpu0.00ms elapsed16.29s tid0x00000243105a4130 nid0x5384 waiting on condition [0x0000007687ffe000]线程名称…