【iOS】autoreleasepool
来说一下最近在了解的autoreleasepool
吧,我们可能平时书写过许多脑残代码,其有很多的缺陷但是我们可能当时学的比较浅就也不太了解,就像下面这样的:
for (int i = 0; i < 1000000; i++) {NSNumber *num = [NSNumber numberWithInt:i];
}
一、@autoreleasepool{}
我们平时创建一个main
函数的代码的时候,就会发现其中有一个这个东西@autoreleasepool{}
,使用clang
编译之后:@autoreleasepool{...}
被编译成了{__AtAutoreleasePool __autoreleasepool; ... }
。
这个__AtAutoreleasePool
到底是什么?
它其实是一个结构体,在创建__AtAutoreleasePool结构体变量的时候调用了objc_autoreleasePoolPush(void),销毁的时候会调动objc_autoreleasePoolPop(void *),即其构造函数和析构函数,所以我们可以看出其其实是一个C++封装的自动释放池变量,会将@autoreleasepool{…}中{}中的内容添加到自动释放池中,方便内存管理。
但是它在main
这个函数中好像感觉并没有什么用,因为程序结束了那么内存不也就被释放了,那这里为什么要加@autoreleasepool{}
?
技术上是可行的,去掉main函数中的@autoreleasepool{}并没有什么关系,但是为了严谨,为了使UIApplicationMin创建出来的自动释放对象有自动释放池可添加,并能在自动释放池结束的时候释放对象而不是依赖操作系统的回收,所以加上@autoreleasepool{},可以把它理解为最外层才触发释放机制的自动释放池。
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);struct __AtAutoreleasePool {__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}void * atautoreleasepoolobj;
};
所以我们平时在ARC
环境下写的@autoreleasepool{}
它其实就是自动释放池创建和释放的简单使用。
它会在{
的时候创建自动释放池,在}的时候销毁自动释放池并发出release
通知,让其中的变量自己进行release
操作。
二、AutoreleasePoolPage
从上边的__AtAutoreleasePool
我们可以看到这两种方法objc_autoreleasePoolPush
和objc_autoreleasePoolPop
,但是这究竟是什么呢?
void *objc_autoreleasePoolPush(void) {return AutoreleasePoolPage::push();
}void objc_autoreleasePoolPop(void *ctxt) {AutoreleasePoolPage::pop(ctxt);
}
我们可以看出这里又引入了新的类AutoreleasePoolPage
,其相关源码如下:
class AutoreleasePoolPage {magic_t const magic;//AutoreleasePoolPage 完整性校验id *next;//下一个存放autorelease对象的地址pthread_t const thread; //AutoreleasePoolPage 所在的线程AutoreleasePoolPage * const parent;//父节点AutoreleasePoolPage *child;//子节点uint32_t const depth;//深度,也可以理解为当前page在链表中的位置uint32_t hiwat;
}
每一个自动释放池都是由一系列AutoreleasePoolPage
组成的,并且每一个AutoreleasePoolPage
的大小都是4096字节(16 进制 0x1000)。
#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES
所以我们从上述的源码可以看出,自动释放池其实就是一个由AutoreleasePoolPage
构成的双向链表,其结构中的child
和parent
分别指向其前趋和后继。
单个AutoreleasePoolPage
结构如下:
其中有 56 bit 用于存储AutoreleasePoolPage
的成员变量,剩下的0x100816038 ~ 0x100817000都是用来存储加入到自动释放池中的对象。
- 该结构体的第一个成员变量是
magic
,我们在isa
中也学习过,isa
中是分判对象是否未完成初始化,在这里也一样,用来检查这个节点是否已经被初始化了。 begin()
和end()
这两个类的实例方法帮助我们快速获取0x100816038 ~ 0x100817000
这一范围的边界地址。next
指向下一个为空的内存地址,如果next指向的地址加入一个object
,它就会如下图所示移动到下一个为空的内存地址中,就像栈顶指针一样。thread
保存了当前页所在的线程。depth
表示page
的深度,首次为0,每个page
的大小都是4096字节(16进制0x1000),每次初始化一个page
,depth
都加一。POOL_SENTINEL
就是哨兵对象,它只是nil
的别名,用于分隔Autoreleasepool
。POOL_BOUNDARY
直译过来就是POOL
的边界。它的作用是隔开page
中的对象。因为并不是每次push
与pop
之间存进的对象都刚好占满一个page
,可能会不满,可能会超过,因此这个POOL_BOUNDARY
帮助我们分隔每个@autoreleasepool
块之间的对象。也就是说这个page
可能存储很多个@autoreleasepool
块的对象,使用POOL_BOUNDARY
来隔开每个@autoreleasepool
块的对象。
#define POOL_SENTINEL nil
如果向上述刚初始化的page
添加对象时,就会添加在next的指向处,next再向后移一位。
并且在每个自动释放池初始化调用objc_autoreleasePoolPush
的时候,都会把一个POOL_SENTINEL push
到自动释放池的栈顶,并且返回这个POOL_SENTINEL
哨兵对象。
int main(int argc, const char * argv[]) {{//这里的 atautoreleasepoolobj 就是一个 POOL_SENTINELvoid * atautoreleasepoolobj = objc_autoreleasePoolPush();// do whatever you wantobjc_autoreleasePoolPop(atautoreleasepoolobj);}return 0;
}
上面的atautoreleasepoolobj
就是一个POOL_SENTINEL
。
而当方法objc_autoreleasePoolPop
调用时,就会向自动释放池中的对象发送release
消息,直到第一个 POOL_SENTINEL
。
这也是autoreleasepool
能准确释放其中对象的原因:在该autoreleasepool push
的时候会返回一个哨兵对象(POOL_SENTINEL
)的地址,并将它给pop
,那么这个pop
就知道在执行pop
释放的时候释放到这个哨兵对象(POOL_SENTINEL
)处就可以停止了,而这其中释放的内容就正好是自动释放池中的对象。
1.objc_autoreleasePoolPush方法:
void *objc_autoreleasePoolPush(void) {return AutoreleasePoolPage::push();
}
这里调用了AutoreleasePoolPage::push()
方法:
static inline void *push()
{id *dest;// POOL_BOUNDARY就是nil// 首先将一个哨兵对象插入到栈顶if (DebugPoolAllocation) {// 区别调试模式// 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中// Each autorelease pool starts on a new pool page.dest = autoreleaseNewPage(POOL_BOUNDARY);} else {dest = autoreleaseFast(POOL_BOUNDARY);}assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;
}
其中调用了autoreleaseFast
方法,hotPage
指的是当前正在使用的AutoreleasePoolPage
:
static inline id *autoreleaseFast(id obj)
{AutoreleasePoolPage *page = hotPage();if (page && !page->full()) {//有 hotPage 并且当前 page 不满,将object加入当前栈中return page->add(obj);} else if (page) {//有hotPage 但当前page已满,找未满页或创建新页,将object添加到新页中return autoreleaseFullPage(obj, page);} else {//无hotPage,创建hotPage,加入其中return autoreleaseNoPage(obj);}
}
1.1 有hotPage并且当前page不满,直接调用page->add(obj)将对象添加到自动释放池中。
// 这其实就是一个压栈操作,将对象加入AutoreleasePoolPage,然后移动栈顶指针
id *add(id obj) {id *ret = next;*next = obj;next++;return ret;
}
1.2 有hotPage但当前page已满,找未满页或创建新页,将object添加到新页中autoreleaseFullPage (当前page满的时候调用):
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
//一直遍历,直到找到一个未满的 AutoreleasePoolPage,如果找到最后还没找到,就新建一个 AutoreleasePoolPagedo {if (page->child) page = page->child;else page = new AutoreleasePoolPage(page);} while (page->full());//将找到的,或者构建的page作为hotPage,然后将obj加入setHotPage(page);return page->add(obj);
}
1.3 无hotPage,创建hotPage,加入其中:
这个时候,由于内存中没AutoreleasePoolPage
,就要从头开始构建这个自动释放池的双向链表,那么当前页表作为第一张页表,是没有parent
指针的。并且我们在第一次创建page
时其首位都是要加POOL_SENTINEL
标识的,方便让page
知道在哪就结束了。
static id *autoreleaseNoPage(id obj) {AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // 创建AutoreleasePoolPagesetHotPage(page); // 设置page为当前页if (obj != POOL_SENTINEL) { // 加POOL_SENTINEL哨兵page->add(POOL_SENTINEL);}return page->add(obj); // 将obj加入
}
autorelease方法
我们现在再想,它dest = autoreleaseFast(POOL_BOUNDARY)
;操作传递的是POOL_BOUNDARY
变量,并没有传对象,那么到底是怎么存入进去的呢?
通过调试查看汇编发现它其实调用的是objc_retainAutorelease
方法,之后层层调用发现调用的是autorelease
方法:
static inline id autorelease(id obj)
{printf("static inline id autorelease%p\n", obj);assert(obj);assert(!obj->isTaggedPointer());id *dest __unused = autoreleaseFast(obj);assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);return obj;
}
2.objc_autoreleasePoolPop方法:
void objc_autoreleasePoolPop(void *ctxt) {AutoreleasePoolPage::pop(ctxt);
}
我们一般都会在这个方法中传入一个哨兵对象POOL_SENTINEL
,方便释放,如下图一样释放对象:
其调用的pop
方法如下:
static inline void pop(void *token) {AutoreleasePoolPage *page = pageForPointer(token);//使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPageid *stop = (id *)token;page->releaseUntil(stop);//调用 releaseUntil 方法释放栈中的对象,直到 stop 位置,stop就是传递的参数,一般为哨兵对象//调用 child 的 kill 方法,系统根据当前页的不同状态kill掉不同child的页面//releaseUntil把page里的对象进行了释放,但是page本身也会占据很多空间,所以要通过kill()来处理,释放空间if (page->child) {if (page->lessThanHalfFull()) { // 当前page小于一半满page->child->kill(); // 把当前页的孩子杀掉} else if (page->child->child) { // 否则,留下一个孩子,从孙子开始杀page->child->child->kill();}}
}
Apple假设,当前page
一半都没满,说明剩余的page
空间已经暂时够了,把多余的child page
就可以全kill
掉,释放空间,如果超过一半,就认为下一页page
还有存在的必要,说不定添加的对象太多就能用的到,所以kill
掉孙子page
,有个儿子page
就暂时够了。
token
oken
是指向该pool
的POOL_BOUNDARY
指针token
的本质就是指向哨兵对象的指针,存储着每次push
时插入的POOL_BOUNDARY
的地址- 只有第一次push的时候会在
page
中插入一个POOL_BOUNDARY
【或者page
满了,或者没有hotPage
需要使用新的page
了】,并不是page
的开头都一定是POOL_BOUNDARY
2.1 pageForPointer 获取 AutoreleasePoolPage:
pageForPointer
方法主要是通过内存地址的操作,获取当前指针所在页的首地址:
static AutoreleasePoolPage *pageForPointer(const void *p) {return pageForPointer((uintptr_t)p);
}static AutoreleasePoolPage *pageForPointer(uintptr_t p) {AutoreleasePoolPage *result;uintptr_t offset = p % SIZE;assert(offset >= sizeof(AutoreleasePoolPage));result = (AutoreleasePoolPage *)(p - offset);result->fastcheck(); // 检查当前的result是不是一个AutoreleasePoolPagereturn result;
}
将指针与页面的大小,也就是 4096 取模,得到当前指针的偏移量,因为所有的AutoreleasePoolPage
在内存中都是对齐的:
p = 0x100816048
p % SIZE = 0x48
result = 0x100816000
而最后调用的方法fastCheck()
用来检查当前的result是不是一个AutoreleasePoolPage
。
通过检查magic_t结构体中的某个成员是否为0xA1A1A1A1。
2.2 releaseUntil 释放对象:
releaseUntil
方法的实现如下:
void releaseUntil(id *stop) {while (this->next != stop) { // 不等于stop就继续popAutoreleasePoolPage *page = hotPage(); // 获取当前页while (page->empty()) { // 当前页为空,就找其父页,并将其设置为当前页page = page->parent;setHotPage(page);}page->unprotect();id obj = *--page->next;memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 将内存内容标记为SCRIBBLEpage->protect();if (obj != POOL_SENTINEL) { // 该对象不为标识POOL_SENTINEL,就释放对象objc_release(obj);}}setHotPage(this);
}
它的实现还是很容易的,用一个while
循环持续释放 AutoreleasePoolPage
中的内容,直到next
指向了stop
。
使用memset
将内存的内容设置成SCRIBBLE
,然后使用 objc_release
释放对象。
2.3 kill()方法:
到这里,没有分析的方法就只剩下kill
了,而它会将当前页面以及子页面全部删除:
void kill() {AutoreleasePoolPage *page = this; // 获取当前页while (page->child) page = page->child; // child存在就一直往下找,直到找到一个不存在的AutoreleasePoolPage *deathptr;do {deathptr = page;page = page->parent;if (page) {page->unprotect();page->child = nil; // 将其child指向置nil,防止出现悬垂指针page->protect();}delete deathptr; // 删除} while (deathptr != this); // 直到this处停止
}
3.autorelease 方法
我们已经对自动释放池生命周期有一个比较好的了解,最后需要了解的话题就是autorelease
方法的实现,先来看一下方法的调用栈:
- [NSObject autorelease]
└── id objc_object::rootAutorelease()└── id objc_object::rootAutorelease2()└── static id AutoreleasePoolPage::autorelease(id obj)└── static id AutoreleasePoolPage::autoreleaseFast(id obj)├── id *add(id obj)├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)│ ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)│ └── id *add(id obj)└── static id *autoreleaseNoPage(id obj)├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)└── id *add(id obj)
在autorelease
方法的调用栈中,最终都会调用上面提到的autoreleaseFast
方法,将当前对象加到AutoreleasePoolPage
中。
这一小节中这些方法的实现都非常容易,只是进行了一些参数上的检查,最终还要调用autoreleaseFast方法:
inline id objc_object::rootAutorelease() {if (isTaggedPointer()) return (id)this;if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;return rootAutorelease2();
}__attribute__((noinline,used)) id objc_object::rootAutorelease2() {return AutoreleasePoolPage::autorelease((id)this);
}static inline id autorelease(id obj) {id *dest __unused = autoreleaseFast(obj);return obj;
}
三、开头问题的解答
看到这里你是否明白了开篇所说的问题所在?当一次运行循环结束之前,也就是autoreleasepool
释放autorelease
对象之前,autoreleasepool
的内存一直在增加,APP会出现内存峰值,卡顿,甚至会被系统强制关闭造成crash。
所以加上@autoreleasepool
保证每次循环生成的autorelease
对象及时的释放才能避免上述问题:
for (int i = 0; i < 1000000; i++) {@autoreleasepool {NSNumber *num = [NSNumber numberWithInt:i];NSLog(@"%@", num);}
}
另外@autoreleasepool
还有延迟释放,使对象超出函数作用域存在等用处。
四、总结
总的来说,autoreleasepool
就是一个双向链表,链表中的每个节点是一个栈,栈中保存了指向autoreleasepool
的指针并且其中加入了需要自动释放池管理的对象,所以在autoreleasepool
中的所有对象引用计数都会+1,一旦出了autoreleasepool
,没有指针指向对象,对象的引用计数就会-1,ARC下,xcode
会为代码自动添加 autoreleasepool
。
- 自动释放池是由
AutoreleasePoolPage
以双向链表的方式实现的 - 当对象调用
autorelease
方法时,会将对象加入AutoreleasePoolPage
的栈中 - 调用
AutoreleasePoolPage::pop
方法会向栈中的对象发送release
消息
关于哨兵对象(POOL_BOUNDARY)和next指针:
next指针只有一个,永远指向下一个能存放autoreleasepool的地址,而哨兵对象可以有很多个,每个autoreleasePool都对应一个哨兵对象,标示这个autoreleasePool对象从哪里开始存。
next和child:
next指向下一个能存放object对象的地址,child是autoreleasePoolPage的参数,指向下一个page。
autoreleasePoolPage与RunLoop的关系:
RunLoop和AutoReleasePool是通过线程的方式一一对应的
在非手动添加Autorelease pool下,Autorelease对象是在当前runloop进入休眠等待前被释放的
当一个runloop在不停的循环工作,那么runloop每一次循环必定会经过BeforeWaiting(准备进入休眠):而去BeforeWaiting(准备进入休眠) 时会调用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush()释放旧的池并创建新池,那么这两个方法来销毁要释放的对象,所以我们根本不需要担心Autorelease的内存管理问题。
RunLoop创建和释放自动释放池的时机:
在进入RunLoop时,创建一个AutoReleasePool。
在准备休眠的时候,释放旧的AutoReleasePool,再新建一个AutoReleasePool。
在RunLoop退出时,释放AutoReleasePool。
保存autoreleasePoolPage的双向链表只有一个么?也就是所有线程的autoreleasePoolPage都保存在一个链表中,还是每个线程保存一个自己的链表?并且链表头也就是链表的入口位置是保存在哪里呢?谁来控制呢?
一个线程有自己单独autoreleasePool链表,也有可能没有链表。链表的hotPage存储在TLS中,因为链表是双向的,通过hotpage就可以找到表头和表尾,不需要再单独存储表头。
需要自己手动添加autoreleasepool的情况
- 编写的不是基于UI框架的程序,例如命令行工具;
- 通过循环方式创建大量临时对象;
- 使用非Cocoa程序创建的子线程;
相关文章:

【iOS】autoreleasepool
来说一下最近在了解的autoreleasepool吧,我们可能平时书写过许多脑残代码,其有很多的缺陷但是我们可能当时学的比较浅就也不太了解,就像下面这样的: for (int i 0; i < 1000000; i) {NSNumber *num [NSNumber numberWithInt…...

0基础学习VR全景平台篇 第80篇:Insta360 影石如何直播推流
一、下载Insta360 Pro APP 1、手机进入Insta360官网Insta360 | Action Cameras | 360 Cameras | VR Cameras,页面往下滑动到Insta360 Pro2相机处,点击相机图片进入详情页。详情页继续下滑到到手机APP处,根据自己的手机系统选择对应的客户端进…...

大语言模型之三 InstructGPT训练过程
大语言模型 GPT历史文章中简介的大语言模型的的发展史,并且简要介绍了大语言模型的训练过程,本篇文章详细阐述训练的细节和相关的算法。 2020年后全球互联网大厂、AI创业公司研发了不少AI超大模型(百亿甚至千亿参数),…...
ChatGPT在自动化报告和数据分析中的应用如何?
ChatGPT在自动化报告和数据分析领域的应用正日益受到关注,这种强大的语言模型不仅可以加速报告生成的过程,还可以辅助数据分析,从而帮助企业和个人更高效地处理信息和做出决策。以下将详细探讨ChatGPT在自动化报告和数据分析中的应用。 **自…...

面试热题(三数之和)
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 输入&…...

在idea运行python文件
在idea运行python文件 如果在idea运行python文件而没有弹出run的选项,则点击File->Settings…->Plugins,在里面搜索python,如果没有显示则在Maketplace进行搜索, 接着Install,然后restart...
SQL - limit
介绍: limit 是限制的意思, 用于限制返回的查询结果的行数(可以通过limit指定查询多少行数据). MySQL支持limit语法, 用来完成分页. 用法: select 字段1, 字段2, ... from table_name limit offset, length;参数说明: offset: 起始行数, 从0开始计数, 如果省略, 则默认为…...

11. Redis基础知识
文章目录 一、概述二、数据类型STRINGLISTSETHASHZSET 三、数据结构字典跳跃表 四、使用场景计数器缓存查找表消息队列会话缓存分布式锁实现其它 五、Redis 与 Memcached数据类型数据持久化分布式内存管理机制 六、键的过期时间七、数据淘汰策略八、持久化RDB 持久化AOF 持久化…...

list模拟实现【引入反向迭代器】
文章目录 1.适配器1.1传统意义上的适配器1.2语言里的适配器1.3理解 2.list模拟实现【注意看反向迭代器】2.1 list_frame.h2.2riterator.h2.3list.h2.4 vector.h2.5test.cpp 3.反向迭代器的应用1.使用要求2.迭代器的分类 1.适配器 1.1传统意义上的适配器 1.2语言里的适配器 容…...
【华为OD机试】字符串变换最小字符串【2023 B卷|100分】
【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述: 给定一个字符串s,最多只能进行一次变换,返回变换后能得到的最小字符串(按照字典序进行比较)。 变换规则:交换字符串中任意两个不同位置的字符。 输入描述: 一串小写字母组成的字…...

ARTS 挑战打卡的第8天 ---volatile 关键字在MCU中的作用,四个实例讲解(Tips)
前言 (1)volatile 关键字作为嵌入式面试的常考点,很多人都不是很了解,或者说一知半解。 (2)可能有些人会说了,volatile 关键字不就是防止编译器优化的吗?有啥好详细讲解的࿱…...

第二课-一键安装SD-Stable Diffusion 教程
前言 看完这篇文章并跟着操作,就可以在本地开始 SD 绘图了。 理论上来说,这篇课程结束,想要画什么图都可以画了。 启动器介绍 SD 是开源的,可以在 github 上找到。但直接下载源码安装,非常费劲,而且因为国内外差异,就是我这样的秃头程序员也难以应对。 所以,我们改…...
Vue3 动态列 <el-table-column> 实现 formatter 的两种方法
文章目录 动态列实现动态列实现formatter第一种第二种方法 动态列实现 参考此篇文章 Vue3 动态列实现 动态列实现formatter 第一种 以此为例:传递该行的wxUserInfo字段(对象)中的nickName 假设该行 {prop: "wxUserInfo", label: …...
室温超导是什么?有哪些应用场景?
目录 一、应用场景:二、案例分析: 室温超导是指在室温下(即约 20C 至 30C)实现超导现象的材料。超导是指某些材料在低温下电阻为零的物理现象,室温超导材料是超导材料的一种。室温超导现象的发现和研究是超导领域的一个…...
Windows+VMware+Ubuntu+Anaconda+VMware Tools
Q1:Windows不支持***agent模拟器 A1:在VMware安装Ubuntu虚拟机 P1: 下载 VMware-workstation-full-15.5.6-16341506.exe 安装包(峰哥电脑软件) P2: 下载Ubuntu镜像 地址 ubuntu-18.04.6-desktop-amd64.iso P3:搭载镜…...
Xray配置文件详解
Xray配置文件详解 1.并发配置2.HTTP配置3.插件配置4.被动代理配置5.反连平台配置1.并发配置 在配置文件中可以用下面的配置改变漏洞探测的 worker 数量: parallel: 30 # 漏洞探测的 worker 数量,可以简单理解为同时有 30 个 POC 在运行这个值并非越大越好,因为高并发的情况…...
flink优化
1. 大状态调优 大状态调优:在我们的项目中,在做新老访客修复时,我们将每个mid的访问时间都存到了状态里面,在做回流用户数时,我们将每个用户的登录时间都存到了状态里面,导致了大状态问题,由于…...
docker: ERROR: Couldn‘t connect to Docker daemon at http+docker://localhost
环境: linuxt centos 7.x 如下图, 使用docker-compose时,提示错误 [explorebridge tinyproxy]$ docker-compose up ERROR: Couldnt connect to Docker daemon at httpdocker://localhost - is it running?If its at a non-standard locati…...

大模型在金融医疗、生命系统和物理仿真领域的创新应用探索
点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入! 在当今迅速发展的科技领域,大模型技术正日益成为金融医疗、生命系统和物理仿真等领域中的重要工具。2023年6月16日,AI TIME举办的青年科学家大模型专场活动邀请了国防科技大学理学院数学…...

tensorflow / tensorflow-gpu cuda cudNN tensorRT 安装,启用显卡加速
tensorflow / tensorflow-gpu cuda cudNN tensorRT 安装,启用显卡加速 说明 Tensorflow-GPU 已被移除。请安装 tensorflow 。 tensorflow 通过 Nvidia CUDA 支持 GPU 加速操作。 自 2019 年 9月发布 的 TensorFlow2.1 以来,tensorFlow 和 tensorflow-GPU 一直是同…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...