G1原理—10.如何优化G1中的FGC

news/2025/1/17 23:49:20/文章来源:https://www.cnblogs.com/mjunz/p/18677839

大纲

1.G1的FGC可以优化的点

2.一个bug导致的FGC(Kafka发送重试 + subList导致List越来越大)

3.为什么G1的FGC比ParNew + CMS要更严重

4.FGC的一些参数及优化思路

 

1.G1的FGC可以优化的点

(1)FGC的基本原理

(2)遇到FGC应该怎么处理

(3)应该如何操作来规避FGC

(4)应该如何操作来加快FGC的速度

 

(1)FGC的基本原理

一.FGC的并行处理

G1有两个得天独厚的优势:

优势一.Region是一个相对独立的内存区域

优势二.每个Region都有一个RSet

 

通过GC Roots + RSet,就能完整对某Region进行所有存活对象的标记。

 

二.FGC的并行处理流程

并行FGC开始前的前置工作:对象头、锁信息等信息的保存处理。在保存完一些对象头相关的信息之后,就要开始FGC了。

 

具体步骤和串行化的FGC是类似的。

步骤一:标记所有存活对象

步骤二:计算对象的新地址

步骤三:更新对象间的引用地址

步骤四:移动对象完成压缩

步骤五:对象移动后的后续处理

 

三.并行标记过程STW

FGC并行化后,其并行标记过程和串行化过程时差别不是很大,都是要标记出来所有的存活的对象。

 

需要注意的是:因为是并行化处理,所以多个线程在进行并行标记时,每个线程都会比起串行化处理时,多一个标记栈(任务栈)。也就是把起始对象GC Roots分成多份,每一个GC线程持有一部分。

 

FGC会对所有堆分区里的对象都进行标记,而且系统程序会STW。

 

四.FGC标记过程中的任务窃取

完成任务栈的对象标记的线程会从未完成的线程那里窃取一些任务。

 

(2)遇到FGC应该怎么处理

一.尽可能避免

FGC是需要极力避免的,JVM的优化手段多数都是尽可能减少FGC的出现。比如调整分代比例、Region大小、停顿时间、老年代预留空间比例等。所以,对于FGC的处理,最重要的手段就是避免它。

 

二.尝试优化FGC的速率

如果是正常的FGC,优化速率的方法就是,减少FGC需要处理的量。也就是FGC时,避免堆中存在大量需要复杂处理的对象。

 

(3)应该如何操作来规避FGC

基本的思路还是要避免达到产生FGC的条件。

 

一.产生FGC的条件主要是以下两种

条件一:MGC不及时,导致垃圾对象存活过多,造成空间不够

这其实也是并发标记的启动时机存在问题。如果并发标记启动的频率,远远落后于垃圾产生的速率。那么就会出现大量空间被垃圾对象占用,导致不必要的FGC。另外就是回收速度太低,导致停顿时间内回收垃圾太少,造成空间不够。

条件二:存活对象太多,各种GC都尝试过,无法腾出足够空间给新对象

执行了YGC + MGC后出现晋升失败,不得不进行FGC。

 

二.产生FGC的场景主要是以下两种

场景一:G1使用的算法,是标记复制算法(YGC和MGC) + 标记整理算法(FGC)。在进行垃圾回收时,新创建的对象及存活的对象,没有足够空间可使用。复制操作无法实现,因为每次GC的存活对象要复制到空闲Region中。

场景二:多次GC后仍然无法给新对象腾出足够的空间,导致FGC。此时所能做的就是尽可能合理优化参数,保证不触发这些场景。

 

(4)应该如何操作来加快FGC的速度

FGC的速度,在JVM层面其实已经做了很多优化,包括并行优化等。那么在此基础之上,我们还能通过什么策略来提升FGC的速度?比如要减少FGC处理的总量,靠减少堆内存来减少FGC要处理的总量吗?针对FGC的速度要做的优化,不能简单的从堆内存空间大小来考虑。

 

G1提供了一种思路,叫"弥留空间"。当G1发现经历多次GC后,就会允许一定比例的空间,作为把垃圾对象当成存活对象处理的空间。这些垃圾对象所在的一定范围的区域,可以成为弥留空间。虽然这个弥留空间是垃圾对象,但在GC处理时,是当作存活对象来处理。

 

FGC在处理这块弥留空间里的对象时,会把它们直接当作存活对象来处理,不需要做各种复杂的标记、判定引用、指针替换等各种操作,直接去复制。明知是死亡对象,但此时先不做全部空间的标记压缩整理,而只做部分的。所以弥留空间就能快速被跳过,减少处理空间,一定程度提高FGC的速率。弥留空间,就是为了提升FGC的效率而设计的。

 

2.一个bug导致的FGC(Kafka发送重试 + subList导致List越来越大)

(1)运营场景业务分析

(2)业务场景背景介绍

(3)问题现场及问题排查

(4)问题解决

 

(1)运营场景业务分析

一般在电商公司里会有一个运营平台,有些公司叫营销平台,这个平台的作用主要就是拉新、增涨营收。就是通过运营平台发起活动、或者投放广告,来实现用户增长。对于平台的存量用户,也会有优惠活动、节日福利活动等来留住用户。

 

各种各演的运营活动,都会通过运营平台去产生。然后专门有一个抽离的运营消息推送服务来推送运营活动消息给用户。由于这些消息的数量非常庞大,对于一家几十万上百万的用户,一天假如有3次运营活动,就要至少好几百万甚至上千万的的消息要推送。这么大量的消息,不会直接全量推送,而是生成推送消息,发送到MQ,然后通过一个消费者去消费这些消息,慢慢把千万级别的消息推送给用户。

 

(2)业务场景背景介绍

在这个场景中,一个很重要的要点就是:运营消息推送平台会生成消息推送到消息中间件Kafka中。这个消息生成、推送至MQ的速率,很大程度影响到整个推送流程的速度。

 

因此,对这个消息生成和推送的过程,就需做优化,当时优化的思路是:

一.分布式生成消息,即多台机器,把用户群体分成多个部分来生成消息

二.batch推送消息,减少与消息中间件的网络通信

三.运营消息推送平台,使用多线程并发推送batch消息

四.把一些大批量的查询借助一些其他的数据搜索引擎或缓存来提升效率

 

比如,借助JVM本地缓存,每台机器保存一些用户账号相关信息。比如,使用ES来存储用户信息,提升多条件下的搜索查询效率。比如,使用Redis Cluster缓存,避免使用数据库导致效率低。

 

这些基本的思路其实还是比较简单的,细节上的优化就是:batch的大小如何确定、多线程推送时线程池的线程数量要怎么设置等。

 

(3)问题现场及问题排查

一.引发OOM的原因是频繁FGC

然后在一次优化测试上线后,运行一段时间,没有什么大问题。在某天Kafka服务发生了一次抖动,持续时间大概5-6s的时间,然后发现这个运营平台直接崩溃了。

 

在优化前,这个运营平台的推送效率有待提升,但是系统还是很稳定的。出现这个问题后紧急查看了报错日志,发现是堆内存OOM导致进程崩溃。

 

于是下载GC日志,下载内存快照文件,查看dump快照文件,最后发现是batch推送时线程池中的线程持有了大量的大List对象。

 

本来是打算找OOM的原因,解决OOM问题的。但发现在OOM前出现了大量的FGC,而大量的FGC才最终导致系统崩溃。

 

那么结合dump快照里大量的List对象,很容易想到是大量的List导致频繁FGC。而大量的List都还存活,所以最终导致OOM。

 

二.频繁FGC的原因是List造成的对象过大

那么为什么会有这么多次FGC,为什么有这么多的List没被回收?通过代码排查,发现了一个很严重的代码bug,这个bug其实很好规避。

 

原来在代码中采取的策略是:每次会查询一组用户数据,这组用户数据会有2000个用户。然后将这组数据封装成消息体是按照一个List放200个用户去封装,也就是将200个用户封装成一个List消息体之后,才会发送到Kafka中。

 

注意:Kafka本身也有一个缓冲机制来实现batch发送。我们的代码将消息一条一条发送时,Kafka会在本地客户端暂存。存到一定大小时,才会按照一个批次推送到Kafka服务端。

 

这种策略正常来说是没问题的,因为200个用户封装后的大小也不大。但是写代码时,因为运营的一个需求,把其中一段代码修改了一下。运营要求在某个促销活动下,消息必须推送到用户侧,不能出现漏发错发多发。

 

原有的代码是:把拿到的2000一组的用户数据,拆分成10个batch,然后推送至Kafka。假如推送失败就不管了,因为漏发某个用户的消息也问题不大。

 

修改后的代码是:针对这种特殊活动,添加了一些逻辑,即等待Kafka返回推送结果。如果拿到推送结果则表明推送成功,则执行完毕。如果拿不到推送结果则表明推送失败,需要把失败的用户集,重新加入到一个集合中去重试推送。具体的伪代码如下:

//获取一组用户信息
ArrayList list = getUserList();
List subList;
List failList = new ArrayList();
int index = 0;
//拆分用户信息
while(true) {if (index >= list.size()-1) {break;}subList = list.subList(index, index + 200);kafkaPushResult = sendToKafka();//等待发送结果if (kafkaPushResult == false) {//发送失败,是第一次,就把发送失败的subList赋值给failListif (index == 0) {failList = subList;}//不是第一次,就把发送失败的全部加入到failList中,等待后续继续发送failList.addAll(subList);}
}

上述代码看起来是不是没什么问题?事实上,当这个kafkaPushResult失败没有触发时,确实不会出什么问题。但是一旦触发了失败,就会出现问题了。

 

原因在于对于List来说,subList不是新建了一个对象,而是把大的list的其中一段使用了两个指针去指向它,所以本质上subList还是大的list的一部分。

 

那么如果频繁触发发送失败,failList这么写就相当于是大的list的一部分。那么执行failList.addAll(subList)代码时,相当于是在把这部分失败的元素,加入到大的list里。

所以,list是会越来越大。如果一次推送几十万用户的消息,一个线程池里面设置30-60个线程。那么在Kafka抖动的几秒的这段时间,这些list会极速扩张好几千倍不止。也就是说,假设这5s内,一共能拿到10w的用户数据。那么在5s内就会膨胀到几千万甚至上亿的数据,紧接着,重试操作会针对这些数据重试,而这个大的list又暂时不释放。如果Kafka抖动时间短,经历几次FGC就完成重新推送,没什么问题了。如果Kafka经常抖动或抖动时间长,就会造成频繁的FGC,甚至OOM。

 

这个场景,由于FGC产生的原因比较直接。所以分析时很容易通过GC日志 + dump快照文件迅速定位了问题。但很多FGC场景,产生的原因各不相同,分析过程还是会非常复杂的。不过基本思路都是:系统日志 -> 监控数据 -> GC日志 -> dump文件 -> 代码反查 -> bug复现 -> 解决问题。

 

(4)问题解决

知道了问题原因,其实就很好解决了。根本原因还是因为在写代码时对api源码的实现不熟悉,同时也没有深入检查代码的习惯,导致在做一些业务处理时出现了意想不到的bug。

 

对于这个代码,其实只需要做一个改动就OK了。在外层初始化failList,并且在需要把subList数据作为数据源的操作时,使用addAll操作,把它加入到新的list中再进行操作即可。

failList = new ArrayList();
if (index == 0) {failList = subList;
}->
failList.addAll(subList)

在使用subList拆分list时,一定要熟悉一下这个subList的源码实现方式。往往JDK的一些优化手段,会给我们程序造成一些不必要的问题。

 

此外,如果出现了频繁FGC,很有可能是对象产生的速度和垃圾回收的速度匹配不上,回收的量不够就会有可能OOM了。

 

3.为什么G1的FGC比ParNew + CMS要更严重

(1)ParNew + CMS的FGC触发

(2)G1的FGC触发

(3)G1的FGC更加恐怖的原因总结

 

(1)ParNew + CMS的FGC触发

ParNew + CMS触发的FGC的规则其实还是比较简单的,总结来说,就是老年代不够用了。

 

当然老年代不够用的过程可能比较多:新生代晋升、大对象占用等。但使用ParNew + CMS时,新生代和老年代的比例往往都是比较均衡,有些系统新生代甚至远大于老年代。

 

那么在这种场景下,FGC相对来说回收的空间就不算太恐怖,毕竟回收的空间只有堆内存的1/2左右。比较耗时的地方就是:全量标记、对象复制、压缩整理的过程。因此FGC即使偶尔发生一次,比如一天一次或几小时一次,也能接受。

 

并且老年代对象的晋升,是有一系列的担保机制的,比如:老年代剩余内存大于新生代存活对象、老年代剩余内存大小大于历次新生代晋升到老年代的对象的平均大小等。

 

因此综合分析下来,ParNew + CMS触发FGC时,从空间上和处理算法(标记整理)上来说,偶尔一次还是能够接受的。

 

(2)G1的FGC触发

G1的FGC触发场景,基本上有两种:

 

一.新生代晋升失败导致的FGC

有可能是因为晋升预留空间不够导致的,比如预留的晋升空间比例参数G1ReservePercent调整为了5,而新生代区本次晋升对象比较多,此时就会发生晋升失败导致FGC。

 

这种情况触发的FGC稍微好一点,因为老年代的实际使用量是:老年代大小 * (1 - --XX:G1ReservePercent%)。并且因为是晋升导致的FGC,此时新生代是刚执行完新生代回收的。所以基本上这种情况下导致的FGC,只需回收相对比较小的区域即可。

 

即便如此,这种情况下的FGC效率依然很低,因为涉及到大量的标记、压缩、整理、引用处理等各种操作。

 

二.对象分配失败导致的FGC

对象分配失败导致的FGC,就比较恐怖了。因为在对象分配失败时,会经历如下整个过程:TLAB -> TLAB扩展 -> 堆内存分配 -> Region扩展 -> YGC -> MGC -> 堆扩展 -> 分配失败。

 

如果是这个过程造成分配失败,则意味着整个堆内存的使用率非常高。即使经过了YGC + MGC + 扩展操作,还是无法成功分配。

 

这时进入的FGC就可以理解为:几乎大部分的内存都被占用了,连一个对象都无法成功分配。此时的FGC需要处理的Region,几乎是整个堆内存里的所有Region。

 

并且结合FGC的整个处理过程:标记->对象头处理->对象移动->压缩->清理->Rset处理等一系列操作,此时的FGC就会非常耗时。

 

所以这个情况下触发的FGC会非常恐怖,当然如果没有乱调参数,正常情况也不会发生这么恐怖的FGC。

 

三.总结

如果YGC执行后晋升失败导致FGC,那么就是reserve空间不够导致的。也就是分配对象失败,执行YGC后,必然会发生晋升,此时有可能就会在晋升失败前触发FGC来清理了。所以基本上不会出现达到多次YGC + MGC之后,还无法分配成功的情况。

 

但要注意有可能会出现:QPS暴增,对象产生速度比较快,然后回收速度比较慢,虽然经历了多次YGC,但垃圾对象依然很多的情况。

 

(3)G1的FGC更加恐怖的原因总结

一.G1通常来说要管理更大的堆内存空间,因此需要处理更多的对象

二.G1触发FGC的条件比较苛刻,分配失败的FGC需处理整个堆内存

三.G1在执行FGC过程中,需要针对复杂的Rset引用关系做更多处理

 

4.FGC的一些参数及优化思路(都围绕回收速度跟不上垃圾产生速度展开)

(1)-XX:G1HeapRegionSize

(2)-XX:G1ReservePercent默认是10

(3)-XX:MaxGCPauseMillis停顿时间

(4)-XX:InitiatingHeapOccupancyPercent

(5)-XX:ConcGCThreads

(6)-XX:G1ConcMarkStepDurationMillis

(7)-XX:MarkSweepAlwaysCompactCount

 

(1)-XX:G1HeapRegionSize

这个参数用于控制Region大小,调整这个参数可以避免老年代中的大对象占用过多的内存。因为老年代大对象占用过多的内存,就会提高老年代的使用率。老年代的使用率高了,就必然会增加晋升失败的概率。所以增大Region大小,可以避免不算太大的对象进入老年代,从而降低晋升失败的概率。

 

调大RegionSize的另外一个优点是:在发生对象复制、晋升时,PLAB也会相对大一些,从而在复制、晋升时,速率也会提升。

 

(2)-XX:G1ReservePercent默认是10

这个参数代表老年代预留给新生代对象晋升的空间占用堆内存的比例,如果经常因为晋升失败导致FGC,说明这个值太小。此时可适当调高这个值,降低FGC频率。

 

(3)-XX:MaxGCPauseMillis停顿时间

这个参数设置是否合理,关系到了垃圾回收的效率。假如设置了20ms的停顿时间,则很有可能导致每次GC回收的垃圾非常少。假如系统并发非常高,产生垃圾的速度非常快,就有可能会不断进行YGC。但是每次YGC都只能回收掉很少一部分垃圾,最终造成FGC。

 

所以,一个合理的停顿时间设置,是非常有必要的。一般情况下,系统的停顿时间可以设置100-200ms之间,具体情况需要根据系统运行情况及JVM监控情况来调整。

 

(4)-XX:InitiatingHeapOccupancyPercent

意思是当堆内存的占用比例达45%时,就会触发并发标记(可能开启MGC)。假如因为垃圾回收的速率跟不上系统产生垃圾的速度而造成频繁的FGC,那么就可以适当调低这个参数,尽快开启MGC,通过提升MGC的频率来避免JVM内堆积过多的垃圾对象。

 

注意:这个参数调小造成的多次GC和停顿时间造成的多次GC不是一个概念。

 

停顿时间造成的GC:是因为每次停顿时间不够,只能回收很少的垃圾,导致垃圾堆积最终FGC。

 

调小InitiatingHeapOccupancyPercent这个参数:此时停顿时间固定,但是回收的频率提升上来了。这样在同样的程序运行时间里,能够回收更多的垃圾,可以避免FGC。

 

(5)-XX:ConcGCThreads

这个参数是指在标记过程中的并行标记(STW)线程数量。如果因为并发标记不够及时,并发标记时间过长,导致垃圾回收的速率跟不上,造成FGC。

 

略微提升这个值,可能能够提升标记速度,以达到避免FGC的效果。当然线程数量多少,要和服务器的配置相匹配,不能盲目调大这个值。

 

(6)-XX:G1ConcMarkStepDurationMillis

这个参数指的是并发标记(不会STW)执行的时间,默认是10ms。调小该参数可以提升并发标记的次数,让并发标记触发的更加频繁一点,从而让重新标记更短一点,以此来提升垃圾回收的速率,避免垃圾回收速度跟不上垃圾产生速度,最终造成FGC。

 

正常来说这个参数也是不需要调整的,只有发现系统经常因为垃圾处理不及时而频繁的触发FGC,才有可能需要调小这个参数。

 

(7)-XX:MarkSweepAlwaysCompactCount

这个参数表示,经过多次GC后:允许JVM内存中有一定比例的空间用来将垃圾对象当作存活对象来处理,可以称这块儿空间为弥留空间。

 

弥留空间主要的目的就是可以把垃圾对象当作存活对象来处理,相当于给了一块比较好处理的空间,能够减少FGC时的处理压力。可以说是间接"减少FGC需要处理的空间",这个空间不宜太大。如果太大就会造成一部分空间被占用,一般来说保持默认即可。

 

(8)总结

上面介绍的这些参数,多数都是以"避免"FGC的思路来展开优化。只有第七个参数是通过为FGC"减负"的思路来展开优化,而且避免FGC通常都是从提升垃圾回收的速率这个角度出发:让垃圾回收速率赶上垃圾对象产生的速率。

 

对于FGC来说,"减负"这个思路,在多数情况下是无法采取的。因为FGC的特性就决定了不太可能通过其本身的参数调整来提速,更多的优化思路和优化手段还是要从"避免"角度出发,避免大多数FGC才是优化的重点。

 

回收速度跟不上垃圾产生速度总结:

一.调大RegionSize,让PLAB增大,从而加快复制、晋升的速度

二.调大老年代预留给新生代晋升的占比,降低晋升失败的频率

三.调小触发MGC的老年代占比,加快MGC

四.增加并发标记的线程,加快GC

五.降低每次并发标记的执行时间,降低重新标记时间,加快GC

六.调大弥留空间占比,降低FGC的处理压力,提升FGC的处理速度

 

为什么G1的FGC比ParNew + CMS要更严重:

一.ParNew + CMS的FGC触发

就是老年代不够用了。

二.G1的FGC触发条件

条件一:新生代晋升失败导致的FGC。

条件二:对象分配失败导致的FGC。

三.G1的FGC更加恐怖的原因总结

原因一:G1通常来说要管理更大的堆内存空间,因此需要处理更多的对象。

原因二:G1触发FGC的条件比较苛刻,分配失败的FGC需处理整个堆内存。

原因三:G1在执行FGC过程中,需要针对复杂的Rset引用关系做更多处理。

 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

firefox浏览器deepin20.9下缩放异常

地址栏输入about:config,修改layout.css.devPixelsPerPx默认1.25,改为1.0就可以了。 deepin版本: 20.9 社区版 firefox版本: 115.18.0esr (64 位) 修改前改参数修改后

【内网攻防】WMI的利用

# 红队 # 红蓝对抗 # 内网攻防 WMI简介 WMI,全称Windows Management Instrumentation,是微软开发的一种用于管理Windows系统的工具。你可以把它想象成一个数据库工具,它存储了关于你的电脑的各种信息,比如系统、应用程序、网络和设备等。 WMI使用了一种特殊的方式来表示这…

【后门】cymothoa后门的利用

# 后门工具 # 红队 cymothoa简介: Cymothoa是一款隐秘的后门工具,通过向目标主机上活跃的进程注入恶意代码来执行后门工作,这也反向说明了,实际上Cymothoa后门会拥有和原进程相同的权限,且Cymothoa是通过向系统进程注入shellcode去执行后门,所以不会像以前写过的许多后门…

Linux 符号链接

Linux中的符号链接是一种特殊类型的文件,允许用户创建指向其他文件或目录的链接,便于访问和管理文件系统。创建和删除符号链接很简单,使用ln -s命令可以创建链接,而unlink和rm命令可用于删除它。用户还可以通过命令查找和处理失效的符号链接,以维护文件系统的整洁。在Linu…

在 Linux 中查看符号链接指向

在 Linux 系统中,可以通过使用 readlink、realpath 和 ls -la 命令来查看符号链接指向的目标文件或目录,选择不同命令可以满足不同的路径解析需求。在 Linux 系统中,符号链接(symbolic link)是一种特殊类型的文件,它指向另一个文件或目录。为了查看符号链接指向的目标文件…

Office 365 E5 账号申请及永久续期教程

详细介绍了如何申请微软提供的Office 365 E5管理账号、使用该账号的基本功能以及实现账号的永久续期方法,强调通过Github Action等自动化手段可以简化续期过程。续订成功通知Office 365 E5 是微软 免费 提供给开发者的 Office 临时 管理号,任何人均可申请。该账号的最大权益便…

【Windows攻防】恶意软件容器化

# windows 微软在2022年7月为了帮助用户保持安全,在Office中默认阻止了internet宏。 阻止从Internet下载的文档中的VBA宏。 接下来我们来了解一下MOTW标记,其实就是网络标记。 什么是MOTW标记? MOTW全名Mark of the Web,他是windows操作系统中的一个安全特性,为了帮助用户…

【Spring Cloud Alibaba AI】 入门与实践

一、概述 Spring AI 是 Spring 官方社区项目,旨在简化 Java AI 应用程序开发,让 Java 开发者像使用 Spring 开发普通应用一样开发 AI 应用。可参考文章《SpringAI:Java 开发的智能新利器》Spring Cloud Alibaba AI 是一个将 Spring Cloud 微服务生态与阿里巴巴 AI 能力无缝集…

Re:Re:笑死

哈哈哈哈哈博客用的很熟练嘛 闲的没事去桃花湖那边开了一圈 下午的旅程后劲儿太大了 开车的时候仿佛眼前还都是山 今晚自己和猫猫独享大床真爽啊!!吼吼 再次给你欣赏一下12的美照哈哈哈哈哈哈

阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?

本文原文链接 文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 …

WSL CUDA安装

【一文解决】已安装CUDA与Pytorch但torch.cuda.is_available()为False_torch.cuda.is available返回false-CSDN博客 cuda安装,选WSL e.g.Ubuntu 20.04安装CUDA & CUDNN 手把手带你撸_ubuntu20.04安装cuda-CSDN博客 【CUDA】Ubuntu系统如何安装CUDA保姆级教程(2022年最新)_…

未来已来:探索智能家居行业的创新与突破

智能家居行业正站在新科技革命的风口浪尖,不断推动着生活方式的革新。以下是该行业在创新与突破方面的详细介绍: 1.行业发展趋势: 智能家居行业正从单品智能设备过渡至全屋智能阶段,平台生态的构建成为主要发展方向。预计到2029年,中国智能家居市场规模将达到1.09万亿元。…