一文读懂 PyTorch 显存管理机制

2023-11-11

点击上方“视学算法”,选择加"星标"或“置顶

重磅干货,第一时间送达c5ea8a9a39090864e36f3474135cce45.png

作者丨米阿罗@知乎(已授权)

来源丨https://zhuanlan.zhihu.com/p/486360176

编辑丨极市平台

首发于踢翻炼丹炉:https://www.zhihu.com/column/c_1320691511223136256

二次转载须经作者授权

导读

 

本文细致的对PyTorch 显存管理机制进行了剖析,包括:寻找合适的block的步骤、实现自动碎片整理详解等,希望本文能帮助大家更透彻的理解显存优化。 

本文使用的 PyTorch 源码为 master 分支,commit id 为 a5b848aec10b15b1f903804308eed4140c5263cb(https://github.com/pytorch/pytorch/tree/a5b848aec10b15b1f903804308eed4140c5263cb)。

背景介绍

剖析 PyTorch 显存管理机制主要是为了减少显存碎片化带来的影响。一个简单示例为:

40f6cd54b174b799805a5c56102c4823.png

如上图所示,假设当前想分配 800MB 显存,虽然空闲的总显存有 1000MB,但是上方图的空闲显存由地址不连续的两个 500MB 的块组成,不够分配这 800MB 显存;而下方的图中,如果两个 500MB 的空闲块地址连续,就可以通过显存碎片的整理组成一个 1000MB 的整块,足够分配 800MB。上方图的这种情况就被称为显存碎片化

解决方法: 当有多个 Tensor 可以被释放时,可以优先释放那些在内存空间上前后有空闲内存的 Tensor。这样便于 PyTorch 整理这些空闲块组成的碎片,让三个块组成一整个更大的块。

核心问题/前提: 能否以非常小的代价( O(1) 或 O(logn) 复杂度)拿到当前想要释放的块,以及它前后的空闲空间大小?有了这个,我们可以对 Tensor 排序,优先选择:“前后空闲块+自己大小” 最大的 Tensor 来释放。

这一前提可以转化为:找到下面两个函数(pseudo code):

block = get_block(tensor) # 找到存储该 Tensor 的 Block
size = get_adjacent_free_size(block) # 返回该 Block 前后的空余空间,便于排序

研究 PyTorch 显存管理机制后可能能回答的问题:

  1. 为什么报错信息里提示显存够,但还是遇到了 OOM?

  2. 显存的多级分配机制是怎样的?为什么要这样设计?

源码解读

主要看 c10/cuda/CUDACachingAllocator.cpp 里面的 DeviceCachingAllocator 类(L403)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L403

主要的数据结构

Block:

  • 分配 / 管理内存块的基本单位,(stream_id, size, ptr) 三元组可以特异性定位一个 Block,即 Block 维护一个 ptr 指向大小为 size 的内存块,隶属于 stream_id 的 CUDA Stream。

  • 所有地址连续的 Block(不论是否为空闲,只要是由 Allocator::malloc 得来的)都被组织在一个双向链表里,便于在释放某一个 Block 时快速检查前后是否存在相邻碎片,若存在可以直接将这三个 Block 合成为一个。

struct Block { 
    int device; // gpu

    // Block 和被分配时调用者的 stream 是绑定的,即使释放了也无法给其他 stream 使用
    cudaStream_t stream; // allocation stream

    stream_set stream_uses; // streams on which the block was used
    size_t size; // block size in bytes
    BlockPool* pool; // owning memory pool
    void* ptr; // memory address
    bool allocated; // in-use flag
    Block* prev; // prev block if split from a larger allocation
    Block* next; // next block if split from a larger allocation
    int event_count; // number of outstanding CUDA events
};

BlockPool:

  • 内存池,用 std::set 存储 Block 的指针,按照 (cuda_stream_id -> block size -> addr) 的优先级从小到大排序。所有保存在 BlockPool 中的 Block 都是空闲的。

struct BlockPool {
    BlockPool(
        Comparison comparator,
        bool small,
        PrivatePool* private_pool = nullptr)
        : blocks(comparator), is_small(small), owner_PrivatePool(private_pool) {}
    std::set<Block*, Comparison> blocks;
    const bool is_small;
    PrivatePool* owner_PrivatePool; // 供 CUDA Graphs 使用,此处不详讲
};
  • DeviceCachingAllocator 中维护两种 BlockPool (large_blocks, small_blocks),分别存放较小的块和较大的块(为了分别加速小需求和大需求),简单地将 <= 1MB 的 Block 归类为小块,> 1MB 的为大块。

直观理解 Block、BlockPool 见下图:

c452b56b38a1cd1e1a6a73dcbbe4a8c9.png

BlockPool 示意图,左边两个 500MB 的块 size 相同,因此上面的比下面的地址更小

总结: Block 在 Allocator 内有两种组织方式,一种是显式地组织在 BlockPool(红黑树)中,按照大小排列;另一种是具有连续地址的 Block 隐式地组织在一个双向链表里(通过结构体内的 prev, next 指针),可以以 O(1) 时间查找前后 Block 是否空闲,便于在释放当前 Block 时合并碎片。

malloc 函数解析:返回一个可用的 Block(L466)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L466

一个区分:(出于简洁考虑,下面全文都对这两个表述进行区分,不多赘述)

  • size:请求分配的显存大小

  • alloc_size:需要 cudaMalloc 时的显存大小

一个 hard-coded: 根据 size 决定实际上的 alloc_size(get_allocation_size 函数,L1078):https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1078

  • 为小于 1MB 的 size 分配 2MB;

  • 为 1MB ~ 10MB 的 size 分配 20MB;

  • 为 >= 10MB 的 size 分配 { size 向上取整至 2MB 倍数 } MB。

寻找是否有合适的 block 会有五个步骤,如果这五个步骤都没找到合适的 Block,就会报经典的 [CUDA out of memory. Tried to allocate ...] 错误(具体见下“分配失败的情况”)。

步骤一:get_free_block 函数(L1088)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1088

TLDR:尝试在 Allocator 自己维护的池子中找一个大小适中的空闲 Block 返回。
* TLDR = Too Long; Didn't Read

  • 用当前的 (size, stream_id) 这二元组制作 Block Key 在对应的 BlockPool 中查找;

  • 环境变量 PYTORCH_CUDA_ALLOC_CONF 中指定了一个阈值 max_split_size_mb,有两种情况不会在此步骤分配:

  1. 需要的 size 小于阈值但查找到的 Block 的比阈值大(避免浪费block);

  2. 两方都大于阈值但 block size 比需要的 size 大得超过了 buffer(此处是 20MB,这样最大的碎片不超过 buffer 大小)。

  • 这里的这个阈值 max_split_size_mb 涉及一个有趣的特性,后面会讲到。

  • 若成功分配一个 Block,则将这个 Block 从 BlockPool 中删除。后续用完释放(free)时会再 insert 进去,见free_block : L976:https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L976)

步骤二:trigger_free_memory_callbacks 函数(L1106)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1106

TLDR:手动进行一波垃圾回收,回收掉没人用的 Block,再执行步骤一。

  • 若第一步的 get_free_block 失败,则在第二步中先调用 trigger_free_memory_callbacks,再调用一次第一步的 get_free_block

  • trigger_free_memory_callbacks 本质上通过注册调用了 CudaIPCCollect 函数,进而调用 CudaIPCSentDataLimbo::collect 函数(torch/csrc/CudaIPCTypes.cpp : L64);https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/torch/csrc/CudaIPCTypes.cpp#L64

  • CudaIPCSentDataLimbo 类管理所有 reference 不为 0 的 Block,所以实际上 collect 函数的调用算是一种懒更新,直到无 Block 可分配的时候才调用来清理那些 reference 已经为 0 的 Block(值得一提的是,该类的析构函数会首先调用 collect 函数,见 torch/csrc/CudaIPCTypes.cpp : L58)https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/torch/csrc/CudaIPCTypes.cpp#L58;

  • 相关源码可以看 torch/csrc/CudaIPCTypes.h & .cpp

步骤三:alloc_block 函数(L1115)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1115

TLDR:Allocator 在已有的 Block 中找不出可分配的了,就调用 cudaMalloc 创建新的 Block。

  • 步骤一、二中重用 block 失败,于是用 cudaMalloc 分配内存,大小为 alloc_size;

  • 注意有一个参数 set_fraction 会限制可分配的显存为当前剩余的显存 * fraction(若需要分配的超过这一限制则失败),但还没搞清楚哪里会指定这个(TODO);

  • 新分配的内存指针会被用于创建一个新 Block,新 Block 的 device 与 cuda_stream_id 与 caller 保持一致。

上面几个步骤都是试图找到一些空闲显存,下面是两个步骤是尝试进行碎片整理,凑出一个大块显存

步骤四:release_available_cached_blocks 函数(L1175)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1175

TLDR:先在自己的池子里释放一些比较大的 Block,再用 cudaMalloc 分配看看

  • 如果上面的 alloc_block 失败了,就会尝试先调用这一函数,找到比 size 小的 Block 中最大的,由大至小依次释放 Block,直到释放的 Block 大小总和 >= size(需要注意,这一步骤只会释放那些大小大于阈值 max_split_size_mb 的 Block,可以理解为先释放一些比较大的);

  • 释放 block 的函数见 release_block(L1241),主要就是 cudaFree 掉指针,再处理一些 CUDA graphs 的依赖,更新其他数据结构等等,最后把这个 Block 从 BlockPool 中移除;

  • 当前整个 BlockPool(作为提醒,Allocator 有两个 BlockPool,这里指的是最初根据 size 指定的大 pool 或小 pool)中,可以释放的 Block 需要满足两个条件:cuda_stream_id 相同的,且大小要大于阈值 max_split_size_mb。如果将这样的 Block 全部释放的空间仍比 size 小,那么这一步就会失败。

  • 释放掉了一批 Block 之后,再次执行步骤三中的 alloc_block 函数,创建新 Block。

步骤五:release_cached_blocks 函数(L1214)

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1214

  • 如果释放一些 Block 还不够分配,则把整个 Allocator 中的 large / small pool 全部释放掉(同样调用 release_block:L1241:https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1241),再次调用 alloc_block 函数。

malloc 分配失败的情况

https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L519-L560

会报经典的 CUDA out of memory. Tried to allocate ... 错误,例如:

CUDA out of memory. Tried to allocate 1.24 GiB (GPU 0; 15.78 GiB total capacity; 10.34 GiB already allocated; 435.50 MiB free; 14.21 GiB reserved in total by PyTorch)

  • Tried to allocate:指本次 malloc 时预计分配的 alloc_size;

  • total capacity:由 cudaMemGetInfo 返回的 device 显存总量;

  • already allocated:由统计数据记录,当前为止请求分配的 size 的总和;

  • free:由 cudaMemGetInfo 返回的 device 显存剩余量;

  • reserved:BlockPool 中所有 Block 的大小,与已经分配的 Block 大小的总和。
    即 [reserved] = [already allocated] + [sum size of 2 BlockPools]

注意,reserved + free 并不等同于 total capacity,因为 reserved 只记录了通过 PyTorch 分配的显存,如果用户手动调用 cudaMalloc 或通过其他手段分配到了显存,是没法在这个报错信息中追踪到的(又因为一般 PyTorch 分配的显存占大部分,分配失败的报错信息一般也是由 PyTorch 反馈的)。

在这个例子里,device 只剩 435.5MB,不够 1.24GB,而 PyTorch 自己保留了 14.21GB(储存在 Block 里),其中分配了 10.3GB,剩 3.9GB。那为何不能从这 3.9GB 剩余当中分配 1.2GB 呢?原因肯定是碎片化了,而且是做了整理也不行的情况。

实现自动碎片整理的关键特性:split

前面提到,通过环境变量会指定一个阈值 max_split_size_mb,实际上从变量名可以看出,指定的是最大的可以被 “split” 的 Block 的大小。

在本节最开头解释过,步骤三 alloc_block 函数通过 cuda 新申请的 Block(这是 Allocator 唯一一个创建新 Block 的途径)大小是计算出的 alloc_size 而不是用户通过 malloc 请求的大小 size。因此,如果 malloc 在上述五个步骤中成功返回了一个 Block,Allocator 会通过函数 should_split (L1068) 检查:

  • 由于过量分配内存,Block 内部产生的大小为 alloc_size - size 的碎片大小称为 remaining;

  • 如果这个 Block 属于 small_blocks 且 remaining >= 512 Bytes;或者 remaining >= 1MB 且该 Block 大小没有超过上述的阈值,则这个 Block 需要被 split。

被 split 的操作很简单,当前的 Block 会被拆分成两个 Block,第一个大小正好为请求分配的 size,第二个则大小为 remaining,被挂到当前 Block 的 next 指针上(这一过程见源码 L570~L584:https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L570-L584)。这样一来,这两个 Block 的地址自然而然成为连续的了。随着程序运行,较大的 Block(只要仍小于阈值 max_split_size_mb)会不断被分成小的 Block。值得注意的是,由于新 Block 的产生途径只有一条,即通过步骤三中的 alloc_block 函数经由 cudaMalloc 申请,无法保证新 Block 与其他 Block 地址连续,因此所有被维护在双向链表内的有连续地址空间的 Block 都是由一个最初申请来的 Block 拆分而来的。

一段连续空间内部(由双向链表组织的 Block 们)如下图所示:

730dbbe71ef2782eb3d9f9f8960f1292.png
连续空间内部的双向链表(链表箭头已省略)

当 Block 被释放时,会检查其 prev、next 指针是否为空,及若非空是否正在被使用。若没有在被使用,则会使用 try_merge_blocks (L1000) 合并相邻的 Block。由于每次释放 Block 都会检查,因此不会出现两个相邻的空闲块,于是只须检查相邻的块是否空闲即可。这一检查过程见 free_block 函数(L952)。又因为只有在释放某个 Block 时才有可能使多个空闲块地址连续,所以只需要在释放 Block 时整理碎片即可。

try_merge_blocks (L1000):https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L1000

free_block 函数:https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L952

关于阈值 max_split_size_mb ,直觉来说应该是大于某个阈值的 Block 比较大,适合拆分成稍小的几个 Block,但这里却设置为小于这一阈值的 Block 才进行拆分。个人理解是,PyTorch 认为,从统计上来说大部分内存申请都是小于某个阈值的,这些大小的 Block 按照常规处理,进行拆分与碎片管理;但对大于阈值的 Block 而言,PyTorch 认为这些大的 Block 申请时开销大(时间,失败风险),可以留待分配给下次较大的请求,于是不适合拆分。默认情况下阈值变量 max_split_size_mb 为 INT_MAX,即全部 Block 都可以拆分。

总结

剖析 PyTorch 显存管理机制可以帮助我们更好地寻找显存优化的思路。目前我们正在联合 Dynamic Tensor Rematerialization (DTR)(https://arxiv.org/abs/2006.09616) 的作者@圆角骑士魔理沙 推进 PyTorch 上 DTR 技术的工业界落地,其中一项就是解决上述文章中提到的训练过程内存碎片化问题。近期会有一篇文章介绍 DTR 落地遇到的问题 & 解决方案 & benchmark,敬请期待。

85c79e27d5491632bd4c167f17daaa66.png

outside_default.png

点个在看 paper不断!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文读懂 PyTorch 显存管理机制 的相关文章

  • 如何用Java写入OS系统日志?

    Mac OS 有一个名为 Console 的应用程序 其中包含记录的消息 错误和故障 我相信 Windows 中的等效项是事件查看器 我想 Linux 上也有一个 但我不知道它是什么 也不知道它在哪里 是否可以像这样从 Java 输出获取消
  • 获取jdbc中表依赖顺序

    我在 MySQL 数据库中有一组表 A B C D 依赖关系如下 B gt C gt A 和 D gt A 也就是说 A 有一个 PrimaryKey C 有一个外键指向 A 的主键 B 有一个外键指向 C 的主键 类似地 D 有一个外键指
  • 如何降低圈复杂度?

    我正在开发一个将 RequestDTO 发送到 Web 服务的类 我需要在发送请求之前验证该请求 请求可以从 3 个不同的地方发送 并且每个 请求类型 有不同的验证规则 例如请求1必须有姓名和电话号码 请求2必须有地址等 我有一个 DTO
  • 以点作为分隔符分割字符串

    我想知道我是否要在一个字符串上分割字符串 正确的方式 我的代码是 String fn filename split return fn 0 我只需要字符串的第一部分 这就是我返回第一项的原因 我问这个是因为我在 API 中注意到 意味着任何
  • WebLogic 10 中的临时目录

    每当 WL 停止时 它都不会删除其临时目录 即 domains mydomain servers myserver tmp WL TEMP APP DOWNLOADS domains mydomain servers myserver tm
  • JAX-WS:有状态 WS 在独立进程中失败

    我在 Tomcat 上部署了一个有状态的 Web 服务 它由工厂服务和主要 API 服务组成 并且工作得很好 工厂服务将 W3CEndpointReference 返回到主 API 实例 客户端使用会话 现在 我尝试将相同的服务作为独立应用
  • 为什么一个线程会中断另一个线程[重复]

    这个问题在这里已经有答案了 在Java多线程应用程序中 我们处理InterruptedThreadException 如果另一个线程中断当前线程 则会抛出此异常 现在 当另一个线程知道它将导致异常时 它可能想要中断当前线程的原因是什么 很多
  • 通过 JNI 从 Applet 调用 DLL

    我有一个 概念验证 的作品 它跨越了一些不熟悉的领域 我的任务是将 EFTPOS 机器连接到在内联网浏览器中作为小程序运行的应用程序 我暂时忽略了 EFTPOS dll 并用我选择的语言 Delphi 创建了一个简单的 JNI 修饰的 DL
  • Codility 钉板

    尝试了解 Codility NailingPlanks 的解决方案 问题链接 https app codility com programmers lessons 14 binary search algorithm nailing pla
  • 更改 JTextPane 的大小

    我是Java新手 刚刚在StackOverflow中找到了这段代码 ResizeTextArea https stackoverflow com questions 9370561 enabling scroll bars when jte
  • JavaFx 中装饰且不可移动的舞台

    我想在 JavaFx 中创建一个装饰舞台 它也将不可移动 我正在从另一个控制器类创建这个阶段 我能够创造和展示舞台 但它是自由移动的 我怎样才能创建这个 非常感谢帮助和建议 我把打开新关卡的方法贴出来 private void addRec
  • 中间件 API 的最佳实践是什么? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我们正在开发一个中间件 SDK 采用 C 和 Java 语言 供游戏开发人员 动画软件开发人员 阿凡达开
  • 如何将txt文件添加到你的android项目中? [复制]

    这个问题在这里已经有答案了 我的Android studio版本是1 5 1 显然这个 never 版本没有 txt 文件的 asset 文件夹 您打算如何将这些文件包含到您的项目中 以及如何进一步使用您内部的应用程序 谢谢你的建议 Pro
  • 猪参考

    我正在学习 Hadoop Pig 并且我总是坚持引用元素 请查找下面的示例 groupwordcount group chararray words bag of tokenTuples from line token chararray
  • NoSuchMethodError:将 Firebase 与应用程序引擎应用程序集成时

    我试图将 firebase 实时数据库与谷歌应用程序引擎应用程序集成 我在调用时收到此错误 gt DatabaseReference ref FirebaseDatabase gt getInstance gt getReference t
  • setKeyListener 将覆盖 setInputType 并更改键盘

    大家好 我在两个设备之间遇到问题 在实践中使用InputType和KeyListener我正在操纵一个EditText让它从数字键盘接收逗号和数字 有关更多背景信息 请检查我之前的question https stackoverflow c
  • 如何将任务添加到 gradle 中的主要“构建”任务

    当我尝试使用以下代码将任务添加到主构建任务时 rootProject tasks getByName build dependsOn mytask 当我跑步时它抱怨gradle w build输出 Where Build file line
  • 设计抽象类时是否应该考虑序列化问题?

    一般来说这个问题来自Eclipse建议在抽象类上添加串行版本UID 由于该类是抽象类 因此该类的实例永远不会存在 因此它们永远不会被序列化 只有派生类才会被序列化 所以我的问题是放置一个安全 SuppressWarnings serial
  • hive 从两个数组创建映射或键/值对

    我有两个具有相同数量值的数组 它们映射为 1 1 我需要从这两个数组创建一个键 值对或映射 键 值 任何想法或提示都会有帮助 当前表结构 USA WEST NUMBER Street City 135 Pacific Irvine USA
  • RecyclerView 不调用 onCreateViewHolder 或 onBindView

    没有收到任何错误 所有数据似乎都有效 由于某种原因 没有调用与视图相关的方法 我已确定以下事项 getItemCount 是唯一被调用的适配器方法 并且返回一个正整数值 我知道这将是你们将要查看的区域 构造函数正在被调用 成员变量有效 Pa

随机推荐

  • Few-shot learning(少样本学习,入门篇)

    本文介绍一篇来自 https www analyticsvidhya com 关于少样本学习的的博客 原文地址 文章目录 1 少样本学习 1 1 为什么要有少样本学习 什么是少样本学习 1 2 元学习和传统有监督学习的区别是什么 1 3 一
  • 几个特殊TCP报文及TCP

    TCP Window Full 接收方接收缓冲区满了后 导致发送方的发送缓冲区装满待确认数据 此时发送方会发送一个TCP Window Full消息 TCP ZeroWindow 接收方应用没有及时recv消息 导致接收缓冲满 即滑动窗口为
  • Java开发学习----AOP通知获取数据(参数、返回值、异常)

    前面的博客我们写AOP仅仅是在原始方法前后追加一些操作 接下来我们要说说AOP中数据相关的内容 我们将从 获取参数 获取返回值 和 获取异常 三个方面来研究切入点的相关信息 前面我们介绍通知类型的时候总共讲了五种 那么对于这五种类型都会有参
  • Farbic Java SDK 1.4安装方法

    Hyperledger Fabric Java SDK是开发基于Hyperledger Fabric区块链的Java应用之必备开发包 本文将介绍如何在Maven Gradle和Eclipse中安装使用Hyperledger Fabric J
  • 【计算机二级】Python类

    1 程序设计语言的发展经历了从机器语言 汇编语言 到高级语言的发展历程 2 程序设计语言是计算机能够理解和识别用户操作意图的一种交互体系 它按照特定规则组织计算机指令 使计算机能够自动进行各种运算处理 按照程序设计语言规则组织起来的一组计算
  • Apache Flink:特性、概念、组件栈、架构及原理分析

    Table of Contents 1 摘要 2 基本特性 3 流处理特性 4 API支持 5 Libraries支持 6 整合支持 7 基本概念 7 1 Stream Transformation Operator 7 2 Paralle
  • 蓝桥杯2015年第六届真题-广场舞

    说在前面 其他博客中的代码应该保证不了健壮性 我这个 应该 可以 题目 题目链接 题解 数学 计算几何 提示 这题默认好像是顺时针或逆时针输入坐标 也就是说先后输入的两个点一定是多边形的一条边 前置知识 PNPoly算法 何为PNPoly算
  • SHT3x-DIS驱动及应用详解(附源码和手册)

    文章目录 一 电路组成 二 通讯指令说明 一 单次获取数据指令 二 周期获取数据指令 1 配置模式 2 读取数据 三 加快响应时间指令 四 停止周期读取数据指令 五 复位 1 IIC接口复位 2 软复位 重新初始化 3 一般呼叫复位指令 4
  • 小米路由器4A,(R4A千兆版)刷openwrt系统(Linux的一种)

    下载系统 第一步下载自己路由器型号的系统固件 https openwrt org toh views toh fwdownload 直接在页面搜索自己路由器品牌找到区域再找到自己的型号对应的 然后选第一个链接是稳定版系统固件 下载后改名为o
  • 日语疑问句

    你好 的用法很简单就是主题主语提示词 难的是他与 的区别是什么 在这儿我列举一下 我们应该怎样区分什么时候用主格助词 什么时候用提示助词 呢 其实只要记住 的重点在于其前面部分 而 的重点在于其后面部分 为了更好掌握主格助词 和提示助词 的
  • 挂载分区提示can't read superblock on /dev/sda1

    环境为linux 某分区数据无法读取 1 fstab及磁盘列表正常 2 手动挂在时系统提示can t read superblock on dev xxx 故障现象如上图 解决办法 root下使用fsck 进行修复指定分区 fsck dev
  • 一起学SF框架系列7.4-spring-AOP-AOP代理创建

    AOP的BeanDefinition加载后 Spring提供了自动代理机制 让容器自动根据目标bean生成AOP代理bean 本文讲述具体如何实现 基本机制 Spring的启动过程中 在bean实例化前后 初始化前后均提供了外部介入处理机制
  • 【python】PCA计算权重

    python PCA计算权重 将分步骤基于python实现PCA计算权重 代码在pycharm中执行 文章目录 python PCA计算权重 1 引入库 2 读取数据 3 数据标准化 4 PCA 主成分分析 4 确定权重 5 对权重结果进行
  • C语言编译出现give arg types警告问题

    C语言编译时 void S34S set 括号参数类型未指明 出现give arg types警告 void S34S set void 不会出现give arg types警告 原因分析 C语言中 使用void来指示函数声明中不需要参数
  • 最佳实践:MySQL CDC 同步数据到 ES

    作者 于乐 腾讯 CSIG 工程师 一 方案描述 1 1 概述 在线教育是一种利用大数据 人工智能等新型互联网技术与传统教育行业相结合的新型教育方式 发展在线教育可以更好的构建网络化 数字化 个性化 终生化的教育体系 有利于构建 人人皆学
  • C++运算符重载

    一 运算符重载简介及意义 c 语言预定义的运算符只能操作基础数据类型 比如int double 对于用户自定义的类型 比如class中的私有成员变量类型无法进行运算操作 在平时进行运算时也需要类似的运算操作时 则需要进行运算符重载 实现自定
  • eclipse如何导入idea中的springboot项目以及如何删除项目

    eclipse中如何打开idea中的springboot项目 第一步 打开eclipse 第二步 打开项目 第三步 在eclipse中删除导入的idea项目 第一步 打开eclipse 首先打开eclipse 如下图 第二步 打开项目 选择
  • 2-1 编译型语言

    1 编译语言的层次和类型 机器语言 汇编语言 编译型语言 解释型语言 脚本语言 编译型语言一般需要经历编译和链接的过程 才能变成真正的可执行程序 编译链接的过程如下图所示
  • 求输入1~50的数字但是只有5次的机会

  • 一文读懂 PyTorch 显存管理机制

    点击上方 视学算法 选择加 星标 或 置顶 重磅干货 第一时间送达 作者丨米阿罗 知乎 已授权 来源丨https zhuanlan zhihu com p 486360176 编辑丨极市平台 首发于踢翻炼丹炉 https www zhihu