堆内存优化之动态栈分配

动态内存管理系统深度解析:从基础到优化! 🏆本文收录于「编程与技术实战」专栏,此专栏涵盖了C/C++编程、人工智能、数据结构、机器学习等技术领域的内容,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!! 阅读详情

前言

在堆上分配内存的操作开销很大,对性能影响会比较明显。

本文探索的就是对于堆内存优化的一种技术,动态栈分配(dynamic stack allocations),中心思想就是把在堆上申请内存的操作分配到栈上来完成。

主要参考文章在文末链接处附上了,一些翻译可能不太准确的术语可以直接看原文进行理解。

堆 & 栈

在探索动态栈分配之前,先简单描述一下基本知识 —— 堆和栈 的概念。

堆

堆内存在运行时创建,没有固定的地址或大小,一般需要由开发者自行申请分配和释放。

在 Linux 和 Posix 上,一般 malloc 使用 mmap 来提前申请一大块内存,之后再分配使用这块内存中的部分内存,减少 CPU 的负担。

栈

栈内存采用连续的内存地址来进行存放,在编译时就能知道它的大小,按函数调用来进行内存分配,属于临时内存,离开作用域即被释放。

由 SP (stack pointer,栈指针) 进行追踪栈内存,所以开发者不需要操心。

栈内存一般是在线程被创建出来的时候开辟的,通常有大小限制,1MB 左右,超出栈内存深度会引发异常。

动态栈分配 (dynamic stack allocations)

动态栈分配,中心思想就是把在堆上申请内存的操作分配到栈上来完成。

一些值得注意的点是:

  • 栈内存同作用域生命周期一致

  • 栈内存通常很小,默认大小为 1MB,Linux 上是 8MB

  • 在函数参数中无法使用,因为参数也在入栈

  • 一般分配超过一页内存(通常 4KB),需要引入 stack probing(栈探针) 来确保栈内存的正常增加

如何使用?

Visual C++ 使用函数 _malloca 来进行栈内存分配,需要传入申请内存的大小,需要与 _freea 配合使用。

Debug 模式下,会分配内存到堆上。

#include<alloca.h>

void allocStackDynamic(size_t size)
{
    assert(size > 0);
    char *mem = static_cast<char *>(_malloca(size));
    memset(mem, 0, size);
    _freea(mem);
}

操作系统是如何操作栈内存的?

这里将描述的是 Windows 平台上的操作。

在进程当中,会有一片虚拟内存是给线程作为栈内存使用的。

一开始,会只有保留的栈内存(reserves)存在,线程根据需要去申请使用(commits),这样避免了虚拟内存的页浪费。

  • 保留的栈内存(reserves)是进程中的虚拟内存,是还没被映射到物理内存的,因此还没有相应的物理页内存。

  • 申请使用的(commits、已分配的内存)是已经映射到物理内存的,有相应的物理页内存。

这两种内存的大小可以在 PE 文件头进行设置,或者通过开辟线程调用 CreateThread 去申请,通常新线程会有 1MB 的保留内存(reserves) 和初始申请的 4KB 的已分配内存(commits)。

为了知悉已分配的栈内存(commits)什么时候会增加,会在最后的已分配的栈内存的内存页会放入哨兵页(guard page)。

如果栈增长触碰到(touched)新的内存页,即哨兵页,会引发异常中断,然后申请使用更多的保留内存,哨兵页同时也挪到最后分配页的后面。

image.png

极端案例

哨兵页(guard page)大小也就 4 KB,如果我们申请的栈内存越过了 4KB,也即哨兵页没被触碰到(touched),会发生什么事?

void allocStackStatic()
{
    char mem[5000]; // stack grows downwards:
    mem[0] = 0;     // => &mem[0] < &mem[4999] < previous SP
}

一旦触碰到(touched)哨兵页后面的内存,那部分内存是未分配的,也即保留内存,这时候进程被终止,引发访问冲突(access violation)。

image.png

如何解决极端案例?

需要编译器去保证栈内存的正常增长,编译器引入了栈探针(stack probing)来对付超出 4KB 的栈内存申请。

由于栈内存是静态分配的,所以编译器是可以检测到这种超大的栈内存分配的,对于这种调用栈(stack frame),编译器会生成栈探针(stack probing)指令,实际是调用 _chkstk 函数。

当运用的是动态栈分配(dynamic stack allocations)时,调用的是 _alloca_probe16 函数。

题外:栈内存发生缺页中断会怎样?

当使用 push 指令(入栈操作),如果发生了缺页中断,那么返回地址要保存在哪里呢?

在 x86 架构下,TSS(task state segment,任务状态段) 保存有不同权限(privilege level)下的各自的栈指针(stack pointer),所以当发生用户态和内核态的切换时,系统会根据不同的权限(privilege level)来进入到不同的调用栈当中。

参考链接

https://geidav.wordpress.com/tag/stack-probing/

https://stackoverflow.com/questions/25222967/what-happens-when-a-page-fault-occurs-in-stack

Ubuntu 部署 Ollama 避坑指南:下载失败 / 服务崩溃?一招解决所有问题! 本文详细介绍了在Ubuntu系统上安装Ollama大模型工具的完整流程和常见问题解决方案。主要内容包括:环境检查(系统版本、架构、依赖)、一键安装步骤、端口配置和服务启动等核心操作。重点针对下载失败、服务启动异常、权限不足等高频问题,提供了国内镜像配置、手动创建服务文件、用户组添加等实用解决方案。最后通过测试Qwen2.5轻量模型验证安装效果,并给出低配设备选型建议。全文提供可复制的命令行操作,帮助用户快速搭建离线可用的大模型环境。 阅读详情

相关推荐

告别AT串口线!用STM32F407的USB FS口直连EC800M进行物联网通信

本文介绍了一种创新的物联网通信方案,利用STM32F407的USB FS接口直连EC800M模块,替代传统的AT串口线连接。通过详细的硬件对比、STM32CubeMX配置和USB驱动调试步骤,展示了如何实现更高效的AT指令通信,同时降低硬件复杂度和功耗。

weixin_29189987的博客 706

栈的动态分配以及栈由谁管理的问题

我们都知道C语言提供了malloc等函数让程序员手动来分配释放堆空间,而栈空间一般是自动分配与释放的。 这里我要补充两点: 1.栈也是能动态分配的,C语言提供了alloca函数让程序员动态分配栈内存,且不需要手动释放,对没错,你来分配,主调函数返回时由编译器(生成的类析构程序)来给你擦屁股。但alloca不具可移植性, 而且在没有传统堆栈的机器上很难实现。当它的返回值直接传入另一个函数时会带来问题,因为他分配在栈上. 由于这些原因,alloca不宜使用在必须广泛移植的程序中, 一般使用的很少,不管它.

FishSuperBear博客 2714

单卡调试OpenGait:从分布式到本地的轻量级改造指南

本文为个人开发者和研究者提供了一份详细的OpenGait框架单卡调试指南。针对该框架默认依赖多GPU分布式训练的问题,文章通过分析核心痛点,逐步讲解了如何从环境搭建、绕过分布式初始化陷阱、处理通信后端报错到最终配置与调试,实现从分布式到本地的轻量级改造,帮助读者在单张GPU上高效进行步态识别模型的验证与学习。

weixin_28223453的博客 139

栈的动态分配alloca

C提供了在栈中动态分配内存的函数alloca,用法和malloc一样,但不用free,因为他是在栈中分配空间,超出定义域后自动释放: int lenght = 10; char* str = (char*)alloca(length * sizeof(char));

Roxanne0521的博客 455

Kaggle时间序列竞赛金牌方案解析与实战指南

本文深度解析了Kaggle时间序列竞赛的金牌方案,揭示了其成功不仅依赖先进模型,更在于系统工程思维。文章系统阐述了从数据理解、特征工程到模型集成与后处理的全流程,并重点剖析了分层建模、自定义损失函数、时序交叉验证等核心技巧,为参赛者提供了从理论到实战的完整指南。

weixin_29196315的博客 356

仓储控制系统(WCS)设计

WCS系统设计方法

cetclyb的博客 2万+

C语言进阶复习(四)--动态内存管理(c重点)

文章目录为什么存在动态内存分配动态内存函数的介绍mallocfreecallocrelloc常见的动态内存错误示范C/C++程序的内存区域划分 为什么存在动态内存分配 我们已经掌握一般静态内存开辟方式: 只要是变量 都会给开辟空间,说白了,变量其实就是地址的名字 int val = 20;//在栈空间上开辟四个字节 char arr[10] = {0};//在栈空间上开辟10个字节的连续空间 但...

e我所欲也的博客 1652

拯救者R7000用户实测:用华为电脑管家11.0版搞定MatePad Pro多屏协同(附驱动与显卡避坑指南)

本文详细介绍了如何在非华为笔记本联想拯救者R7000上安装华为电脑管家11.0版,实现与MatePad Pro的多屏协同功能。从驱动优化到显卡配置,提供了全面的避坑指南和实用技巧,帮助用户解决连接失败等常见问题,提升跨设备生产力体验。

weixin_30788619的博客 398

栈和堆都能动态或静态分配内存空间吗?

size为待申请栈的大小,void*返回申请空间的地址。失败时(size 过大),可能会存在栈溢出问题(可能会指向堆区或者其它区域)堆就不多说了,只能通过malloc、new等方式动态申请分配内存空间。下面来聊聊栈的两者申请方式。但alloca不宜使用在必须广泛移植的程序中,有些机器不一定具有传统意义上的"栈/堆"。最典型的就是:在一个函数中创建了一个数组,这就是一种栈的静态分配。堆只能通过动态申请分配内存空间,而栈两者方式都可以!的函数,和malloc类似,不同在于。在Linux,提供了一个名为。

Zhenyu_Coder的博客 931

AutoSAR系列讲解(入门篇)4.6-BSW的Watchdog功能

BSW的看门狗功能

雪云飞星的博客 1万+

【RT-DETR多模态融合改进】| 自研 多模态大核选择模块:MM_LSK,适配多模态图像信息融合

本文记录的利用改进的多模态大核选择模块(MM-LSK)优化RT-DETR,构建可见光-红外双模态目标检测网络。在遥感等大尺寸图像的可见光-红外双模态小目标检测任务中,单模态检测易受光照、云雾、背景干扰等因素影响,且小目标外观信息匮乏,无法仅基于单模态外观实现较好的识别,因此需要融合双模态互补特性与广泛的跨模态上下文信息进行辅助。

Limiiiing的博客 382

C++规范编码引涉语法点之(7)静态分配和动态分配/堆和栈

【1】内存的静态分配和动态分配的区别主要是两个: 一是时间不同。静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。 二是空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由函数malloc进行分配。不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现。 【2】对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。 ...

qfturauyls的博客 507

小论文/大论文必备 | YOLO多模态目标检测、绘制曲线对比图 | 引入多种绘制曲线对比图,包括mAP0.5,mAP0.5:0.95,Loss损失变化的曲线对比

本文通过可视化方法分析YOLO多模态模型的训练过程,对比不同实验的检测精度和损失函数变化。主要内容包括:1)绘制mAP@0.5和mAP@0.5:0.95曲线评估检测精度;2)分析边界框回归损失(box_loss)、分类损失(cls_loss)和分布式损失(dfl_loss)的收敛趋势;3)提供完整的Python代码实现,支持多实验对比,可生成高清曲线图。该方法能直观展示模型训练效果,便于优化策略评估和参数调优。

Ai缝合怪、助力小伙伴高效发论文 261

【编程底层思考】什么是逃逸分析,基于逃逸分析可以做哪些优化(分离对象或标量替换\栈上分配\同步锁消除)

标量替换是逃逸分析的一种优化手段,它将不会逃逸的对象拆解成更小的单元(标量),例如将一个对象的字段拆分成独立的变量。如果分析发现对象不会逃逸出方法的作用域,即不会被外部方法访问,那么这个对象可能会在栈上分配,或者进行标量替换。这两种优化都可以减少堆内存的使用,从而减少垃圾回收的压力。栈上分配的对象在方法执行完毕后,随着栈帧的销毁而自动被回收,这样可以减少垃圾回收的频率和内存的占用。总的来说,逃逸分析是JVM中一个强大的优化技术,它通过减少对象在堆上的分配和垃圾回收,来提高Java程序的性能和内存利用效率。

Dylaniou的博客 641

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

golang 堆内存栈内存管理

堆内存管理堆内存分类全局Span管理 mheap ,mheap.central本地span管理-p.mcachearena元数据--heaparena堆内存分配1 辅助gc2 空间分配3 位图标记4 收尾工作栈栈分配栈增长栈收缩结束运行的协程栈如何处理 堆内存管理 堆内存分类 1 堆内存分为64M大小的arena,64*1024=65536 65536/8=8192 即每个arene分成8192个page。每个page为8kb go在管理内存是,把内存划分成大小不同的67钟规格,最小8字节 最大3.

weixin_41479678的博客 1790

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

堆内存与栈内存的核心区别是什么?

特别是在高性能、多线程应用中,我们需要深入理解堆内存与栈内存的核心区别,以便有效地管理和优化内存使用。3. 性能影响:由于堆内存是动态分配的,其分配和释放操作对程序的性能有着显著的影响。而栈内存虽然也是重要的,但其主要影响在于函数的执行效率和程序的执行顺序。而栈内存的分配则更多地依赖于编程语言的特性,如C/C++中的局部变量和函数参数的存储方式。由于它存储的数据是在函数调用过程中使用的,所以栈内存的分配和释放对程序的性能有着显著的影响。堆内存是动态分配的,它的大小和行为由操作系统和程序的具体需求来决定。

qq_35974860的博客 80

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Julia语言的堆

Julia是一种高级编程语言,旨在成为科学计算的最佳选择。它结合了动态类型、快速执行和强大的数值计算能力,非常适合处理大规模数据。Julia的内存管理策略与其他现代编程语言(如Python和C++)有许多相似之处。a::Int64b::Float64end堆作为一种灵活的内存管理结构,在Julia语言中起着至关重要的作用。通过动态内存分配和自动垃圾回收,开发者能够更加专注于编写高效的代码,而不必过多担心内存管理问题。掌握堆内存的使用技巧和优化策略,对于提高程序性能和资源利用率是相当重要的。

2501_91226610的博客 733

堆是分配对象的唯一选择么?

在《深入理解 Java 虚拟机》中关于 Java 堆内存有这样一段描述:随着 JIT 编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。在 Java 虚拟机中,对象是在 Java 堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析( Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内。

joker19990512的博客 401

为什么栈比堆快

在计算机内存中,堆(Heap)和栈(Stack)是两种重要的内存分配区域,它们有不同的特性、用途和性能差异。

qq_41328432的博客 671

linux优化空间&完全卸载mysql——centos7.9

大家好,我是yma16,linux优化空间&完全卸载mysql——centos7.9。linux内存分配在Linux中,内存分配是通过几种机制来实现的。下面是一些常用的内存分配机制:静态内存分配:静态内存分配是指在程序编译期间就确定了内存的分配大小和位置。这种分配方式适用于全局变量和静态变量。静态内存分配是通过编译器在可执行文件的数据段或者BSS段中进行的。动态内存分配:动态内存分配是指在程序运行期间根据需要来动态分配和释放内存。动态内存分配是通过系统库函数(如malloc、free等)来实现的。

编程知识分享,主打前端 2万+

从内存管理视角:FreeRTOS任务栈与堆的深度剖析与优化实践

本文从内存管理视角深度剖析FreeRTOS任务栈与堆的优化实践,结合韦东山等专家经验,详解任务栈配置策略、堆内存碎片防治及动态分配最佳实践。通过实战案例展示如何有效提升嵌入式系统稳定性和性能,为开发者提供经过验证的内存优化方案。

ujm5678901的博客 301

JVM 实战 - JVM之运行时数据区 - 栈顶优化

通常情况下,对象是在堆内存中分配的,但某些特定情况下,尤其是对于生命周期很短的对象,JVM可以采用栈顶优化策略,将这些对象直接分配到线程栈上而不是堆中。此外,现代JVM中的其他优化技术,如G1垃圾收集器等,也会考虑减少短生命周期对象在堆上的分配,以提高整体系统性能。对象分配在线程栈上意味着它们不需要额外的空间管理(如垃圾回收),当方法执行结束时,栈帧自然会被弹出,所分配的对象也随之被销毁,无需GC介入。由于对象位于栈上,对它的访问速度理论上比堆上的对象更快,因为栈是线程私有的,没有多线程竞争问题。

qq_33240556的博客 303

嵌入式C/C++:栈与堆内存管理

今天介绍下栈和堆,栈和堆是嵌入式C++技术中两种核心的内存分配方式。

CodeAllen嵌入式 588

C语言内存管理与优化策略

内存优化是C语言编程中的重要环节,需要深入理解内存管理和相关的优化策略。通过合理选择数据类型、优化结构体设计、使用位段、避免冗余内存分配、合理管理动态内存、优化数据结构和算法、利用编译器优化选项、缓存优化以及内存池技术等方法,我们可以编写出更加高效、稳定的C语言程序。希望本文的介绍能为读者在C语言内存管理与优化方面提供有益的参考。本文将科普C语言内存管理的基本概念,并介绍一系列内存优化策略,帮助读者编写更加高效、稳定的C语言程序。C语言的内存管理机制主要包括静态内存分配、栈内存分配和堆内存分配三种方式。

SASfafaah的博客 1128

Java的堆内存和栈内存

Java堆和栈

qq_36412089的博客 4338

C++栈与堆内存对比

C++内存分为栈和堆两种主要区域。栈自动管理、快速分配;堆灵活但需要手动管理。理解两者的区别对编写高效代码至关重要。栈内存自动管理,分配速度极快。C++栈与堆内存对比。

2501_94114815的博客 14
上一篇: 《python3 网络爬虫开发实践》笔记
下一篇: c++ vtable 深入解析
流左沙
博客等级 码龄11年 46粉丝 55原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值