【iOS】——ARC源码探究
一、ARC介绍
ARC的全称Auto Reference Counting. 也就是自动引用计数。使用MRC时开发者不得不花大量的时间在内存管理上,并且容易出现内存泄漏或者release一个已被释放的对象,导致crash。后来,Apple引入了ARC。使用ARC,开发者不再需要手动的retain/release/autorelease. 编译器会自动插入对应的代码,再结合Objective-C的runtime,实现自动引用计数。
在Objective C中,有三种类型是ARC适用的:
- block
- OC的对象,id, Class, NSError*等
- 由__attribute__((NSObject))标记的类型。
像double *,CFStringRef等不是ARC适用的,仍然需要手动管理内存。
ARC 的工作原理大致是这样:当我们编译源码的时候,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,来添加相应的引用计数操作代码。所以,ARC 是工作在编译期的一种技术方案,这样的好处是:
1、编译之后,ARC 与非 ARC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。
2、相对于垃圾回收这类内存管理方案,ARC 不会带来运行时的额外开销,所以对于应用的运行效率不会有影响。相反,由于 ARC 能够深度分析每一个对象的生命周期,它能够做到比人工管理引用计数更加高效。例如在一个函数中,对一个对象刚开始有一个引用计数 +1的操作,之后又紧接着有一个 -1 的操作,那么编译器就可以把这两个操作都优化掉。
ARC 也附带有运行期的一些机制来使 ARC 能够更好的工作,主要是指 weak 关键字。weak 变量能够在引用计数为 0 时被自动设置成 nil,显然是有运行时逻辑在工作的。
ARC背后的引用计数主要依赖于这三个方法:
retain 增加引用计数
release 降低引用计数,引用计数为0的时候,释放对象。
autorelease 在当前的auto release pool结束后,降低引用计数。
二、源码探究
retain函数
inline id
objc_object::retain()
{assert(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {return rootRetain();}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
首先进行通过assert函数断言检查确保当前对象不是taggedPointer。因为标记指针的内存管理方式与普通对象不同。
接着检查对象的类是否具有定制的retain/release行为。如果没有,那么调用rootRetain()函数,这是一个更快的内部函数,直接更新引用计数,避免了消息发送的开销。
如果类有定制的retain/release行为,那么使用objc_msgSend发送retain消息到对象。
taggedPointer即标签指针,如果存储的是某些比较小的对象,相比于以往指向在堆上的对象的指针,taggedPointer可以直接在指针中编码数据,从而提高性能。
接着来看rootRetain函数
rootRetain()函数
ALWAYS_INLINE id
objc_object::rootRetain()
{return rootRetain(false, false); //传递false和false参数。
}
这里会调用重载的rootRetain函数并传入两个false参数
ALWAYS_INLINE bool
objc_object::rootTryRetain()
{return rootRetain(true, false) ? true : false; // 调用rootRetain,参数为true和false。
}
这里是尝试性调用重载的rootRetain()并传入ture和false两个参数,返回true如果成功增加引用计数,否则返回false。
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{// Inline函数,用于核心的retain操作,接受两个bool参数:// tryRetain决定是否为尝试性retain;// handleOverflow决定是否处理引用计数溢出。if (isTaggedPointer()) return (id)this; // 如果当前对象是标记指针,直接返回this,因为标记指针有自己的内存管理机制。bool sideTableLocked = false; // 初始化侧边表锁定状态为未锁定。bool transcribeToSideTable = false; // 初始化是否需要将数据转录到侧边表为否。isa_t oldisa; // 定义oldisa变量,用于存储对象的原始ISA信息。isa_t newisa; // 定义newisa变量,用于存储新的ISA信息。do {transcribeToSideTable = false; // 每次循环前,重置是否需要转录到侧边表的状态。oldisa = LoadExclusive(&isa.bits); // 使用LoadExclusive获取ISA的bits字段,保证原子操作。// LoadExclusive允许我们独占地读取内存,这样在我们读取之后和写入之前,其他线程不能修改这个内存位置。newisa = oldisa; // 复制旧的ISA信息到新ISA中,准备修改。if (slowpath(!newisa.nonpointer)) { // 检查ISA的nonpointer标志,判断是否有侧边表。// slowpath宏用于指示编译器此条件在正常情况下很少为真,用于优化。ClearExclusive(&isa.bits); // 清除独占状态,因为接下来要处理侧边表。if (!tryRetain && sideTableLocked) sidetable_unlock(); // 如果不是尝试性retain且侧边表已锁定,解锁侧边表。if (tryRetain) // 尝试性retain操作,调用侧边表的尝试性retain方法。return sidetable_tryRetain() ? (id)this : nil;else // 非尝试性retain操作,调用侧边表的常规retain方法。return sidetable_retain();}// 以上处理有侧边表的情况,接下来处理没有侧边表的情况。// 不检查newisa.fast_rr,因为我们已经调用了任何RR覆盖。// fast_rr是用于快速引用计数的字段,当有侧边表时,fast_rr字段可能无效。if (slowpath(tryRetain && newisa.deallocating)) { // 如果是尝试性retain且对象正在dealloc中。ClearExclusive(&isa.bits); // 清除独占状态,因为对象正在dealloc中。if (!tryRetain && sideTableLocked) sidetable_unlock(); // 如果不是尝试性retain且侧边表已锁定,解锁侧边表。return nil; // 返回nil,因为尝试性retain失败。}// 上面的条件判断确保我们不会在对象dealloc时尝试增加引用计数。uintptr_t carry; // 定义carry变量,用于保存进位信息。// 使用addc函数原子地增加引用计数,同时检查是否溢出。newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);if (slowpath(carry)) { // 如果addc操作产生了进位,即发生了溢出。if (!handleOverflow) { // 如果不处理溢出。ClearExclusive(&isa.bits); // 清除独占状态。// 下面调用rootRetain_overflow函数处理溢出情况,参数为tryRetain。return rootRetain_overflow(tryRetain);}// 如果需要处理溢出,准备将一半的引用计数转移到侧边表。if (!tryRetain && !sideTableLocked) sidetable_lock(); // 如果不是尝试性retain且侧边表未锁定,锁定侧边表。sideTableLocked = true; // 设置侧边表锁定状态为已锁定。transcribeToSideTable = true; // 设置需要转录到侧边表的状态为是。newisa.extra_rc = RC_HALF; // 设置extra_rc字段为RC_HALF,表示一半的引用计数。newisa.has_sidetable_rc = true; // 设置has_sidetable_rc标志,表示侧边表中有引用计数。}} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));// StoreExclusive尝试原子地更新ISA的bits字段,如果更新失败(其他线程修改了bits),则继续循环。if (slowpath(transcribeToSideTable)) { // 如果需要将数据转录到侧边表。// 转录额外的一半引用计数到侧边表,无需锁定。sidetable_addExtraRC_nolock(RC_HALF);}if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); // 如果不是尝试性retain且侧边表已锁定,解锁侧边表。// 此处的sidetable_unlock调用确保我们释放了侧边表的锁,除非正在进行尝试性retain。return (id)this; // 成功增加引用计数后,返回this指针。
}
这段代码很长大致可分为四个部分:对象是否是标记指针、对象是否有侧边表、引用计数是否会溢出、是否是尝试性retain。
独占访问是指在某一时刻只有一个线程能够访问特定的资源或内存位置。这是为了避免并发访问导致的数据竞争(和不一致状态。在并发编程中,独占访问通常通过锁或原子操作来实现。
LoadExclusive函数和ClearExclusive函数就是通过原子操作来进行与独占访问有关的操作
当对象的引用计数超过了一定的阈值,通常是因为被多个引用持有,runtime会将一部分引用计数信息移动到侧边表中,以避免在对象的
isa字段中存储过大的数值,从而节省空间和提高效率。
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
struct SideTable {spinlock_t slock;RefcountMap refcnts;weak_table_t weak_table;//省略其他实现...
};
这个数据结构就是存储了一个自旋锁,一个引用计数map。
在实现上,这个引用计数的map以对象的地址作为key,引用计数作为value。这意味着每个对象在侧边表中最多只有一个条目,对象的地址提供了唯一性,便于查找和更新。
release函数
inline void
objc_object::release()
{assert(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {rootRelease();return;}((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
首先进行通过assert函数断言检查确保当前对象不是taggedPointer。
接着检查对象的类是否具有定制的retain/release行为。如果没有,那么调用rootRelease()函数。
如果类有定制的retain/release行为,那么使用objc_msgSend发送release消息到对象。
rootRelease函数
ALWAYS_INLINE bool
objc_object::rootRelease()
{return rootRelease(true, false); // 调用rootRelease的重载版本,参数为true和false。
}
ALWAYS_INLINE bool
objc_object::rootReleaseShouldDealloc()
{return rootRelease(false, false); // 调用rootRelease的重载版本,参数为false和false。
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{if (isTaggedPointer()) return false; // 如果是标记指针,直接返回false,因为标记指针有自己的内存管理。bool sideTableLocked = false; // 初始化侧边表锁定状态为未锁定。isa_t oldisa; // 存储对象的原始ISA信息。isa_t newisa; // 存储新的ISA信息,用于修改。retry:do {oldisa = LoadExclusive(&isa.bits); // 使用LoadExclusive获取ISA的bits字段,开始独占访问。newisa = oldisa; // 复制旧的ISA信息到新ISA中,准备修改。if (slowpath(!newisa.nonpointer)) { // 如果有侧边表。ClearExclusive(&isa.bits); // 清除独占状态。if (sideTableLocked) sidetable_unlock(); // 如果侧边表已锁定,解锁。return sidetable_release(performDealloc); // 调用侧边表的release方法。}// 以下处理没有侧边表的情况。uintptr_t carry;newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // 原子递减引用计数,并检查是否产生借位。if (slowpath(carry)) { // 如果产生了借位,即发生了下溢。// 不ClearExclusive,保留独占访问状态。goto underflow; // 跳转到underflow标签,处理下溢情况。}} while (slowpath(!StoreReleaseExclusive(&isa.bits,oldisa.bits, newisa.bits))); // 使用StoreReleaseExclusive尝试更新ISA的bits字段,如果更新失败,继续循环。if (slowpath(sideTableLocked)) sidetable_unlock(); // 如果侧边表已锁定,解锁。return false; // 如果没有下溢,直接返回false。underflow:// 发生了下溢:从侧边表借用引用计数或析构对象。// abandon newisa以撤销递减操作。newisa = oldisa;if (slowpath(newisa.has_sidetable_rc)) { // 如果有侧边表引用计数。if (!handleUnderflow) { // 如果不处理下溢。ClearExclusive(&isa.bits); // 清除独占状态。return rootRelease_underflow(performDealloc); // 调用处理下溢的函数。}// 从侧边表转移引用计数到内联存储。if (!sideTableLocked) { // 如果侧边表未锁定。ClearExclusive(&isa.bits); // 清除独占状态。sidetable_lock(); // 锁定侧边表。sideTableLocked = true; // 设置侧边表锁定状态为已锁定。goto retry; // 重新开始循环,防止竞态条件。}// 尝试从侧边表移除一些引用计数。size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); // 从侧边表借入一半的引用计数。if (borrowed > 0) { // 如果成功借入。// 侧边表引用计数减少。// 尝试将它们添加到内联计数。newisa.extra_rc = borrowed - 1; // 重新递减引用计数。bool stored = StoreReleaseExclusive(&isa.bits,oldisa.bits, newisa.bits); // 尝试更新ISA的bits字段。if (!stored) { // 如果更新失败。// 内联更新失败。// 将借来的引用计数放回侧边表。sidetable_addExtraRC_nolock(borrowed); // 将引用计数放回侧边表。goto retry; // 重新开始循环。}// 递减成功后从侧边表借用。// 这个递减不可能是析构递减 - 侧边表锁和has_sidetable_rc标志确保如果所有其他线程都试图在我们工作时-release,最后一个会阻塞。sidetable_unlock(); // 解锁侧边表。return false; // 返回false。}else {// 侧边表为空。}}// 真正析构对象。if (slowpath(newisa.deallocating)) { // 如果对象正在析构中。ClearExclusive(&isa.bits); // 清除独占状态。if (sideTableLocked) sidetable_unlock(); // 如果侧边表已锁定,解锁。return overrelease_error(); // 返回overrelease错误。}newisa.deallocating = true; // 设置对象为正在析构中。if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; // 尝试更新ISA的bits字段,如果失败,重新开始循环。if (slowpath(sideTableLocked)) sidetable_unlock(); // 如果侧边表已锁定,解锁。__sync_synchronize(); // 确保所有线程可见状态的更新。if (performDealloc) { // 如果需要执行析构。((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); // 发送-dealloc消息。}return true; // 返回true,表示对象应该被析构。
}
这段代码大致可分为四个部分:对象是否是标记指针、对象是否有侧边表、引用计数是否会下溢、通过侧边表或析构处理下溢。
当引用计数降到0时,对象会被标记为正在析构中,并最终调用dealloc方法进行析构。如果对象有侧边表,它会尝试从侧边表借用引用计数以避免下溢。如果侧边表为空或对象已经在析构中,它会处理overrelease错误或执行析构操作。
ARC规则下autorelease
autorelease函数的作用是把对象放到autorelease pool中,到pool drain的时候,会释放池中的对象。
在ARC规则下,alloc/init/new/copy/mutableCopy开头的方法返回的对象不是autorelease对象
新建一个自定义类
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface CustomObject : NSObject
+ (instancetype)object;
- (void)dealloc;
@endNS_ASSUME_NONNULL_END
#import "CustomObject.h"@implementation CustomObject
//这个方法返回autorelease对象
+ (instancetype)object{return [[CustomObject alloc] init];
}- (void)dealloc{NSLog(@"CustomObject Dealloc");
}
@end
下面确定object方法返回的对象是不是autorelease的
#import <Foundation/Foundation.h>
#import "CustomObject.h"
int main(int argc, const char * argv[]) {__weak CustomObject * weakRef;{CustomObject * temp = [CustomObject object];weakRef = temp;}NSLog(@"%@",weakRef);
}

这是因为[CustomObject object]返回的不是一个autorelease对象,在作用域(大括号)结束后,并不会立刻被释放,所以在NSLog处还能看到对象的地址。
如果把[CustomObject object]替换成[[CustomObject alloc] init],会发现作用域结束后立刻释放。

假如我们用autorelease包裹后:
__weak CustomObject * weakRef;@autoreleasepool {CustomObject * temp = [CustomObject object];weakRef = temp;}NSLog(@"%@",weakRef);

会看到dealloc方法先调用。
放到自动释放池的对象是在超出自动释放池作用域后立即释放的。事实上在iOS 程序启动之后,主线程会启动一个Runloop,这个Runloop在每一次循环是被自动释放池包裹的,在合适的时候对池子进行清空。
对于Cocoa框架来说,提供了两种方式来把对象显式的放入AutoReleasePool.
- NSAutoreleasePool(只能在MRC下使用)
@autoreleasepool {}代码块(ARC和MRC下均可以使用)
下面从源码入手分析autuorelease
autorelease函数
inline id
objc_object::autorelease()
{if (isTaggedPointer()) return (id)this;if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease();return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}
首先进行通过assert函数断言检查确保当前对象不是taggedPointer。
接着检查对象的类是否具有定制的retain/release行为。如果没有,那么调用rootAutorelease()函数。
如果类有定制的retain/release行为,那么使用objc_msgSend发送autorelease()消息到对象。
inline id
objc_object::rootAutorelease()
{if (isTaggedPointer()) return (id)this;if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;return rootAutorelease2();
}
首先,函数检查对象是否是taggedPointer。如果是taggedPointer,那么 autorelease 操作不需要做任何事情,因为taggedPointer的对象通常较小且生命周期管理是内置的。在这种情况下,函数简单地返回 this 指针。
接下来,函数调用 prepareOptimizedReturn(ReturnAtPlus1) 函数。这个函数检查是否可以在当前调用栈深度 + 1 的地方直接返回,从而跳过自动释放池的操作。这通常在某些优化场景下发生,比如当对象在当前作用域结束时就会被销毁,那么就没有必要将其放入自动释放池中等待稍后的释放。如果可以优化,函数再次直接返回 this 指针。
如果上述两种情况都无法应用,那么函数会调用 rootAutorelease2() 函数。rootAutorelease2() 是 autorelease 操作的核心实现,负责将对象放入当前线程的自动释放池中,以便在适当的时候(通常是作用域结束时)释放对象。
rootAutorelease2函数
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{assert(!isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}
首先判断对象是否为taggedPointer,如果不是就调用AutoreleasePoolPage::autorelease这个方法把该对象作为参数。
AutoreleasePoolPage::autorelease函数
public: static inline id autorelease(id obj)
{// 断言确保传入的对象非空。assert(obj);// 确保对象不是标记指针,标记指针不需要自动释放。assert(!obj->isTaggedPointer());// 调用快速自动释放函数,尝试将对象加入当前热自动释放池页面。id *dest __unused = autoreleaseFast(obj);// 断言检查,确保dest要么是空,要么是空池占位符,要么指向obj。// 这里用于验证autoreleaseFast是否正确执行。assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);// 返回原始对象,autorelease操作完成后对象仍然可用。return obj;
}// 快速自动释放实现,尝试将对象加入到当前线程的热自动释放池页面。
static inline id *autoreleaseFast(id obj)
{// 获取当前线程的热自动释放池页面。AutoreleasePoolPage *page = hotPage();// 如果页面存在并且没有满,则尝试将对象加入到页面中。if (page && !page->full()) {return page->add(obj);}// 如果页面存在但已满,调用特殊处理函数处理满页情况。else if (page) {return autoreleaseFullPage(obj, page);}// 如果页面不存在,调用无页面自动释放处理函数。else {return autoreleaseNoPage(obj);}
}// 自动释放池页面的add方法,用于向页面中添加一个对象。
id *AutoreleasePoolPage::add(id obj)
{// 确保页面没有满。assert(!full());// 临时解除保护,允许修改页面。unprotect();// 记录下一个要写入的位置,这里使用next指针。// 注意返回的是next-1的地址,但通过直接返回next避免了指针偏移。id *ret = next;// 将对象写入到next指向的位置,然后递增next。*next++ = obj;// 重新保护页面,防止其他线程修改。protect();// 返回对象在页面中的地址,用于验证。return ret;
}
autoreleaseFast函数尝试将对象加入到当前线程的“热”自动释放池页面。如果页面存在并且没有满,对象将被直接加入到页面中。如果页面已满,或者页面不存在,将分别调用autoreleaseFullPage和autoreleaseNoPage函数分别进行满页和无页的处理。
AutoreleasePoolPage::add方法负责将对象实际添加到页面中。它先解除页面保护,将对象写入到指定位置,然后递增页面的写入指针,最后重新保护页面。这里的保护和解除保护操作是为了确保在多线程环境下页面数据的一致性和安全性。
autorelease方法会把对象存储到AutoreleasePoolPage的双向链表里。等到autorelease pool被drain的时候,把链表内存储的对象删除。所以AutoreleasePoolPage就是自动释放池的内部实现。
一个 poolPage 的大小 是 4096 字节。其中56 bit 用来存储其成员变量,剩下的存储加入到自动释放池中的对象。原因在于在现代操作系统中,内存通常以页面为单位进行分配和管理,而页面的大小通常为4096字节。因此,将自动释放池页面的大小设定为4096字节可以很好地与操作系统内存管理机制对齐
相关文章:
【iOS】——ARC源码探究
一、ARC介绍 ARC的全称Auto Reference Counting. 也就是自动引用计数。使用MRC时开发者不得不花大量的时间在内存管理上,并且容易出现内存泄漏或者release一个已被释放的对象,导致crash。后来,Apple引入了ARC。使用ARC,开发者不再…...
ubuntu服务器安装labelimg报错记录
文章目录 报错提示查看报错原因安装报错 报错提示 按照步骤安装完labelimg后,在终端输入labelImg后,报错: (labelimg) rootinteractive59753:~# labelImg ………………Got keys from plugin meta data ("xcb") QFactoryLoader::Q…...
Transformer中Decoder的计算过程及各部分维度变化
在Transformer模型中,解码器的计算过程涉及多个步骤,主要包括自注意力机制、编码器-解码器注意力和前馈神经网络。以下是解码器的详细计算过程及数据维度变化: 1. 输入嵌入和位置编码 解码器的输入首先经过嵌入层和位置编码: I…...
QT实现滑动页面组件,多页面动态切换
这篇文章主要介绍了Qt实现界面滑动切换效果,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下。 一、简述 一个基于Qt的动态滑动页面组件。 二、 设计思路 1、自定义StackWidget类,继承自QWidget,实现一个堆叠…...
使用Python-docx库创建Word文档
哈喽,大家好,我是木头左! 简介 Python-docx是一个用于处理Microsoft Word文档的Python库。它允许用户创建、修改和提取Word文档的内容。在本文中,将详细介绍如何使用Python-docx库创建一个新的Word文档。 安装Python-docx库 要使用Python-docx库,首先需要安装它。可以使…...
C# 设计一个可变长度的数据通信协议编码和解码代码。
设计一个可变长度的数据通信协议编码和解码代码。 要有本机ID字段,远端设备ID字段,指令类型字段,数据体字段,校验字段。其中一个要求是,每次固定收发八个字节,单个数据帧超过八个字节需要分包收发。对接收的…...
【MATLAB库函数系列】MATLAB库函数pwelch之功率谱估计的详解及实现
功率谱估计 由于实际信号通常是非定常的,我们只能假设其在10ms的时间段内是定常的,并在此基础上对短的定常信号求PSD或者能谱。 窗函数的作用就是将原始的信号分割成一段段可以计算PSD和能谱的短信号,并且保证了周期结构的连续性、避免了频谱泄漏。不同的窗函数具有不同的…...
科技出海|百分点科技智慧政务解决方案亮相非洲展会
近日,华为非洲全联接大会在南非约翰内斯堡举办,吸引政府官员行业专家、思想领袖、生态伙伴等2,000多人参会,百分点科技作为华为云生态合作伙伴,重点展示了智慧政务解决方案,发表《Enable a Smarter Government with Da…...
Prometheus 云原生 - Prometheus 数据模型、Metrics 指标类型、Exporter 相关
目录 开始 Prometheus 数据类型 简单理解 时序样本 格式 和 命名要求 Metrics 指标类型 Counter 计数器 Gauge Histogram Summary Exporter 相关 概述 Exporter 类型 Exporter 规范 开始 Prometheus 数据类型 简单理解 a)安装好 Prometheus 后会暴露…...
Qt窗口程序整理汇总
到今日为止,通过一个个案例的实验,逐步熟悉了 Qt6下 窗体界面开发的,将走过的路,再次汇总整理。 Qt Splash样式的登录窗https://blog.csdn.net/castlooo/article/details/140462768 Qt实现MDI应用程序https://blog.csdn.net/cast…...
简单实现一个本地ChatGPT web服务(langchain框架)
简单实现一个本地ChatGPT 服务,用到langchain框架,fastapi,并且本地安装了ollama。 依赖安装: pip install langchain pip install langchain_community pip install langchain-cli # langchain v0.2 2024年5月最新版本 pip install bs4 pi…...
Elasticsearch-多边形范围查询(8.x)
目录 一、字段设计 二、数据录入 三、查询语句 四、Java代码实现 开发版本详见:Elasticsearch-经纬度查询(8.x-半径查询)_es经纬度范围查询-CSDN博客 一、字段设计 PUT /aoi_points {"mappings": {"properties": {"location": {…...
Kotlin Misk Web框架
Kotlin Misk Web框架 1 Misk 框架介绍2 Misk/SpringBoot 框架对比3 Misk 添加依赖/配置3.1 build.gradle.kts3.2 settings.gradle.kts3.3 gradle.properties 4 Misk 请求接口5 Misk 程序模块6 Misk 主服务类7 Misk 测试结果 1 Misk 框架介绍 Misk 是由 Square 公司开发的一个开…...
【设计模式之美】【建造型】工厂模式:通过面向接口编程思路,串起业务流程
文章目录 一. 简单工厂(Simple Factory)第一种简单工厂:面向接口编程与工厂类:划分功能职责第二种:单例简单工厂:节省内存和对象创建的时间 二. 工厂方法(Factory Method)࿱…...
AI算法19-偏最小二乘法回归算法Partial Least Squares Regression | PLS
偏最小二乘法回归算法简介 算法概述 偏最小二乘法模型可分为偏最小二乘回归模型和偏最小二乘路径模型。其中偏最小二乘回归模型是一种新型的多元统计方法,它集中了主成分分析、典型相关分析和线性回归的特点,特别在解决回归中的共线性问题具有无可比拟…...
live555关于RTSP协议交互流程
RTP在和h264 RTP在和h265 RTP载荷AAC live555关于RTSP协议交互流程 live555的核心数据结构值之闭环双向链表 live555 rtsp服务器实战之createNewStreamSource 概要 rtsp在交互的过程中用到很多协议:tcp,udp,rtp,rtcp,sdp等协议;该篇文章主要分析在live555中这些…...
Centos7 安装私有 Gitlab
在 CentOS 7上,下面的命令也会在系统防火墙中打开 HTTP、HTTPS 和 SSH 访问。这是一个可选步骤,如果您打算仅从本地网络访问极狐GitLab,则可以跳过它。 sudo yum install -y curl policycoreutils-python openssh-server perl sudo systemct…...
浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用(AI智能体)
浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用 ygluu 卢益贵 关键词:UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂:AI(AIGC/ChatGPT)与流程式游戏开发》讨论之后就…...
第T5周:使用TensorFlow实现运动鞋品牌识别
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 文章目录 一、前期工作1.设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据 二、数据预处理1、加载数据2、数据可视化3、再…...
网络编程学习之tcp
按下*(星号)可以搜索当前光标下的单词。 Tcp编程的过程 打开网络设备 Bind:给服务地址把ip号和端口号连接进去 Tcp是有状态的 Listen是进入监听状态,看有没有客户端来连接服务器 Tcp比udp消耗过多资源 Upd类似于半双工&#…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
深度解析云存储:概念、架构与应用实践
在数据爆炸式增长的时代,传统本地存储因容量限制、管理复杂等问题,已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性,成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理,云存储正重塑数据存储与…...
