05-08 周三 FastBuild FastAPI 引入并发支持和全局捕获异常

时间版本修改人描述
2024年5月8日20:41:03V0.1宋全恒新建文档

简介

由于FastBuild之前花费了大概5天的时间优化,但最近重新部署,又发现了一些问题,就很痛苦,五一之后,自己又花了三天的时间系统的进行了优化。

 上一波优化位于05-07 周二 Python使用并行程序取代串行加速运行,样例程序演示.

 而这一波优化,则主要集中在Python的并发执行和全局捕获异常

问题

这次的主要问题是如下所示:

  • 当镜像过大时,拉取失败,页面提示非常糟糕,直接提示Bad Request。
  • 启动镜像进行容器环境检测的时候,基本上需要花费16秒,太久了,不得不用并行
image-20240508203410941

 这次主要修改的内容为其实有两个

  1. 并发策略的引入
  2. 全局捕获异常的引入

 为了解决这个问题,自己真的是花费了很多的时间,因为感情的事情以及工作的压力,让自己精神也很焦虑,就很糟糕的状态。不过也没办法。哎

 昨天晚上搞到了晚上23:40,内心又很焦虑,完了之后,又看到邻居家的姑娘结婚了,就很心疼自己的父母,515呢,又有一个动态量化的需求要实现,而推理啊,动态量化啊什么的,自己其实懂得很少,各种各样的事情,搞的自己异常焦虑。

 人,真的不能走弯路,三十年来狼藉,东壁打到西壁。误入歧途和执迷不悔的人生,总是要进来一些希望和光吧。我想要改变。

实践记录

并发策略引入

待解决的问题

 这个是优化之前,FastBuild通过启动的容器,基于python Docker SDK进行交互,获取容器中的运行环境的耗时。

get_image_container took 0.9041
启动容器, 镜像名称: 10.200.88.53/sunning-zhejianglab.com/vit:v2, 容器id: cd391ddc3278
get_kernel_version took 3.7441
get_kernel_artifact took 3.7442
get_sshd_version took 4.4891
get_sshd_artifact took 8.4335
get_jupyter_lab_artifact took 9.9615
get_os_release_contents took 4.1719
get_ubuntu_version took 4.1720
get_os_artifact took 10.3813
get_python_artifact took 11.9672
get_package_manager_artifact took 13.0781
get_pip_artifact took 14.1710
get_conda_artifact took 17.4669
get_artifact_result_parallel took 17.5001
get_image_meta took 0.0626
镜像类型: amd64
镜像大小: 32.44G

可以看到,尽管使用了并行,执行,但是耗时真的太久了,前端等不到响应,页面就直接抛出了一个Bad Request.一开始很大的精力就是解决这个问题。

 而问题代码为:

    def get_image_descriptor(self) -> ImageDescriptor:
        """
        获取镜像描述信息
        :return:
        """
        descriptor = ImageDescriptor(self.image_name)
        descriptor.kernel = self.get_kernel_artifact()

        descriptor.os = self.get_os_artifact()
        descriptor.package_manager = self.get_package_manager_artifact()
        descriptor.pip = self.get_pip_artifact()
        descriptor.conda = self.get_conda_artifact()
        descriptor.python = self.get_python_artifact()
        descriptor.image_id = self.image_id

        descriptor.sshd = self.get_sshd_artifact()
        descriptor.jupyter_lab = self.get_jupyter_lab_artifact()
        return descriptor

 首先,可以看到,上述的代码是串行执行的,但实际上,由于这些代码相互并不影响,因此,先进行了如下的重构。

concurrent.futures.ThreadPoolExecutor

 为了解决这个问题,就需要首先进行时间的统计,首先要分析出问题,这多亏了如下的博客:

  • python threadpoolexecutor_使用Python的concurrent.futures轻松实现并发编程 其中有装饰器 @timeit的实现,可以方便统计出函数的执行时间,侵入性少。

  • python concurrent.futures 模块线程处理详解 这里包含了一些简单,但是实用的例子,帮助我们理解线程池的概念。

 这里根据参数的传递,分为两种情况,一种就是不需要参数的

无参

 下面就是掩饰了这个线程池的并发获取这些信息的过程。

  • get_all_artifact_funcs返回待执行的所有函数
  • get_artifact_result_parallel通过线程池发起8个线程去执行会话,得到探测结果
  • get_image_descriptor将所有结果取出。
   def get_image_descriptor(self) -> ImageDescriptor:
        """
        获取镜像描述信息
        :return:
        """
        descriptor = ImageDescriptor(self.image_name)
        descriptor.image_id = self.image_id
        result = self.get_artifact_result_parallel()
        descriptor.kernel = result["get_kernel_artifact"]
        descriptor.os = result["get_os_artifact"]
        descriptor.package_manager = result["get_package_manager_artifact"]
        descriptor.pip = result["get_pip_artifact"]
        descriptor.conda = result["get_conda_artifact"]
        descriptor.python = result["get_python_artifact"]
        descriptor.sshd = result["get_sshd_artifact"]
        descriptor.jupyter_lab = result["get_jupyter_lab_artifact"]

        return descriptor

    def get_all_artifact_funcs(self) -> List:
        return [self.get_kernel_artifact, self.get_os_artifact, self.get_package_manager_artifact,
                self.get_pip_artifact, self.get_conda_artifact, self.get_python_artifact,
                self.get_sshd_artifact, self.get_jupyter_lab_artifact]

    @timeit
    def get_artifact_result_parallel(self):
        # 使用线程池执行所有的artifact获取函数
        with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
            # 执行所有函数并将结果映射到一个字典中
            results = {func.__name__: executor.submit(func) for func in self.get_all_artifact_funcs()}

            # 等待所有任务完成并更新descriptor
            res = {}
            for name, future in results.items():
                res[name] = future.result()
            return res

 经过这样设置之后,发现了如下的结果:

get_image_container took 0.9041
启动容器, 镜像名称: 10.200.88.53/sunning-zhejianglab.com/vit:v2, 容器id: cd391ddc3278
get_kernel_version took 3.7441
get_kernel_artifact took 3.7442
get_sshd_version took 4.4891
get_sshd_artifact took 8.4335
get_jupyter_lab_artifact took 9.9615
get_os_release_contents took 4.1719
get_ubuntu_version took 4.1720
get_os_artifact took 10.3813
get_python_artifact took 11.9672
get_package_manager_artifact took 13.0781
get_pip_artifact took 14.1710
get_conda_artifact took 17.4669
get_artifact_result_parallel took 17.5001
get_image_meta took 0.0626
镜像类型: amd64
镜像大小: 32.44G

可以看到,这个函数要执行17秒,而启动容器,和获取镜像元数据,均不到1s。所以必须继续优化。

继续定位,发现了如下的问题代码。

 直观理解,每次和容器通过python docker sdk进行交互,都要花费时间,而这个get_command_result这个断言是多余的,果断拿掉这个之后,代码执行效率提升一倍。

    def get_command_result(self, cmd: str) -> str:
        """
        :param cmd:
        :return:
        """
        assert self.command_success(cmd)
        exit_code, output = self.container.exec_run(cmd)
        return output.decode("utf-8")
    
    

 得到了如下的结果,整体需要花费10秒的时间。

get_image_container took 0.9335
启动容器, 镜像名称: 10.200.88.53/sunning-zhejianglab.com/vit:v2, 容器id: 9e02c9d8894a
__is_ubuntu took 0.4077
contains_service took 1.0056
contains_jupyter_lab_service took 1.0056
get_jupyter_lab_artifact took 1.0057
get_kernel_version took 1.3691
get_kernel_artifact took 1.3692
contains_service took 2.9088
contains_sshd_service took 2.9089
get_os_release_contents took 2.5661
get_ubuntu_version took 2.5670
get_os_artifact took 2.9750
__is_ubuntu took 2.9669
get_sshd_version took 1.0172
get_sshd_artifact took 3.9262
get_conda_version took 2.2805
get_package_manager_artifact took 6.5252
get_python_artifact took 7.1439
get_multiple_pip_version took 7.8002
__is_ubuntu took 0.3780
get_conda_environments took 2.9953
get_pip_artifact took 8.5834
__is_ubuntu took 0.3355
get_conda_artifact took 9.6392
get_artifact_result_parallel took 9.6798
get_image_meta took 0.0621
镜像类型: amd64
镜像大小: 32.44G
有参

其实可以看到上述的多个线程在执行的时候,占用较多时间的为:

  • get_conda_artifact took 9.6392
  • get_pip_artifact took 8.5834
  • get_package_manager_artifact took 6.5252
  • get_python_artifact took 7.1439

 而__is_ubuntu took 一共执行了4次。所以继续优化

 以优化get_conda_artifact 为例

@timeit
    def get_conda_artifact(self) -> Artifact:
        if self.command_not_exist("conda -v"):
            return CondaArtifact("", [], [])

        return CondaArtifact(version=self.get_conda_version(), environments=(self.get_conda_environments()),
                             source=self.get_conda_source(is_apt=self.is_ubuntu()))

 主要是将get_conda_version,get_conda_environments, get_conda_source继续并行。

注: 下文演示了executor.submit的函数包含参数的形式。

    @timeit
    def get_conda_artifact(self) -> Artifact:
        if self.command_not_exist("conda -v"):
            return CondaArtifact("", [], [])
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            version_future = executor.submit(self.get_conda_version)
            environments_future = executor.submit(self.get_conda_environments)
            source_future = executor.submit(self.get_conda_source, is_apt=self.is_ubuntu())

            return CondaArtifact(version=version_future.result(), environments=environments_future.result(),
                                 source=source_future.result())

 至于其他的如消除操作系统的交互需求的,主要是基于镜像的元数据进行交互,因为镜像元数据获取,不到1s。具体可以参见如下代码:

    def __init__(self, image_name, container: Container, image_meta) -> None:
        super().__init__()
        meta_str = json.dumps(image_meta).lower()
        if "ubuntu" in meta_str:
            self._is_ubuntu = True
        if "centos" in meta_str:
            self._is_centos = True
        self.image_name = image_name
        self.container = container
        self.ps_lines = self.get_command_result("ps -ef").splitlines()
        self.image_id = self.container.image.id

 如果继续优化,则是将每次执行,先判断是否包含,其实也可以直接执行命令,根据返回的错误码直接判断是否有无,我就是停在了这里

    @timeit
    def get_sshd_artifact(self) -> Artifact:
        if self.contains_sshd_service():
            return SshdArtifact(True, version=self.get_sshd_version())
        return SshdArtifact(False)

 当前的执行结果为:

INFO:     Application startup complete.
get_image_meta took 0.0800
get_image_container took 0.9872
启动容器, 镜像名称: 10.200.88.53/sunning-zhejianglab.com/vit:v2, 容器id: 040f66ab3183
__is_ubuntu took 0.0000
__is_ubuntu took 0.0000
__is_ubuntu took 0.0000
contains_service took 0.0000
contains_sshd_service took 0.0025
contains_service took 0.0000
contains_jupyter_lab_service took 0.0091
get_jupyter_lab_artifact took 0.0098
get_kernel_version took 0.4791
get_kernel_artifact took 0.4802
get_os_release_contents took 3.2166
get_ubuntu_version took 3.2197
get_os_artifact took 3.2201
get_sshd_version took 3.4866
get_sshd_artifact took 3.4893
__is_ubuntu took 0.0000
get_pip_artifact took 5.1001
get_conda_version took 2.4581
get_package_manager_artifact took 6.0547
get_python_artifact took 6.9397
get_conda_environments took 3.8802
get_conda_artifact took 7.4389
get_artifact_result_parallel took 7.4907

 check_image一个本地镜像的环境,基本上花费的时间为8s左右。

threading.Thread的用法

解决的问题

 这个主要是为了解决,当我拉取镜像,超过了5s的时间,我就直接向用户返回超时了,后台继续拉取。因此,我肯定也是要使用后台启动线程的。

 但是这个超时的问题,纠结了自己很久,一方面,刚使用了concurrent.futures.ThreadPoolExecutor,而其在获取result的时候也有超时异常,但自己在为了解决镜像拉取时的代码

    def pull_image(self, image_name, log_path=None, to_stdout=True):
        """
        仅仅负责拉取镜像
        :param to_stdout: 是否要将镜像拉取日志输出到标准输出
        :param log_path: 待写入的日志文件路径
        :param image_name:基础镜像名称
        :return:
        """
        last_colon_index = image_name.rfind(":")
        if last_colon_index == -1:
            repository, tag = image_name, ""
        else:
            repository, tag = image_name[0:last_colon_index], image_name[last_colon_index + 1:]

        auth_config = self.get_auth_config(image_name)

        # 当前优化的程序,在check时,一般会在8秒内进行镜像检测,因此拉取镜像,如果5秒仍然无法拉取成功,则提示前端错误,并异步拉取镜像
        generator = self.api_client.pull(repository, tag, stream=True, auth_config=auth_config)
        self._log_pull_image_process(generator, log_path, to_stdout=to_stdout)

 开始的时候,自己尝试将generator和打印日志,用try包括,使用future.result(timeout=5),但这个地方就是不抛出异常,太折磨死了。之后,我就感觉,可能是generator是异步返回的,日志是流式处理的,因此成功执行了,虽然在这个地方设置了future.result(timeout=5),也是不生效的。

 而这个时候,楼主其实已经很累了,因为毕竟自己要赶紧投入时间到动态量化上啊。

 一开始,自己尝试了使用装饰器使用signal设置函数超时异常来解决,但是,这个是太强制了,而且它直接将当前的拉取镜像的线程所在的线程给终止了。这就不是自己的本意了。没办法,自己就继续想方法。能不能让拉取的镜像在另外一个线程中进行呢,主线程直接sleep(5)之后,判断如果线程还是活着的,就直接抛异常,让这个拉取镜像的线程继续运行,沿着这个思路,正好发现了threading.Thread,更发现了其有join(timeout)的方法,简直是量身定做。

应用场景

 这个其实是主线程执行的动作,启动了镜像拉取线程,下述代码演示了元组参数和具名参数的情形。

def async_pull_image(image_name, image_utils, log_path=None, to_stdout=True):
    thread = threading.Thread(target=image_utils.pull_image_to_fastbuild, args=(image_name,),
                              kwargs={"log_path": log_path, "to_stdout": to_stdout})
    thread.start()
    thread.join(5)

    if thread.is_alive():
        raise FBException(code=15004, message=f"镜像{image_name}拉取时间超时,FastBuild系统将在后台拉取镜像,"
                                              f"请5分钟后再尝试使用当前镜像进行镜像构建任务")

注,抛出的全局捕获异常,用于直接在会话中返回给前端错误,这个方法很灵活,而且还可以统一全局错误码。

 下述代码是使用threading.Thread的另外一个情形了,可以看到需要提供两个位置参数,而位置参数,均放在args元组中即可。

    @staticmethod
    def post_state(callback_url, state):
        thread_name = f"回调函数{state['taskId']}线程"
        headers = DBCallbackService.query_latest_callback().headers
        threading.Thread(target=CallBackService.state_upload, args=(callback_url, state, headers),
                         name=thread_name).start()
        return Response.success(msg=f"{thread_name},请等待回调结束", data=state)

全局捕获异常

 全局捕获异常可以参见 状态码与异常处理,里面演示了全局捕获异常的实践方式。

要解决的问题

 核心要解决的问题,就是前端调用我的程序,如果在15秒以内,不回应,页面就发出Bad Request的响应了,用户体验非常的差。

 通过使用异常,可以直接将会话中的底层异常抛出,而在全局异常中返回给用户提示信息,非常的优雅。

image-20240508213707018

实践

# 全局异常处理中间件
@app.exception_handler(FBException)
async def http_exception_handler(request: Request, exc: FBException):
    print(f"全局异常处理中间件: {exc.message}")
    if request.url:
        print(f"request_url: {request.url}")
    else:
        print(f"系统发生异常FBException")
    return JSONResponse(
        status_code=200,
        content={"message": f"{exc.message}", "code": exc.code}
    )

 主要是解决了超时返回的问题:

def async_pull_image(image_name, image_utils, log_path=None, to_stdout=True):
    thread = threading.Thread(target=image_utils.pull_image_to_fastbuild, args=(image_name,),
                              kwargs={"log_path": log_path, "to_stdout": to_stdout})
    thread.start()
    thread.join(5)

    if thread.is_alive():
        raise FBException(code=15004, message=f"镜像{image_name}拉取时间超时,FastBuild系统将在后台拉取镜像,"
                                              f"请5分钟后再尝试使用当前镜像进行镜像构建任务")

 这样,在平台进行check的时候,自己可以直接将异常提示信息抛出,而后台继续拉取这个镜像。这样就解决了镜像拉取和check两个耗时操作耦合在一起的问题了。

image-20240508213835949

代码实用技巧

zip和列表推导式

 下述代码演示了列表推导式,以及使用zip,重组两个数组的演示过程,还是可以看出代码还是非常凝练的。

 下述代码的作用,是根据三个命令,获取容器中对应的python运行环境。

    def get_multiple_python_version(self):
        version_info = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            python_futures = [executor.submit(self.get_python_version, python_cmd)
                              for python_cmd in ["python -V", "python2 -V", "python3 -V"]]

            return [f"{k}({v.result()})" for k, v in dict(zip(["python", "python2", "python3"], python_futures)).items()
                    if v.result()]

装饰器

统计时间
def timeit(method):
    """
    A decorator that reports the execution time.
    """

    @wraps(method)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = method(*args, **kwargs)
        end_time = time.time()
        print(f"{method.__name__} took {end_time - start_time:.4f}")

        return result

    return wrapper

 使用的时候非常的方便

    @timeit
    def get_kernel_artifact(self) -> Artifact:
        return KernelArtifact(self.get_kernel_version())

 即可得到

get_image_container took 0.9041
启动容器, 镜像名称: 10.200.88.53/sunning-zhejianglab.com/vit:v2, 容器id: cd391ddc3278
get_kernel_version took 3.7441
get_kernel_artifact took 3.7442
get_sshd_version took 4.4891
get_sshd_artifact took 8.4335
get_jupyter_lab_artifact took 9.9615
get_os_release_contents took 4.1719
get_ubuntu_version took 4.1720
get_os_artifact took 10.3813
get_python_artifact took 11.9672
get_package_manager_artifact took 13.0781
get_pip_artifact took 14.1710
get_conda_artifact took 17.4669
get_artifact_result_parallel took 17.5001
get_image_meta took 0.0626
镜像类型: amd64
镜像大小: 32.44G

 这样的函数统计时间,对于问题定位一目了然。

使用signal设置函数超时异常

 这个是为了当时程序拉取镜像,比较大的时候,我想要提前返回,通过全局异常告诉前端,让后端继续执行。但比较遗憾,这个会把当前会话线程直接中断,所以最后我没有使用这个方案。

def timeout(seconds=10, error_message="Function call timed out"):
    def decorator(func):
        def _handle_timeout(signum, frame):
            print("处理函数调用时,超时了。")
            raise FBException(code=15004, message="镜像拉取时间超时,系统将在后台拉取镜像,请稍后再次尝试")

        @wraps(func)
        def wrapper(*args, **kwargs):
            # 设置信号处理器
            signal.signal(signal.SIGALRM, _handle_timeout)
            # 设置超时时间
            signal.alarm(seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                # 取消信号
                signal.alarm(0)
            return result

        return wrapper

    return decorator

 这里其实也是使用装饰器模式,处理异常时,抛出了一个全局异常,就比较方便。

总结

 自己可真的是蔡格尼克效应的实在的表现。简直是有一件事情之后,自己一定要把它做完,才能放下他,不被它占据自己的注意力。

 对于当前的自己来说,其实更加紧急的事515需求,因为推理啊,量化啊,这些知识其实自己都不懂的,压力很大。

 最近自己又在京东上购买了一本书《心若菩提》和印象笔记的Evermarker,这两天因为这个FastBuild的第二次优化,就很焦虑,不过好在又到了一个新的状态保存点了。接下来,就是全力以赴搞量化了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

刷题训练之模拟

> 作者:დ旧言~ > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:熟练掌握模拟算法。 > 毒鸡汤:学习,学习,再学习 ! 学,然后知不足。 > 专栏选自:刷题训…

华为车BU迈入新阶段,新任CEO对智能车的3个预判

作者 |张马也 编辑 |德新 4月24日,北京车展前夕,华为召开了新一年的智能汽车解决方案新品发布会。 这次发布会,也是华为智能汽车解决方案BU(简称「车BU」)CEO 靳玉志的公开首秀。 一开场,靳玉志即抛出了…

损失一件外套?

2024/05/07,晴 碎碎念一波! 早上洗漱完要出门时,发现自己昨天穿的外套不见了!!!外套上身效果很不错,买了1年多穿的频率非常高,现在丢了还真挺可惜。 衣服口袋有一个耳机&#xff0…

信创基础软件之数据库

一、数据库概述 数据库是一种用于存储和管理拥有固定格式和结构数据的仓库型数据管理系统。其主要用于业务数据的存储和业务逻辑运算,具体负责保障数据的安全性、完整性、多用户对数据的并发使用以及发生故障后的系统恢复。 二、数据库的体系架构 数据库内核:对数…

Java中next()与nextLine()的区别[不废话,直接讲例子]

在使用牛客进行刷题时,我们很多时候会遇到这样的情况: 区别很简单,如果你要输入用空格或者回车分开的数据如: abc_def_ghi 这三组数据( _ 是空格) 用hasNext: 执行结果: 如果只用换行符号进行…

返回链表的中间节点题目讲解(超快方法)

一:题目 二:思路讲解 采用快慢指针方法来解决 1:slow指针一次跳一个节点,fast指针一次跳两个节点,这样当fast到尾节点的时候,slow刚好到中间节点,但是奇数个的时候,fast不会刚好的…

Java | Leetcode Java题解之第59题螺旋矩阵II

题目&#xff1a; 题解&#xff1a; class Solution {public int[][] generateMatrix(int n) {int num 1;int[][] matrix new int[n][n];int left 0, right n - 1, top 0, bottom n - 1;while (left < right && top < bottom) {for (int column left; co…

DenseCLIP环境配置

直接看raoyongming/DenseCLIP: [CVPR 2022] DenseCLIP: Language-Guided Dense Prediction with Context-Aware Prompting (github.com) 但这里的环境配置可能和现在不太适配&#xff0c;自己配了好久没弄好 后面尝试了另外的版本的&#xff08;但这个版本少了一些内容&#…

MySQL-ELK基础

1&#xff1a;什么是 ELK ELK是由一家elastic公司开发的三个开源项目的首字母缩写&#xff0c;这三个项目分别是&#xff1a;Elasticsearch、Logstash 和 Kibana。三个项目各有不同的功能&#xff0c;之后又增加了许多新项目, 于是 从5.X版本后改名为Elastic Stack Elastic S…

电脑屏幕监控软件都有哪些 | 五大好用屏幕监控软件盘点

电脑屏幕监控软件在企业管理、家庭教育等方面发挥着越来越重要的作用。 这些软件通过实时监控电脑屏幕活动&#xff0c;为用户提供了强大的管理和监控功能。 本文将为您盘点五大好用的电脑屏幕监控软件&#xff0c;帮助您更好地了解并选择适合自己的软件。 电脑屏幕监控软件都…

J1019基于SpringBoot的护肤品推荐系统设计与实现(源码+包运行+技术指导)

项目描述 临近学期结束&#xff0c;开始毕业设计制作&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉的困难吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于SpringBoot的护…

Visual Studio C++ 2019进行安装

Visual Studio C 2019进行下载安装 链接&#xff1a;https://my.visualstudio.com/Downloads?qvisual%20studio%202017&wt.mc_idomsftvscom~older-downloads

Linux网站服务

1.概念:HTML:超级文本编辑语言 网页:使用HTML,PHP,JAVA语言格式书写的文件。 主页:网页中呈现用户的第一个界面。 网站:多个网页组合而成的一台网站服务器。 URL:统一资源定位符&#xff0c;访问网站的地址。 网站架构:LAMP: LinuxApacheMYSQLPHP(系统服务器程序数据管理…

Mamba结构的Demo源码解读

文章目录 前言一、mamba结构构建辅助函数解读1、dataclass方法解读2、Norm归一化LayerNormRMSNormRMSNorm源码 3、nn.Parameter方法解读 二、mamba原理二、mamba模型构建1、主函数入口源码解读2、Mamba类源码解读 三、ResidualBlock的mamba结构源码解读四、MambaBlock构成Resid…

【C++】二叉搜索树(手撕插入、删除、寻找)

一、什么是二叉搜索树 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值它的左…

collections模块

tuple的功能 只有可哈希的对象才可以作为字典的key&#xff0c;而immutable的对象是可哈希的 tuple的拆包&#xff0c;分别进行映射 拆包的灵活用法 tuple的不可变不是绝对的 nametuple的详解 __slots__是用于限制class里面有那些属性值的&#xff0c;可以自行去了解一下 f…

Python基础详解三

一&#xff0c;函数的多返回值 def methodReturn():return 1,2x,ymethodReturn() print(x,y) 1 2 二&#xff0c;函数的多种参数使用形式 缺省参数&#xff1a; def method7(name,age,address"淄博"):print("name:"name",age"str(age)&quo…

你需要知道vim操作 源头都在vimtutor里

vim之源&#xff1a;vimtutor vim的五种模式Normal mode&#xff08;正常模式&#xff09;Insert mode&#xff08;插入模式&#xff09;Visual mode&#xff08;可视模式&#xff09;Replace mode&#xff08;替换模式&#xff09;Command-line mode&#xff08;命令行模式&am…

Gradle 基础学习(三) 认识Command-Line Interface

Gradle命令行接口 除了IDE外&#xff0c;我们主要通过Gradle命令行接口来运行Gradle任务和管理Gradle项目。 下面是Gradle命令行使用的一些参考&#xff0c;熟悉后建议实际项目中使用Gradle Wrapper&#xff0c;gradle用法都可以替换为gradlew (macOS / Linux) 或gradlew.bat…

【SDN:逻辑上集中的控制平面,路由选择算法,LS路由工作过程,距离矢量路由选择(distance vector routing)】

文章目录 SDN&#xff1a;逻辑上集中的控制平面SDN的主要思路SDN控制平面和数据平面分离的优势SDN 架构: 数据平面交换机 路由选择算法路由(route)的概念最优化原则(optimality principle)路由的原则路由算法的分类LS路由工作过程&#xff08;相当于一个上帝&#xff09;链路状…
最新文章