深入理解 Linux 物理内存分配全链路实现

news2024/11/16 13:55:44

目录

内核物理内存分配接口

物理内存分配内核源码实现

 内存分配的心脏 __alloc_pages

prepare_alloc_pages

内存慢速分配入口 alloc_pages_slowpath

 总结


内核物理内存分配接口

在物理内存分配成功的情况下, alloc_pages,alloc_page 函数返回的都是指向其申请的物理内存块第一个物理内存页 struct page 指针。大家可以直接理解成返回的是一块物理内存,而 CPU 可以直接访问的却是虚拟内存,所以内核又提供了一个函数 __get_free_pages ,该函数直接返回物理内存页的虚拟内存地址。用户可以直接使用。

物理内存分配内核源码实现

在介绍 Linux 内核关于内存分配的源码实现之前,我们需要先找到内存分配的入口函数在哪里,在上小节中为大家介绍的众多内存分配接口的依赖层级关系如下图所示

我们看到内存分配的任务最终会落在 alloc_pages 这个接口函数中,在 alloc_pages 中会调用 alloc_pages_node 进而调用 __alloc_pages_node 函数,最终通过 __alloc_pages 函数正式进入内核内存分配的世界~~

__alloc_pages 函数为 Linux 内核内存分配的核心入口函数      

 内存分配的心脏 __alloc_pages

/*
 * This is the 'heart' of the zoned buddy allocator.
 */
struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid,
                            nodemask_t *nodemask)
{
    // 用于指向分配成功的内存
    struct page *page;
    // 内存区域中的剩余内存需要在 WMARK_LOW 水位线之上才能进行内存分配,否则失败(初次尝试快速内存分配)
    unsigned int alloc_flags = ALLOC_WMARK_LOW;
    // 之前小节中介绍的内存分配掩码集合
    gfp_t alloc_gfp; 
    // 用于在不同内存分配辅助函数中传递参数
    struct alloc_context ac = { };

    // 检查用于向伙伴系统申请内存容量的分配阶 order 的合法性
    // 内核定义最大分配阶 MAX_ORDER -1 = 10,也就是说一次最多只能从伙伴系统中申请 1024 个内存页。
    if (WARN_ON_ONCE_GFP(order >= MAX_ORDER, gfp))
        return NULL;
    // 表示在内存分配期间进程可以休眠阻塞
    gfp &= gfp_allowed_mask;

    alloc_gfp = gfp;
    // 初始化 alloc_context,并为接下来的快速内存分配设置相关 gfp
    if (!prepare_alloc_pages(gfp, order, preferred_nid, nodemask, &ac,
            &alloc_gfp, &alloc_flags))
        // 提前判断本次内存分配是否能够成功,如果不能则尽早失败
        return NULL;

    // 避免内存碎片化的相关分配标识设置,可暂时忽略
    alloc_flags |= alloc_flags_nofragment(ac.preferred_zoneref->zone, gfp);

    // 内存分配快速路径:第一次尝试从底层伙伴系统分配内存,注意此时是在 WMARK_LOW 水位线之上分配内存
    page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac);
    if (likely(page))
        // 如果内存分配成功则直接返回
        goto out;
    // 流程走到这里表示内存分配在快速路径下失败
    // 这里需要恢复最初的内存分配标识设置,后续会尝试更加激进的内存分配策略
    alloc_gfp = gfp;
    // 恢复最初的 node mask 因为它可能在第一次内存分配的过程中被改变
    // 本函数中 nodemask 起初被设置为 null
    ac.nodemask = nodemask;

    // 在第一次快速内存分配失败之后,说明内存已经不足了,内核需要做更多的工作
    // 比如通过 kswap 回收内存,或者直接内存回收等方式获取更多的空闲内存以满足内存分配的需求
    // 所以下面的过程称之为慢速分配路径
    page = __alloc_pages_slowpath(alloc_gfp, order, &ac);

out:
    // 内存分配成功,直接返回 page。否则返回 NULL
    return page;
}
  • 首先内核会尝试在内存水位线 WMARK_LOW 之上快速的进行一次内存分配。这一点我们从开始的 unsigned int alloc_flags = ALLOC_WMARK_LOW 语句中可以看得出来。

  • 校验本次内存分配指定伙伴系统的分配阶 order 的有效性,伙伴系统在内核中的最大分配阶定义在 /include/linux/mmzone.h 文件中,最大分配阶 MAX_ORDER -1  = 10,也就是说一次最多只能从伙伴系统中申请 1024 个内存页,对应 4M 大小的连续物理内存。

  • 调用 prepare_alloc_pages 初始化 alloc_context ,用于在不同内存分配辅助函数中传递内存分配参数。为接下来即将进行的快速内存分配做准备。

  • struct alloc_context {
        // 运行进程 CPU 所在 NUMA  节点以及其所有备用 NUMA 节点中允许内存分配的内存区域
        struct zonelist *zonelist;
        // NUMA  节点状态掩码
        nodemask_t *nodemask;
        // 内存分配优先级最高的内存区域 zone
        struct zoneref *preferred_zoneref;
        // 物理内存页的迁移类型分为:不可迁移,可回收,可迁移类型,防止内存碎片
        int migratetype;
    
        // 内存分配最高优先级的内存区域 zone
        enum zone_type highest_zoneidx;
        // 是否允许当前 NUMA 节点中的脏页均衡扩散迁移至其他 NUMA 节点
        bool spread_dirty_pages;
    };

  • 调用 get_page_from_freelist 方法首次尝试在伙伴系统中进行内存分配,这次内存分配比较快速,只是快速的扫描一下各个内存区域中是否有足够的空闲内存能够满足本次内存分配,如果有则立马从伙伴系统中申请,如果没有立即返回, page 设置为 null,进行后续慢速内存分配处理。

  • 这里需要注意的是:首次尝试的快速内存分配是在 WMARK_LOW 水位线之上进行的。

  • 当快速内存分配失败之后,情况就会变得非常复杂,内核将不得不做更多的工作,比如开启 kswapd 进程异步内存回收,更极端的情况则需要进行直接内存回收,或者直接内存整理以获取更多的空闲连续内存。这一切的复杂逻辑全部封装在 __alloc_pages_slowpath 函数中。

  • 总体流程介绍完之后,我们接着来看一下以上内存分配过程涉及到的三个重要内存分配辅助函数:prepare_alloc_pages,__alloc_pages_slowpath,get_page_from_freelist

prepare_alloc_pages

prepare_alloc_pages 初始化 alloc_context ,用于在不同内存分配辅助函数中传递内存分配参数,为接下来即将进行的快速内存分配做准备。

prepare_alloc_pages 主要的任务就是在快速内存分配开始之前,做一些准备初始化的工作,其中最核心的就是从指定 NUMA 节点中,根据  gfp_mask 掩码中的内存区域修饰符获取可以进行内存分配的所有内存区域 zone (包括其他备用 NUMA 节点中包含的内存区域)。

NUMA 节点的数据结构 struct pglist_data。struct pglist_data 结构中不仅包含了本 NUMA 节点中的所有内存区域,还包括了其他备用 NUMA 节点中的物理内存区域,当本节点中内存不足的情况下,内核会从备用 NUMA 节点中的内存区域进行跨节点内存分配。

我们可以根据 nid 和 gfp_mask 掩码中的物理内存区域描述符利用 node_zonelist 函数一次性获取允许进行内存分配的所有内存区域(所有 NUMA 节点)。

内存慢速分配入口 alloc_pages_slowpath

alloc_pages_slowpath 函数非常的复杂,其中包含了内存分配的各种异常情况的处理,并且会根据前边介绍的 GFP_,ALLOC_ 等各种内存分配策略掩码进行不同分支的处理,这样就变得非常的庞大而繁杂。

static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                        struct alloc_context *ac)
{
        ......... 初始化慢速内存分配路径下的相关参数 .......

retry_cpuset:

        ......... 调整内存分配策略 alloc_flags 采用更加激进方式获取内存 ......
        ......... 此时内存分配主要是在进程所允许运行的 CPU 相关联的 NUMA 节点上 ......
        ......... 内存水位线下调至 WMARK_MIN ...........
        ......... 唤醒所有 kswapd 进程进行异步内存回收  ...........
        ......... 触发直接内存整理 direct_compact 来获取更多的连续空闲内存 ......

retry:

        ......... 进一步调整内存分配策略 alloc_flags 使用更加激进的非常手段进行内存分配 ...........
        ......... 在内存分配时忽略内存水位线 ...........
        ......... 触发直接内存回收 direct_reclaim ...........
        ......... 再次触发直接内存整理 direct_compact ...........
        ......... 最后的杀手锏触发 OOM 机制  ...........

nopage:
        ......... 经过以上激进的内存分配手段仍然无法满足内存分配就会来到这里 ......
        ......... 如果设置了 __GFP_NOFAIL 不允许内存分配失败,则不停重试上述内存分配过程 ......

fail:
        ......... 内存分配失败,输出告警信息 ........

      warn_alloc(gfp_mask, ac->nodemask,
            "page allocation failure: order:%u", order);
got_pg:
        ......... 内存分配成功,返回新申请的内存块 ........

      return page;
}

内存分配在慢速路径下所需要的相关参数 :

static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                        struct alloc_context *ac)
{
    // 在慢速内存分配路径中可能会导致内核进行直接内存回收
    // 这里设置 __GFP_DIRECT_RECLAIM 表示允许内核进行直接内存回收
    bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM;
    // 本次内存分配是否是针对大量内存页的分配,内核定义 PAGE_ALLOC_COSTLY_ORDER = 3
    // 也就是说内存请求内存页的数量大于 2 ^ 3 = 8 个内存页时,costly_order = true,后续会影响是否进行 OOM
    const bool costly_order = order > PAGE_ALLOC_COSTLY_ORDER;
    // 用于指向成功申请的内存
    struct page *page = NULL;
    // 内存分配标识,后续会根据不同标识进入到不同的内存分配逻辑处理分支
    unsigned int alloc_flags;
    // 后续用于记录直接内存回收了多少内存页
    unsigned long did_some_progress;
    // 关于内存整理相关参数
    enum compact_priority compact_priority;
    enum compact_result compact_result;
    int compaction_retries;
    // 记录重试的次数,超过一定的次数(16次)则内存分配失败
    int no_progress_loops;
    // 临时保存调整后的内存分配策略
    int reserve_flags;

    // 流程现在来到了慢速内存分配这里,说明快速分配路径已经失败了
    // 内核需要对 gfp_mask 分配行为掩码做一些修改,修改为一些更可能导致内存分配成功的标识
    // 因为接下来的直接内存回收非常耗时可能会导致进程阻塞睡眠,不适用原子 __GFP_ATOMIC 内存分配的上下文。
    if (WARN_ON_ONCE((gfp_mask & (__GFP_ATOMIC|__GFP_DIRECT_RECLAIM)) ==
                (__GFP_ATOMIC|__GFP_DIRECT_RECLAIM)))
        gfp_mask &= ~__GFP_ATOMIC;

retry_cpuset:

retry:

nopage:

fail:

got_pg:

alloc_pages_slowpath 的内存分配逻辑:

static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                        struct alloc_context *ac)
{
        ......... 初始化慢速内存分配路径下的相关参数 .......

retry_cpuset:

    // 在之前的快速内存分配路径下设置的相关分配策略比较保守,不是很激进,用于在 WMARK_LOW 水位线之上进行快速内存分配
    // 走到这里表示快速内存分配失败,此时空闲内存严重不足了
    // 所以在慢速内存分配路径下需要重新设置更加激进的内存分配策略,采用更大的代价来分配内存
    alloc_flags = gfp_to_alloc_flags(gfp_mask);

    // 重新按照新的设置按照内存区域优先级计算 zonelist 的迭代起点(最高优先级的 zone)
    // fast path 和 slow path 的设置不同所以这里需要重新计算
    ac->preferred_zoneref = first_zones_zonelist(ac->zonelist,
                    ac->highest_zoneidx, ac->nodemask);
    // 如果没有合适的内存分配区域,则跳转到 nopage , 内存分配失败
    if (!ac->preferred_zoneref->zone)
        goto nopage;
    // 唤醒所有的 kswapd 进程异步回收内存
    if (alloc_flags & ALLOC_KSWAPD)
        wake_all_kswapds(order, gfp_mask, ac);

    // 此时所有的 kswapd 进程已经被唤醒,正在异步进行内存回收
    // 之前我们已经在 gfp_to_alloc_flags 方法中重新调整了 alloc_flags
    // 换成了一套更加激进的内存分配策略,注意此时是在 WMARK_MIN 水位线之上进行内存分配
    // 调整后的 alloc_flags 很可能会立即成功,因此这里先尝试一下
    page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
    if (page)
        // 内存分配成功,跳转到 got_pg 直接返回 page
        goto got_pg;

    // 对于分配大内存来说 costly_order = true (超过 8 个内存页),需要首先进行内存整理,这样内核可以避免直接内存回收从而获取更多的连续空闲内存页
    // 对于需要分配不可移动的高阶内存的情况,也需要先进行内存整理,防止永久内存碎片
    if (can_direct_reclaim &&
            (costly_order ||
               (order > 0 && ac->migratetype != MIGRATE_MOVABLE))
            && !gfp_pfmemalloc_allowed(gfp_mask)) {
        // 进行直接内存整理,获取更多的连续空闲内存防止内存碎片
        page = __alloc_pages_direct_compact(gfp_mask, order,
                        alloc_flags, ac,
                        INIT_COMPACT_PRIORITY,
                        &compact_result);
        if (page)
            goto got_pg;

        if (costly_order && (gfp_mask & __GFP_NORETRY)) {
            // 流程走到这里表示经过内存整理之后依然没有足够的内存供分配
            // 但是设置了 NORETRY 标识不允许重试,那么就直接失败,跳转到 nopage
            if (compact_result == COMPACT_SKIPPED ||
                compact_result == COMPACT_DEFERRED)
                goto nopage;
            // 同步内存整理开销太大,后续开启异步内存整理
            compact_priority = INIT_COMPACT_PRIORITY;
        }
    }

retry:

nopage:

fail:

got_pg:
    return page;
}

之前我们介绍到快速分配路径是在  WMARK_LOW 水位线之上进行内存分配,与其相配套的内存分配策略比较保守,目的是快速的在各个内存区域 zone 之间搜索可供分配的空闲内存。快速分配路径下的失败意味着此时系统中的空闲内存已经不足了,所以在慢速分配路径下内核需要改变内存分配策略,采用更加激进的方式来进行内存分配,

  • 首先会把内存分配水位线降低到 WMARK_MIN 之上,然后将内存分配策略调整为更加容易促使内存分配成功的策略。
  • 当剩余内存处于 WMARK_MIN 与 WMARK_LOW 之间时,内核会唤醒所有 kswapd 进程来异步回收内存,直到剩余内存重新回到水位线 WMARK_HIGH 之上。
  • 到目前为止,内核已经在慢速分配路径下通过 gfp_to_alloc_flags 调整为更加激进的内存分配策略,并将水位线降低到 WMARK_MIN,同时也唤醒了 kswapd 进程来异步回收内存。
  • 此时在新的内存分配策略下进行内存分配很可能会一次性成功,所以内核会首先尝试进行一次内存分配。
  • 如果首次尝试分配内存失败之后,内核就需要进行直接内存整理 direct_compact (整理脆片)来获取更多的可供分配的连续内存页。
  • 如果经过 direct_compact 之后依然没有足够的内存可供分配,那么就会进入 retry 分支采用更加激进的方式来分配内存。如果内存分配策略设置了 __GFP_NORETRY 表示不允许重试,那么就会直接失败,流程跳转到 nopage 分支进行处理。
  • 内存分配流程来到 retry 分支这里说明情况已经变得非常危急了,在经过 retry_cpuset 分支的处理,内核将内存水位线下调至 WMARK_MIN,并开启了 kswapd 进程进行异步内存回收,触发直接内存整理 direct_compact,在采取了这些措施之后,依然无法满足内存分配的需求。

  • 内核会近一步采取更加激进的非常手段来获取连续的空闲内存,一开始需要调用 __gfp_pfmemalloc_flags 函数来重新调整内存分配策略,调整后的策略为:后续内存分配会忽略水位线的影响,并且允许内核从紧急预留内存中获取内存。

  • 在调整好更加激进的内存分配策略 alloc_flags 之后,内核会首先尝试从伙伴系统中进行一次内存分配,这时会有很大概率促使内存分配成功。

  • 如果在忽略内存水位线的情况下,内存依然分配失败,则进行直接内存回收 direct_reclaim 。

  • 经过 direct_reclaim 之后,仍然没有足够的内存可供分配的话,那么内核会再次进行直接内存整理  direct_compact 。

  • 如果 direct_compact 之后还是没有足够的内存,那么现在内核已经处于绝境了,是时候使用杀手锏:触发 OOM 机制杀死得分最高的进程以获取更多的空闲内存。

下面内核也不会直接开始 OOM,而是进入到重试流程,在重试流程开始之前内核需要调用 should_reclaim_retry 判断是否应该进行重试,重试标准:

  1. 如果内核已经重试了 MAX_RECLAIM_RETRIES (16) 次仍然失败,则放弃重试执行后续 OOM。

  2. 如果内核将所有可选内存区域中的所有可回收页面全部回收之后,仍然无法满足内存的分配,那么放弃重试执行后续 OOM。

到现在为止,内核已经尝试了包括 OOM 在内的所有回收内存的措施,但是仍然没有足够的内存来满足分配要求,看上去此次内存分配就要宣告失败了。但是这里还有一定的回旋余地,如果内存分配策略中配置了 __GFP_NOFAIL,则表示此次内存分配非常的重要,不允许失败。内核会在这里不停的重试直到分配成功为止。当 CPU 自己所在的本地 NUMA 节点内存不足时,CPU 就需要跨 NUMA 节点去访问其他内存节点,这种跨 NUMA 节点分配内存的行为就发生在这里,这种情况下 CPU 访问内存就会慢很多

这里笔者需要着重强调的一点就是,在 nopage 分支中决定开始重试之前,内核不能立即进行重试流程,因为之前已经经历过那么多严格激进的内存回收策略仍然没有足够的内存,内存现状非常紧急。

所以我们有理由相信,如果内核立即开始重试的话,依然没有什么效果,反而会浪费过多时间在搜索空闲内存上,导致其他进程处于饥饿状态。

所以在开始重试之前,内核会调用 cond_resched() 让 CPU 重新调度到其他进程上,让其他进程也运行一会,与此同时 kswapd 进程一直在后台异步回收着内存。

当 CPU 重新调度回当前进程时,说不定 kswapd 进程已经回收了足够多的内存,重试成功的概率会大大增加同时又避免了资源的无谓消耗。

 总结

结合 Linux 内核 5.19 版本源码详细讨论了物理内存分配在内核中的整个链路实现。在整个链路中,内存的分配整体分为了两个路径:

  1. 快速路径 fast path:该路径的下,内存分配的逻辑比较简单,主要是在 WMARK_LOW 水位线之上快速的扫描一下各个内存区域中是否有足够的空闲内存能够满足本次内存分配,如果有则立马从伙伴系统中申请,如果没有立即返回。

  2. 慢速路径 slow path:慢速路径下的内存分配逻辑就变的非常复杂了,其中包含了内存分配的各种异常情况的处理,并且会根据文中介绍的 GFP_,ALLOC_ 等各种内存分配策略掩码进行不同分支的处理,整个链路非常庞大且繁杂。

参考文献

深入理解 Linux 物理内存分配全链路实现

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

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

相关文章

2022最常用密码公布,你的账户安全吗?

密码管理工具 NordPass 公布了 2022 年最常用密码列表,以及破解密码所需的时间。该研究基于对来自 30 个不同国家 / 地区的 3TB 数据库的分析。研究人员将数据分为不同的垂直领域,使得其能够根据国家和性别进行统计分析。今年的研究主要聚焦于文化如何影…

工业软件对于现代制造业的生产效率和质量有何影响?

工业软件在提高现代制造业的生产力和质量方面发挥着至关重要的作用。比如: 流程自动化:工业软件可以实现各种制造流程的自动化,消除手动任务并减少人为错误。自动化通过简化操作、缩短周期时间和提高整体效率来提高生产力。它还可以最大限度地…

vue3和element plus踩坑

1.有说vue版本有两个,但检查之后发现只有一个,且为vue3的版本 2.也有说是因为命名的问题,组件名和页面名一致 最后发现是因为 在main.js里面引入element plus 使用这种use方式会报错,虽然也不知道为什么 import { createApp } …

《计算机系统与网络安全》第十一章 入侵检测与防御技术

🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁 🦄 个人主页——libin9iOak的博客🎐 🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~&#x1f33…

Dell-Precision5520 电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网,转载需注明出处。(下载请直接百度黑果魏叔) 硬件配置 硬件型号驱动情况 主板Dell-Precision5520 处理器Intel Core i7-7820HQ已驱动 内存Micron 2400MHz DDR4 16GB x2已驱动 硬盘Samsung 970EVO 512GB已驱动 显…

Java中volatile的作用和原理

用法 volatile 是 Java 中的关键字,直接修饰成员变量,不能和 final 关键字同时使用。 private volatile boolean flag false;作用 当一个变量被声明为volatile时,它可以确保以下两点: 保证可见性:当一个线程修改了…

三维天地助力高校实验室数字化智能决策分析

近年来,随着检验检测行业技术的不断发展,高校实验室管理的复杂程度也在不断提高。由于传统的检测实验室日常工作任务繁重、费时费力,存在数据或信息的手动录入、人工计算,纸质文档资料的长期保存,数据快速汇总困难等诸…

大数据面试题:Kafka的Message包括哪些信息

面试题来源: 《大数据面试题 V4.0》 大数据面试题V3.0,523道题,679页,46w字 参考答案: 一个 Kafka 的 Message 由一个固定长度的 header 和一个变长的消息体 body 组成,header 部分由一个字节的 magic&…

Android 12 LED 定制灯效开发小结

文章目录 背景:Android 10 的设备上测试正常Android 12 中目前出现无法闪烁的问题电量变化广播监听总结参考 背景: 在定制的Android 10系统中,通过修改 Framwork 层的代码后,调用标准的接口后,能实现 LED 灯的闪烁灯效…

抖音旋转验证码分析

旋转验证码类型challenge_code为99996, 拿到的旋转验证码通常都是如下: 待旋转的图片: 旋转的背景图: 加密分析过程 可以参考:https://blog.csdn.net/weixin_38819889/article/details/129727564 旋转的难点在于如何…

英国 Tortoise Media发布2023年全球AI指数排名;美团宣布完成收购光年之外

🦉 AI新闻 🚀 美团宣布完成收购光年之外,加强人工智能竞争力 摘要:美团在公告中宣布于2023年6月29日盘后收购光年之外的全部权益,以加强其在快速增长的人工智能行业中的竞争力。光年之外是中国领先的通用人工智能创新…

【ISO26262】汽车功能安全第一部分:术语

【tommi_wei@163.com】 故障响应时间 fault reaction time 从故障(2.42) 探测到进入安全状态(2.102) 的时间间隔。 故障容错时间间隔 fault tolerant time interval 在危害事件(2.59) 发生前, 系统(2.129) 中一个或多个故障(2.42) 可存在的时间间隔。 功能安全 functio…

C语言之网络高级编程笔记

基于Webserver的工业数据采集项目 html cgi Modbus协议 (应用层) 工具:Modus Slave/Poll wireshark Postman 一、Modbus起源 1.起源: Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。 Modbus通信协议具有多个变种&#xf…

【Matlab】神经网络遗传算法函数极值寻优——非线性函数求极值

目前关于神经网络遗传算法函数极值寻优——非线性函数求极值的博客资源已经不少了,我看了下来源,最初的应该是来自于Matlab中文论坛,论坛出版的《MATLAB神经网络30个案例分析》第4章就是《神经网络遗传算法函数极值寻优——非线性函数极值寻优…

考研算法35天:三元组的最小距离 【双指针,滑动窗口,多路归并】

算法详解 多路归并;多路归并算法从理论到应用(易懂)_留恋单行路的博客-CSDN博客 多路归并就是将多个已经归并排序排好序的数组再进行排序(不一定是通过归并排序)。 算法题目 这道题就是一般做法是先通过排序将三个数组排好然后再进行三指针求最小。但…

4.23 时域微积分特性

时域微分还有个证明方式 2式两边求导即可推出时域微分特性

ElasticSearch学习01——Windows10环境下ES安装经验与踩到的坑

由于对ES基本概念和历史演进在网上随处可查,所以本文在此不做赘述.随意本文直接讲述如何安装使用ES 1.安装JDK ​ ElasticSearch是基于lucence开发的,也就是运行需要java jdk支持。所以要先安装JAVA环境。由于ElasticSearch 5.x 往后依赖于JDK 1.8的,所…

配置Jenkins的slave agent并使用它完成构建任务

上一章,使用单机配置并运行了一个简单的maven项目,并发布到了一个服务器上启动。这一章将要配置一个slave agent,并将上一章的job放到agent上执行。我们agent使用的是ssh的方式 前置步骤 准备两台虚拟机: 192.168.233.32&#…

4.27 功率谱

功率信号能量一定是无穷大的 1处解释,由于上述信号是截断信号,只有-T/2 ~ T/2有有效信号,因此有了1式 能量信号和能量密度构成傅里叶变换对 功率信号和功率密度构成傅里叶变换对 自相关函数和他的能量谱或者功率谱构成傅里叶变换对

Quiz 12: Regular Expressions | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介Regular Expressions单选题(1-8)操作题Regular Expressions Python for Everybody 课程简介 Python for Everybody 零基础程序设计(Python 入门) This course aims to teach everyone the …