【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类似于半双工&#…...

前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法
前言 本文总结Web应用开发中文件上传、下载的方法,即从前端表单输入文件并封装表单数据,然后请求后端服务器的处理过程;从基础的JavaScript中XmlHttpRequest对象、Fetch API实现上传、下载进行说明,并给出了前端常用的axios库的请…...

STM32智能交通监测系统教程
目录 引言环境准备智能交通监测系统基础代码实现:实现智能交通监测系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景:交通监测与管理问题解决方案与优化收尾与总结 1. 引言 智能交通监测系统通…...

【利用Selenium+autoIt实现文件上传】
利用Selenium+autoIt实现文件上传 利用Selenium+autoIT实现文件上传autoIt脚本制作转换成exe文件java代码运行部分利用Selenium+autoIT实现文件上传 当你看到这篇文章时,证明你遇到了和我一样的难题。正常情况下我们利用selenium完全可以实现表单的提交和文件上传等操作。但当…...

python join
1、join函数 *.join(seq) 以*作为分隔符,将seq所有的元素合并为一个新的字符串 seq ABDWDPO new_seq list(.joint(seq)) # ABDWDPO #[A, B, D, W, D, P, O]...

cython加速python代码
python这个语言在使用的层面上看几乎没有缺点,简单易学,语法简单,唯一的弱点就是慢, 当然了万能的python社区是给了解决方法的,那就是cython 使用Cython可以显著提升Python代码的执行效率,特别是在涉及到数…...

React@16.x(60)Redux@4.x(9)- 实现 applyMiddleware
目录 1,applyMiddleware 原理2,实现2.1,applyMiddleware2.1.1,compose 方法2.1.2,applyMiddleware 2.2,修改 createStore 接上篇文章:Redux中间件介绍。 1,applyMiddleware 原理 R…...

level 6 day1 Linux网络编程之网络基础
v1 网络的历史和分层 TCP 是可靠传输,IP协议是不可靠传输 网络的体系结构 网络分层的思想: OSI体系结构 两层交换机是指数据链路层的交换 三层交换是指网络层这边的交换 四层模型 蓝色的字 是由手机发给PC机,由传输层来决定应该交给哪一…...

PostgreSQL UPDATE 命令
PostgreSQL UPDATE 命令 PostgreSQL 是一种功能强大的开源对象关系型数据库管理系统(ORDBMS),它使用并扩展了SQL语言。在处理数据库时,我们经常需要更新现有的记录。在PostgreSQL中,UPDATE命令用于修改表中的现有记录…...

什么? CSS 将支持 if() 函数了?
CSS Working Group 简称 CSSWG, 在近期的会议中决定将 if() 添加到 CSS Values Module Level 5 中。 详情可见:css-meeting-bot 、[css-values] if() function 当我看到这个消息的时候,心中直呼这很逆天了,我们知道像 less 这些 css 这些预…...

function calling实现调用理杏仁api获取数据
LLM是不存在真正逻辑的且并不是知晓万事万物的(至少目前是这样)在很多更垂直的环境下LLM并不能很好的赋能。 function calling的实现使LLM可以对接真正的世界以及真正有逻辑的系统,这将很大程度上改变LLM的可用范围(当然安全问题依…...