如何配置X86应用程序启用大地址模式(将用户态虚拟内存从2GB扩充到3GB),以解决用户态虚拟内存不够用问题?(项目实战案例解析)

目录

1、概述

2、为什么不直接将程序做成64位的?

3、进程内存不足导致程序发生闪退的案例分析

3.1、问题说明

3.2、将Windbg附加到程序进程上进行动态调试

3.3、动态调试的Windbg感知到了中断,中断在DebugBreak函数调用上

3.4、malloc或new失败的可能原因分析

3.5、为什么没能生成dump文件?

3.6、本例中malloc返回NULL的原因分析

3.7、为啥有的机器不出现,只在个别电脑上出现?

4、程序用户态虚拟内存占用高导致不够用的解决办法

4.1、修改WebRTC编译选项,减少内存占用

4.2、将程序做成64位的

4.3、使用多进程模式

4.4、使用Visual Studio的链接选项,将用户态虚拟内存从2GB扩充到3GB(最终选择的这个方法)

5、最后


C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       对于32位程序,默认情况下其用户态虚拟内存只有2GB,可能会出现内存不够用的情况,继而出现后续内存申请失败,导致软件出现异常。本文结合项目中出现的一个具体问题实例,详细讲述问题的排查定位的过程,并详细讨论了解决用户态虚拟内存不够用的手段与策略,最后讲述如何配置X86应用程序启用大地址模式(将用户态虚拟内存从2GB扩充到3GB)去解决内存不够用的问题。

1、概述

        对于32位程序,系统给程序进程分配4GB的虚拟内存,默认情况下,用户态虚拟内存占2GB,内核态虚拟内存占2GB。对于应用程序,业务代码基本都是运行在用户态中的,占用的是用户态的虚拟内存。可能会因为程序模块多占用的内存过大,也可能是程序中存在内存泄漏,导致程序进程占用的用户态虚拟内存达到或者接近2GB的上限,导致后续内存申请失败,或者产生Out of memory内存耗尽的异常。

        如果是内存泄漏导致的,则要排查泄漏的原因,解决泄漏问题。

        如果是程序业务模块过多,占用了大量的内存,使程序占用的用户态虚拟内存接近2GB(快达到2GB的上限),导致后续内存申请失败,则需要对程序占用的内存进行优化,减少程序对虚拟内存的占用。如果内存优化空间有限,仍然无法解决问题,则可以将程序的用户态虚拟内存从2GB扩充到3GB,将问题规避掉。

对于32程序,总的虚拟内存是4GB,默认情况下,用户态虚拟内存占2GB,内核态虚拟内存占2GB。如果将用户态虚拟内存由2GB扩充到3GB,则内核态虚拟内存会从2GB较少到1GB,即内核态虚拟内存就变小了,对运行在内核态的模块的执行效率会带来一定的影响,虽然这种影响不大。另外,尽量对虚拟内存进行优化,如果程序占用的虚拟内存较大,要频繁地在虚拟内存与物理内存之间切换,也会对程序的执行效率产生影响。

2、为什么不直接将程序做成64位的?

       64位程序的虚拟内存到大的多,既然32位程序的虚拟内存有限,为什么不做成64位的呢?32位程序可以在32位操作系统中运行,也可以在64位操作系统中运行(64位系统兼容32位程序)。但64位程序只能在64系统中运行,无法在32位系统运行。

        为了同时支持32位和64位操作系统,将程序都做成32位的,当然,有些软件做成了32位和64位两个版本,可以根据操作系统的位数,选择安装对应位数的程序。

       如果要将程序做成64位的,则程序从上到下的所有模块都要编译成64位的,因为32位模块和64位模块是不能混在一起使用的,如果强行混在一起使用,程序会报错的。

       以64位Windows系统为例,64位系统是如何保证32位程序与64位都能正常运行的呢?程序会依赖很多系统dll库,而32位程序只能依赖使用32位的dll库,64位程序也只能依赖使用64位的dll库。系统为了同时支持32位与64位程序的运行,提供了32位版本和64位版本的系统dll库:

1)C:\Windows\System32:64位系统dll库目录。

2)C:\Windows\SysWOW64:32位系统dll库目录。

系统在启动程序时会根据程序的位数,去选择加载对应位数的系统dll库。

       方便大家理解和记忆,此处说一下C:\Windows\SysWOW64路径中的WOW64的含义,MSDN上对WOW64的解释如下:

WOW64 is the x86 emulator that allows Win32-based applications to run on 64-bit Windows. It is intended to run 32-bit personal productivity applications needed by software developers and administrators. It is not intended to run 32-bit server applications. 

WOW64的大致含义是,W-Win32,O-On,W64-Win64(64-bit Windows),32位程序运行在64系统上。

3、进程内存不足导致程序发生闪退的案例分析

3.1、问题说明

       之前有客户反馈,我们的客户端软件在他们某台华为MATE笔记本电脑上运行时,会时不时地出现闪退问题(问题不是必现的)。程序闪退时并没有弹出崩溃的提示框(如果程序的异常捕获模块感知到程序发生了崩溃,会弹出一个崩溃提示框),说明程序中安装的异常捕获模块并没有感知到异常,所以没有生成dump文件,所以也就没法使用Windbg静态分析dump文件的方式去分析这个问题。

程序中安装的异常捕获模块,大概只能捕获到90%左右场景的异常,有少部分异常是捕捉不到的。对于异常捕获不到的场景,则需要使用其他方法排查分析,比如使用Windbg进行动态调试。

3.2、将Windbg附加到程序进程上进行动态调试

       既然异常捕获模块没有感知到,我们只能将Windbg附加到程序进程上进行动态调试,即将Windbg附加到程序进程上,和程序一起跑,如果程序发生异常,Windbg会第一时间感知到并中断下来,这个时候我们就趁这个中断的机会,去查看函数调用堆栈去分析。

       但这个问题不是必现的,只能让客户每次启动程序时,都手动将Windbg附加到目标进程上,让Windbg跟着程序一起跑。一旦问题复现,Windbg就会感知到并中断下来。


        在这里,给大家重点推荐一下我的几个热门畅销专栏(博客主页还有其他专栏,可以去查看)

专栏1:(该精品技术专栏的订阅量已达到430多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)

C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战经验为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对C++相关知识点进行详细地展开与剖析!专栏涉及了C/C++开发领域多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏3: 

VC++常用功能开发汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585

专栏将10多年C++开发实践中常用的功能,以高质量的代码展现出来,并对相关功能的实现细节进行了详细的说明。这些常用的代码,其质量与稳定性是有保证的,可以直接拿过去使用,可以有效地解决C++软件开发过程中遇到的问题。


3.3、动态调试的Windbg感知到了中断,中断在DebugBreak函数调用上

       后来同事每次运行程序时都将Windbg附加到程序进程上,复现了问题,正在调试的Windbg中断了下来,发现中断在DebugBreak接口调用处,如下所示:

输入kn命令查看此时的函数调用堆栈: 

正是DebugBreak接口就是让正在调试的进程中断下来的。DebugBreak是Windows API函数,从函数名称上也能看出来,该函数就是让正在调试的调试器中断下来,此时的中断确实是调用了DebugBreak引起的中断。

       于是顺着函数调用堆栈向上看,问题是出在WebRTC开源库中的,然后根据函数调用堆栈中的函数,找到对应的C++源码,看到是代码中调用malloc申请动态内存时,申请内存失败,返回空指针NULL,然后引发DebugBreak的调用,具体流程可以对照下列代码看:

1)申请内存的malloc返回NULL:

2)malloc返回NULL,会执行到RTC_CHECK宏中的rtc_FatalMessage接口: 

3)rtc_FatalMessage接口中紧接着调用到FatalLog接口: 

4) FatalLog接口中调用了DebugBreak接口

       此外,我们在实际调试时发现,使用g命令将DebugBreak函数调用引发的中断跳过去,Windbg还会产生一次中断,是因为调用abort系统函数,abort函数的内部实现代码如下:

/***
*void abort() - abort the current program by raising SIGABRT
*
*Purpose:
*   print out an abort message and raise the SIGABRT signal.  If the user
*   hasn't defined an abort handler routine, terminate the program
*   with exit status of 3 without cleaning up.
*
*   Multi-thread version does not raise SIGABRT -- this isn't supported
*   under multi-thread.
*******************************************************************************/
void __cdecl abort (
        void
        )
{
    _PHNDLR sigabrt_act = SIG_DFL;
 
#ifdef _DEBUG
    if (__abort_behavior & _WRITE_ABORT_MSG)
    {
        /* write the abort message */
        _NMSG_WRITE(_RT_ABORT);
    }
#endif  /* _DEBUG */
 
 
    /* Check if the user installed a handler for SIGABRT.
     * We need to read the user handler atomically in the case
     * another thread is aborting while we change the signal
     * handler.
     */
    sigabrt_act = __get_sigabrt();
    if (sigabrt_act != SIG_DFL)
    {
        raise(SIGABRT);
    }
 
    /* If there is no user handler for SIGABRT or if the user
     * handler returns, then exit from the program anyway
     */
    if (__abort_behavior & _CALL_REPORTFAULT)
    {
        _call_reportfault(_CRT_DEBUGGER_ABORT, STATUS_FATAL_APP_EXIT, EXCEPTION_NONCONTINUABLE);
    }
 
    /* If we don't want to call ReportFault, then we call _exit(3), which is the
     * same as invoking the default handler for SIGABRT
     */
    _exit(3);
}

       abort函数内部是通过调用exit系统函数将当前进程强制退出的,但在退出之前会调用raise(SIGABRT),该函数触发一个SIGABRT信号终止异常,如果当前正在调试状态,会让调试器中断下来。

3.4、malloc或new失败的可能原因分析

       如果malloc申请内存失败,则会返回NULL;如果new申请内存失败,默认会抛出bad_alloc异常。  那为啥会出现malloc或new操作失败的问题呢?之前我们总结过,一般malloc或new失败可能是以下几种原因导致的:

1)申请的内存过大,进程中没有这么大内存可用了
可能受一些异常数据的影响,申请了很大尺寸的内存。比如前段时间排查一个崩溃问题,当时因为数据有异常,一次性申请了9999*9999*4*2 = 762MB的堆内存,进程中没有这么大可用的堆内存了,所以申请失败了。
2)用户态的内存已经达到了上限,申请不到内存了       
有可能是虚拟内存占用太多,也有可能代码中有内存泄露,导致用户态的虚拟内存被消耗完了。对于一个32程序,一个进程分配了4GB的虚拟地址空间,而用户态和内核态内存各占一半,即用户态的虚拟内存只有2GB,如果程序占用的虚拟内存比较大,比如接近2GB的用户虚拟内存了,再申请大的内存就会申请失败了。或者程序中有内存泄露,快要把用户态的2GB的虚拟内存给占用完了,再申请内存可能会申请失败的。
3)进程中的内存碎片过多    
如果进程中在大量的new和delete,产生了大量的小块内存碎片,可用的内存被切割成一小块一小块的小内存块,如果要申请一块长度很长的内存,因为到处是内存碎片,没有这么一大块连续的可用内存,可能会导致内存申请失败的。
4)发生堆内存越界
堆内存被破坏,导致new操作产生异常(此时new不会返回NULL,会抛出异常)。我们可以在出问题的地方,对该处的new添加一个保护(但不可能对代码中所有new的地方都加这样的保护),我们通过添加try...catch去捕获new抛出的异常,并将异常码打印出来,如下所示:(下面的代码在循环申请内存,直到内存申请失败为止,主要用来测试用)

​#include <iostream>
using namespace std;
 
int main(){
    char *p;
    int i = 0;
    try
    {
        do{
            p = new char[10*1024*1024];
            i++;
            
            Sleep(5);
        }
        while(p);
    }
    catch(const std::exception& e)
    {
        std::cout << e.what() << "\n"
                    << "分配了" << i*10 << "M" << std::endl;
 
    }
    
    return 0;   
}

3.5、为什么没能生成dump文件?

       现在我们再回过头去看看,程序发生闪退时为什么没有生成dump文件。 上面已经分析出程序闪退的原因了,是因为WebRTC开源库中调用malloc申请内存失败(因为进程的用户态虚拟内部不够用了,没有足够空闲的内存可供分配了)返回空指针NULL,WebRTC认为申请内存失败了,业务没法正常跑下去了,认为是致命的,然后最终调用abort系统函数强行将当前进程终止的。

       程序中只是调用malloc申请内存失败,然后调用abort强行终止进程,并没有产生C++异常或崩溃。并没有产生C++异常或崩溃,异常捕获模块是感知不到的,所以没生成dump文件的,这和运行时实际表现出来的现象是完全吻合的!

       如果代码中不是调用malloc去动态申请内存,而是使用new去申请,则在申请不到内存时new内部会抛出bad_alloc异常,这个会导致程序崩溃的,异常捕获模块应该能感知到,会生成dump文件。

3.6、本例中malloc返回NULL的原因分析

       在本例中排除了内存泄漏的可能,推测是程序占用的虚拟内存过多,接近程序用户态虚拟内存2GB的上限,导致后续申请内存时没有足够的内存可供分配了,所以申请内存失败!

       我们软件之前的版本,没有使用WebRTC开源库,一直没有这个问题的。这个问题是在引入WebRTC开源库后才出现的,可能和引入的WebRTC有关系的。WebRTC开源库内部功能庞大,内部包含了大量的业务和逻辑,会占用大量的内存,按讲是不适合用在32位程序中,因为32位程序的用户态虚拟内存默认只有2GB,很有可能会出现用户态虚拟内存不够用的情况。

3.7、为啥有的机器不出现,只在个别电脑上出现?

       为啥这个问题有的机器不出现,只在个别电脑上出现呢?可能和机器的硬件配置及操作系统版本有关系。不同版本的操作系统的内存管理机制可能是有差异的。WebRTC开源库内部会根据机器的配置及网络带宽,去动态地调整音视频编码的分辨率等参数,会消耗不同大小的内存。

4、程序用户态虚拟内存占用高导致不够用的解决办法

        WebRTC开源库比较大,会消耗很多的内存,如何解决WebRTC占用大量虚拟内存的问题,有如下的方法。

4.1、修改WebRTC编译选项,减少内存占用

        可以尝试修改WebRTC编译选项,对其进行裁剪缩编,释放出一些占用内存的代码,但这种做法降低内存的效果有限,因为WebRTC作为大型库本来就需要占用大量的内存资源。

WebRTC库的源码就有10多个GB,是个非常庞大的开源库,内部包含了大量的业务逻辑和功能,需要占用大量的内存!有些内存在库初始化的时候就申请了,即很多内存一上来就占用上了,而不是需要使用时再去申请。

4.2、将程序做成64位的

       要将程序做成64位的,底层的模块都要编译成64位的,32位模块与64位模块是不同混在一起使用的。如果强行混在一起,则运行会报错的。程序底层包含了上百个模块,都要将代码移植到64位上,可能会遇到这样那样的问题,短时间内完成迁移,会产生很多bug的。

        再就是我们的程序要兼容32位操作系统,目前只能做成32位的,没有人力去分别制作32位版本和64位版本。

即便可以将主程序做成64位的,64位程序的用户态虚拟内存非常大,可以“肆无忌惮”的使用。但占用的虚拟内存过大,在代码执行过程中虚拟内存要切换到物理内存上,会来回在虚拟内存与物理内存之间频繁地切换,也会影响程序的执行效率。此外,物理内存较小,也会影响虚拟内存到物理内存的切换,也会显著降低程序的运行速度。

4.3、使用多进程模式

        但上述方法,在使用WebRTC开源库时可能有问题,如果要解码更多路数的视频,会占用更多的内存。可以考虑将WebRTC封装成进程,使用多进程的模式,主进程与WebRTC进程使用RPC方式进行接口的调用。像Chrome那样,搞多个进程,不同的进程处理不同的事务,可以将程序占用的内存分摊到不同的进程上。并且一个进程崩溃了,也不会影响到主进程,将崩溃的进程重新启动起来就好。

       但多个进程之间需要通信,需要协同控制,控制不好也容易出问题。进程之间如何高效地的传递数据也是个问题,这都需要人力和技术去支撑。但多进程模式是比较稳妥的解决方案之一。

4.4、使用Visual Studio的链接选项,将用户态虚拟内存从2GB扩充到3GB(最终选择这个方法)

       可以在Visual Studio链接选项中打开扩大用户态虚拟内存的选项/L largeAddressAware,如下所示:

这样可以将用户态虚拟内存扩到3GB,这样可以有效缓解内存不够用的问题。

       32位进程只有4GB的虚拟内存,如果将用户态虚拟内存由2GB扩到3GB,内核态的虚拟内存应该会被压缩到1GB,这样会不会导致内核态的代码执行比较慢,导致程序的运行性能下降呢?可能运行性能会有一定的损失,但既然系统运行这种扩充用户态虚拟内存的方式,应该影响不会很大。这个方法简单快速,也不会引入新的bug,短期内最合适,所以最后选的是这个方法!

5、最后

       在Visual Studio中修改链接选项,可以直接将用户态虚拟内存从2GB扩充到3GB,可以有效的规避内存不够用的问题。这种方法简单方便,可以快速地解决问题。但最根本的还是要对内存进行优化,尽量减少对内存的占用,也能提高程序的执行效率。当然优化代码的过程中,也可能会引入这样那样的bug,需要根据时间和工作量评估可行性。

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

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

相关文章

企业微信主体能不能修改?

企业微信变更主体有什么作用&#xff1f;当我们的企业因为各种原因需要注销或已经注销&#xff0c;或者运营变更等情况&#xff0c;企业微信无法继续使用原主体继续使用时&#xff0c;可以申请企业主体变更&#xff0c;变更为新的主体。企业微信变更主体的条件有哪些&#xff1…

Java 三大特性之继承

目录 一、为什么需要继承&#xff1f; 二、继承概念 三、继承的语法 四、子类访问父类成员 五、super关键字 六、继承关系下的构造方法 七、继承关系下的初始化 八、protected关键字 九、继承的三种方式 十、final关键字 十一、继承和组合 一、为什么需要继承&#…

五一玩狗“丧志”记

我天生喜欢狗狗。五一来到媳妇老家这几天&#xff0c;只要有机会我都要给一个只叫“瘦瘦”的狗狗多攒一些吃的。 它是一条看家狗&#xff0c;有大人膝盖那么高八十厘米那么长。通体毛色以黄黑为主&#xff0c;少许白色主要集中在爪子和下巴。两耳直挺挺尖尖的竖立着&#xff0c…

mac通过termius连接Linux服务器

mac上安装 linux系统 如果有 linux服务器账号密码&#xff0c;那么上一步可忽略&#xff1b; 比如&#xff1a;直接连接阿里云或腾讯云账号 1. 安装termius 链接: https://pan.baidu.com/s/1iYsZPZThPizxqtkLPT89-Q?pwdbw6j 提取码: bw6j 官网 Termius - SSH platform for …

CNN实现fashion_mnist数据集分类(tensorflow)

1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、加载fashion_mnist数据与预处理 import numpy as np (train_images,train_labels),(test_images,test_labels) tf.keras.d…

[数据结构]————排序总结——插入排序(直接排序和希尔排序)—选择排序(选择排序和堆排序)-交换排序(冒泡排序和快速排序)—归并排序(归并排序)

文章涉及具体代码gitee&#xff1a; 登录 - Gitee.com 目录 1.插入排序 1.直接插入排序 总结 2.希尔排序 总结 2.选择排序 1.选择排序 ​编辑 总结 2.堆排序 总结 3.交换排序 1.冒泡排序 总结 2.快速排序 总结 4.归并排序 总结 5.总的分析总结 1.插入排…

抖音小风车一键跳转企业微信如何实现

我们在做抖音直播时&#xff0c;都喜欢挂上小风车去做转化&#xff0c;有的直播间小风车可以直接跳转到微信&#xff0c;这是怎么做到的呢&#xff1f;现在把这个经验给大家分享下&#xff1a; 首先我们需要先理解抖音直播间小风车是什么&#xff1f; 抖音小风车实际是一张直播…

c语言:打印任意行数的菱形

例如&#xff1a;以下图片形式 #include <stdio.h> int main() {int line 0;scanf_s("%d", &line);int i 0;//打印上半部分for (i 0; i < line; i){//打印空格数int j 0;for (j 0; j < line - 1 - i; j){printf(" ");}//打印*数量for…

内核中常用宏定义| container_of

文章目录 前言container_of函数介绍container_of函数实现container_of函数解析offsetof的使用container_of的使用结语 前言 前两篇我们写到内核中两种C语言高级语法__attribute__, __read_mostly。本篇写内核中另外一种常用宏定义之container_of container_of函数介绍 conta…

高级事件.

高级事件 1. 注册事件&#xff08;addEventListener)2.删除事件(removeEventListener&#xff09;3.DOM事件流4.事件对象及其方法&#xff08;当形参来看&#xff09;5.阻止默认事件/冒泡6.事件委托7.鼠标事件&#xff08;禁止右键/选中文字)8.鼠标事件对象8.常用键盘事件9.键盘…

【C++】模板初阶:泛型编程的起点

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

大模型下的Agent、AIGC的商业案例集合

算是一份摘录 1 AIGC 对影楼的影响 https://mp.weixin.qq.com/s/3j-6FAxZEEvXUZ1q6by2uw 2 出海Talkie &#xff1a;情感智能体 https://mp.weixin.qq.com/s/KHPmfuVvywxxcI2rqoOghA Talkie 为每条消息提供 3 个免费灵感&#xff0c;如果用户需要更多 AI 生成的灵感选项&…

Delta lake with Java--在spark集群上运行程序

昨天写了第一篇入门&#xff0c;今天看见有人收藏&#xff0c;继续努力学习下去。今天要实现的内容是如何将昨天的HelloDetlaLake 在spark集群上运行&#xff0c;。具体步骤如下 1、安装spark,我使用的是 spark-3.5.1-bin-hadoop3-scala2.13&#xff0c;去官网下载&#xff0c…

无穷级数错题本

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <

2024五一赛数学建模A题B题C题完整思路+数据代码+参考论文

A题 钢板最优切割路径问题 &#xff08;完整资料在文末获取&#xff09; 1. 建立坐标系和表示方法&#xff1a; 在建模之前&#xff0c;我们需要将切割布局转换为数学表示。首先&#xff0c;我们可以将布局中的每个点表示为二维坐标系中的一个点。例如&#xff0c;B1可以表示…

Ubuntu服务器创建新用户及解决新用户登录Access denied问题

目录 Ubuntu服务器创建新用户及解决新用户登录Access denied问题创建账号步骤创建用户只创建用户添加用户到sudo组 允许账号远程连接重启ssh服务 删除账号要删除用户而不删除用户文件如果要删除并且删除用户的家目录和邮件 查询指令查看所有用户查询特定用户账户信息查看用户组…

【Java基础】Maven的生命周期(clean+site+default)

1. 前言 在 Maven 出现之前&#xff0c;项目构建的生命周期就已经存在&#xff0c;开发人员每天都在对项目进行清理&#xff0c;编译&#xff0c;测试及部署&#xff0c;但由于没有统一的规范&#xff0c;不同公司甚至不同项目之间的构建的方式都不尽相同。 Maven 从大量项目…

[C++基础学习-07]----C++结构体详解

前言 结构体&#xff08;Struct&#xff09;是C中一种用户定义的复合数据类型&#xff0c;用于存储不同类型的数据项。结构体可以包含不同类型的数据成员&#xff0c;这些数据成员可以是基本类型&#xff08;如int、float、char等&#xff09;&#xff0c;也可以是数组、指针、…

Linux编辑器——vim的基础使用

文章目录 1.vim的基本概念2.vim的基本操作3.vim命令模式命令集3.1移动光标3.2删除文字3.3复制3.4替换3.5撤销3.6更改3.7跳到指定的行 1.vim的基本概念 本文将介绍vim的三种模式&#xff0c;分别位&#xff1a;命令模式、插入模式、低行模式。他们的功能区分如下&#xff1a; 正…

2. 深度学习笔记--损失函数

在机器学习中&#xff0c;损失函数是代价函数的一部分&#xff0c;而代价函数则是目标函数的一种类型。 Loss function&#xff0c;即损失函数&#xff1a;用于定义单个训练样本与真实值之间的误差&#xff1b; Cost function&#xff0c;即代价函数&#xff1a;用于定义单个批…