PHP7垃圾回收算法

news2025/2/24 23:42:53

前提

本文为了梳理PHP GC工作流程,所以从引用计数、部分标记清除算法做引子,然后介绍PHP GC工作流程,最后介绍性能更高的GC算法

引用计数

概述

引用计数算法中引入了一个概念计数器。计数器代表对象被引用的次数

基本原理

为了记录一个对象有没有被其他对象引用,我们可以在每个对象的头上引用一个叫“计数器”的东西,用来记录有多少其他对象引用了它

这个计数器的值的变化都是由mutator引起的。例如:

public class MyObject {
    public Object ref = null;
    public static void main(String[] args) {
        MyObject objA = new MyObject();
        MyObject objB = new MyObject();
        objA.ref = objB;
    }
}

在这里插入图片描述
上图为例,而objA 做为一个局部变量引用了它,所以它的引用计数就是1,objB这个局部变量引用了它,然后objA又引用了它一次,所以它的引用计数就是2

mutator在运行中还会不断地修改对象之间的引用关系,我们知道,这种引用关系的变化都是发生在赋值的时候。例如,接上文的例子,我们再执行这样一行代码

objA = null;

那么从objA到objB的引用就消失了,也就是上图中,那个从A的ref指向B的箭头就消失了

运行原理

update_ptr(ptr, obj){
    inc_ref_cnt(obj)  // 计数器+
    dec_ref_cnt(*ptr) // 计数器-
    *ptr = obj       // 重新指向 obj
}

inc_ref_cnt(obj){
    obj.ref_cnt++
}

dec_ref_cnt(*ptr){
    obj.ref_cnt--
    if (obj.ref_cnt == 0)
        for(child : children(obj)) // 当自己被清除时,自己所引用的子节点的计数器必须减一。进行递归操作。
            dec_ref_cnt(*child)

        // 然后通过reclaim()函数,将obj连接到空闲链表上面
        reclaim(obj)
}

把 obj 赋值给 ptr 这个指针之前,我们可以先改变一下这两个对象的引用计数

在一次赋值中,要先把老的对象的引用计数减一,把新的对象的引用计数加一

如果某个对象的引用计数为0,就把这个对象回收掉,然后把这个对象所引用的所有对象的引用计数减1。

为什么要先inc_ref_cnt(obj)然后再dec_ref_cnt(*ptr)呢?

  • 如果按照先dec_ref_cnt()后inc_ref_cnt()函数的顺序调用ptr和 obj又是同一对象的话执行dec_ref_cnt(ptr)时ptr的计数器的值就有可能变为0而被回收

  • 再想执行inc_ref_cnt(obj)时obj早就被回收了,可能会引发重大的BUG

优点

可即回收的垃圾: 每个对象在被引用次数为0的时候,可以立即知道

没有暂停时间: 对象的回收根本不需要另外的GC线程专门去做,业务线程自己就搞定了,不需要STW

缺点

计数器的增减处理频繁

循环引用无法回收: objA引用了objB,objB也引用了objA, 两个对象的引用计数就都是1。这种情况下,这两个对象是不能被回收的

在这里插入图片描述

部分标记清除算法

概述

为了解决循环依赖的问题

部分标记清除算法通过把对象涂成4种不同的颜色进行管理

四色标记流程

前提

黑(BLACK): 不是垃圾对象(对象产生的初始颜色)

白(WHITE): 垃圾对象

灰(GRAY): 搜索完毕的对象

阴影(HATCH): 可能是循环垃圾

在这里插入图片描述
循环引用的对象群是ABC和DE,其中A和D由根引用,此外C和E引用F

所有对象的颜色现在还是初始的黑色

dec_ref_cnt() 函数

dec_ref_cnt(obj){
    obj.ref_cnt--
    if(obj.ref_cnt == 0 )
        delete(obj)
    else if(obj.color != HATCH)
        obj.color = HATCH
        enqueue(obj, $hatch_queue)
}

算法在对obj的计数器进行减量操作后,检查obj的颜色。当obj的颜色不是阴影的时候,算法会将其涂上阴影并追加到队列中

在这里插入图片描述
由根到A的引用被删除了,指向A的指针被追加到队列($hatch_queue)之中。A被涂上了阴影

new_obj()函数

new_obj(size){
    obj = pickup_chunk(size)                // 创建对象
    if(obj != NULL)
        obj.color = BLACK
        obj.ref_cnt = 1
        return obj
    else if(is_empty($hatch_queue) == FALSE) // 如果$hatch_queue不为空
        scan_hatch_queue()                  // 标记清除回收垃圾
        return new_obj(size)                // 重新分配
    else
        allocation_fail()
}

当分配无法顺利进行的时候,程序会调查队列是否为空

当队列不为空时,程序会通过scan_hatch_ queue() 函数搜索队列,分配分块

scan_hatch_queue() 函数执行完毕后,程序会递归地 调用 new_obj() 函数再次尝试分配。 如果队列为空,则分配将会失败

scan_hatch_queue(){
    // 对象出队
    obj = dequeue($hatch_queue)
    // 判断对象是不是阴影
    if(obj.color == HATCH)
        paint_gray(obj)     // 查找对象进行计数器的减量操作
        scan_gray(obj)      // 查找灰色对象,按条件变换白色对象
        collect_white(obj)   // 回收白色对象
    else if(is_empty($hatch_queue) == FALSE)
        scan_hatch_queue()
}

paint_gray()函数

// 查找对象进行计数器的减量操作
paint_gray(obj){
    if(obj.color == (BLACK|HATCH))
        obj.color = GRAY // 搜索完毕的颜色
        for(child :children(obj))
            (*child).ref_cnt--
            paint_gray(*child)
}

在这里插入图片描述

scan_gray()函数

// 从第一个灰色的对象开始找,找到后如果计数器为0就将颜色改为白色,计数器大于0就会执行paint_black(). 然后递归子节点
scan_gray(obj){
    if(obj.color == GRAY)
        if(obj.ref_cnt > 0 )
            paint_black(obj)
        else
            obj.color = WHITE
            for(child :children(obj))
                scan_gray(child)
}

// 从那些可能被涂成了灰色的有循环引用的对象群中,找出不是垃圾的对象,并将其归回原处
paint_black(obj){
    obj.color = BLACK
    for(child :children(obj))
        (*child).ref_cnt++
        if((*child).color != BLACK)
            paint_black(child)
}

在这里插入图片描述
形成了循环垃圾的对象 A、B、C 被涂成了白色,而有循环引用的非垃圾对象 D、 E、F 被涂成了黑色

collect_white()函数

collect_white(obj){
    if(obj.color == WHITE)
        obj.color = BLACK
        for(child :children(obj))
            collect_white(*child)
        reclaim(obj)
}

在这里插入图片描述

部分标记清除算法的局限性

这个算法不仅付出很大成本搜索对象,还需要查找三次对象,分别是mark_gray()、sacn_gray()、collect_white()

这很大程度的增加了内存管理所花费的时间。还因此对引用计数法最大暂停时间短的优势造成的破坏性的影响

PHP7 GC

对象颜色流转

在这里插入图片描述
目前垃圾回收只针对array、object两种类型

GC算法简述

遍历roots链表, 把当前元素标为灰色(zend_refcounted_h.gc_info置为GC_GREY),然后对当前元素的成员进行深度优先遍历,把成员的refcount减1,并且也标为灰色。(gc_mark_roots())

遍历roots链表中所有灰色元素及其子元素,如果发现其引用计数仍旧大于0,说明这个元素还在其他地方使用,那么将其颜色重新标记会黑色,并将其引用计数加1(在第一步有减1操作)。如果发现其引用计数为0,则将其标记为白色。(gc_scan_roots())

遍历roots链表,将黑色的元素从roots移除。然后对roots中颜色为白色的元素进行深度优先遍历,将其引用计数加1(在第一步有减1操作),同时将颜色为白色的子元素也加入roots链表。最后然后将roots链表移动到待释放的列表to_free中。(gc_collect_roots())

释放to_free列表的元素

zend_refcounted_h 结构体

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* reference counter 32-bit */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3 (
                zend_uchar    type,     // 当前元素的类型,同zval的u1.v.type
                zend_uchar    flags,    // 标记数据类型,可以是字符串类型或数组类型等

                // 后面的两个字节标记当前元素的颜色和垃圾回收池中的位置
                // 其中高地址的两位用来标记颜色,低地址的14位用于记录位置
                uint16_t      gc_info
            )  // keeps GC root number (or 0) and color
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;
  • type: 当前元素的类型,同zval的u1.v.type
  • flags: 标记数据类型,可以是字符串类型或数组类型等
  • gc_info: 后面的两个字节标记当前元素的颜色和垃圾回收池中的位置,其中高地址的两位用来标记颜色,低地址的14位用于记录位置
	 define GC_COLOR  0xc000
   	 define GC_BLACK  0x0000(黑色: 不是垃圾对象)
     define GC_WHITE  0x8000(白色: 垃圾对象)
   	 define GC_GREY   0x4000(灰色: 将被标记为白色)
     define GC_PURPLE 0xc000(紫色: 加入的垃圾收集器)

垃圾收集器结构体 - zend_gc_globals

typedef struct _zend_gc_globals {
    zend_bool         gc_enabled;   //是否启用gc
    zend_bool         gc_active;    //是否在垃圾检查过程中
    zend_bool         gc_full;      //缓存区是否已满

    gc_root_buffer   *buf;              //启动时分配的用于保存可能垃圾的缓存区
    gc_root_buffer    roots;            //指向buf中最新加入的一个可能垃圾
    gc_root_buffer   *unused;           //指向buf中没有使用的buffer
    gc_root_buffer   *first_unused;     //指向buf中第一个没有使用的buffer
    gc_root_buffer   *last_unused;      //指向buf尾部

    gc_root_buffer    to_free;          //待释放的垃圾列表
    gc_root_buffer   *next_to_free;     //下一待释放的垃圾列表  

    uint32_t gc_runs;       //统计gc运行次数
    uint32_t collected;     //统计已回收的垃圾数
} zend_gc_globals;
  • buf: 垃圾缓冲区,PHP7默认10000个节点位置。第0个位置保留
  • roots: 指向缓冲区中最新加入的可能是垃圾的元素
  • unused: 指向缓冲区中没有使用的位置,GC算法没有开始,指向空
  • first_unused: 指向缓冲区中第一个未使用的位置,新的元素插入缓冲区后,指针会向后移动一位
  • last_unused: 指向缓冲区中最后一个位置
  • to_free: 待释放的列表
  • next_to_free: 下一个代释放的列表

gc_possible_root 函数 - 把对象加入缓冲区

当进行unset的时候,会调用对应函数类似于:ZEND_UNSET_VAR_SPEC_CV_UNUSED_HANDLER

同时判断对象为collectable类型,且未加入垃圾回收缓存区。就会调用 gc_possible_root 尝试加入缓冲区

/**
 * @brief 缓冲区处理
 *  1. 变量检查,必须是array或object且必须是黑色,说明没有加入过缓冲区
 *  2. 首先尝试在unused队列中取一个buffer
 *      1. 如果unused队列不为空,从unused队列中取到一个buffer, unused后移
 *      2. 如果GC_G(first_unused) != GC_G(last_unused), buffer队列未满,则从first_unused取一个buffer, 同时将first_unused后移
 *      3. 缓冲区已满的情况。启动垃圾回收 跳转到 zend_gc_collect_cycles 函数。垃圾回收之后,就有空的buffer可以从unused队列取出
 *  3. 得到了新的buffer,把传入的变量先设置为字符串,然后写入buffer之中,并挂载到全局roots链中
 * 
 * @param ref 是zend_value相应的gc地址
 * @return ZEND_API 
 */
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
{
}

zend_gc_collect_cycles函数 - GC回收流程

/**
 * @brief GC回收流程
 *  1. GC_G(roots).next != &GC_G(roots), 判断roots链不为空
 *  2. gc_mark_root函数: 遍历roots链表,对当前节点value的所有成员(如数组元素、成员属性)进行深度优先遍历把成员refcount减1
 *  3. gc_scan_roots函数: 遍历roots链表中所有灰色元素及其子元素,如果发现其引用计数仍旧大于0,说明这个元素还在其他地方使用
 *      那么将其颜色重新标记会黑色,并将其引用计数加1。如果发现其引用计数为0,则将其标记为白色
 *  4. gc_collect_roots 函数: 遍历roots链表,将黑色的元素从roots移除
 *      对roots中颜色为白色的元素进行深度优先遍历,将其引用计数加1,同时将颜色为白色的子元素也加入roots链表
 *      最后然后将roots链表移动到待释放的列表to_free中
 *  5. 把全局to_free列表复制到本地to_free,然后遍历释放,最后把回收使用过的垃圾池buffer,将其放入unused队列
 * 
 * @return ZEND_API 
 */
 ZEND_API int zend_gc_collect_cycles(void)
{
}

gc_mark_roots 函数 - 对roots链的紫色对象进行标记

/**
 * @brief 对roots链的紫色对象进行标记
 * 
 */
static void gc_mark_roots(void)
{
    gc_root_buffer *current = GC_G(roots).next;

    while (current != &GC_G(roots)) {
        // 对紫色对象进行标记
        if (GC_REF_GET_COLOR(current->ref) == GC_PURPLE) {
            gc_mark_grey(current->ref);
        }
        current = current->next;
    }
}

gc_mark_grey 函数 - 标记为灰色

/**
 * @brief 标记为灰色
 *  1. 对不是灰色对象,标记为灰色
 *  2. 对象类型。通过get_gc获取子节点,并递归标记子节点。最后引用计数减1
 *  3. 数组类型。如果是全局符号表(EG(symbol_table)),则将引用标记为黑色,并返回
 *              如果不是全局符号表,代码将把引用转换为 zend_array 类型
 *              然后遍历hashtable,引用计数减1并递归子节点
 *  4. 引用类型。检查引用对象的 val 是否为引用计数类型(Z_REFCOUNTED)
 *              如果是,那么它会继续判断对象存储(EG(objects_store).object_buckets)是否为空并且 val 的类型是否为对象类型(IS_OBJECT)
 *              如果对象存储不为空或者 val 的类型不是对象类型,那么它会将 val 的引用计数值减1
 * 
 * @param ref 
 */
static void gc_mark_grey(zend_refcounted *ref)
{
}

gc_scan_roots函数 - 对roots链进行扫描

static void gc_scan_roots(void)
{
    gc_root_buffer *current = GC_G(roots).next;

    while (current != &GC_G(roots)) {
        gc_scan(current->ref);
        current = current->next;
    }
}

gc_scan 函数 - GC扫描

/**
 * @brief GC扫描
 *  1. 对象为灰色
 *  2. 如果引用计数大于0,就把对象标记为黑色,并跳转到gc_scan_black
 *  3. 如果引用计数小于0,则标记为白色(垃圾对象)
 *      1. 对象类型。通过get_gc获取子节点,并递归标记子节点
 *      2. 数组类型。如果是全局符号表就标记为黑色
 *                 如果不是全局符号表,代码将把引用转换为 zend_array 类型,然后遍历hashtable并递归子节点
 *      3. 引用类型。如果对象存储不为空或者 val 的类型不是对象类型,那么就goto tail_call
 * 
 * @param ref 
 */
static void gc_scan(zend_refcounted *ref)
{
}

gc_scan_black 函数 - 黑色对象扫描

/**
 * @brief 黑色对象扫描
 *  1. 标记为黑色(不是垃圾对象)
 *  2. 对象类型。通过get_gc获取子节点,并递归标记子节点。最后引用计数加1
 *  3. 数组类型。如果不是全局符号表,代码将把引用转换为 zend_array 类型
 *              然后遍历hashtable,引用计数加1并递归子节点
 *  4. 引用类型。如果对象存储不为空或者 val 的类型不是对象类型,那么它会将 val 的引用计数值加1
 * 
 * @param ref 
 */
static void gc_scan_black(zend_refcounted *ref)
{
}

gc_collect_roots 函数 - 对roots链进行回收

/**
 * @brief 对roots链进行回收
 *  1. 把黑色对象从roots链脱链
 *  2. 对roots链的白色对象(垃圾)进行回收,通过 gc_collect_white 函数
 *  3. 把roots链的对象交换到to_free列表中
 * 
 * @param flags 
 * @param additional_buffer 
 * @return int 
 */
static int gc_collect_roots(uint32_t *flags, gc_additional_buffer **additional_buffer)
{
}

gc_collect_white 函数 - 对白色对象进行回收

/**
 * @brief 对白色对象进行回收
 *  1. 把白色对象设置为黑色对象
 *  1. 对象类型。通过get_gc获取子节点,并递归标记子节点。如果为黑色对象,且子节点过多触发 gc_add_garbage 功能
 *  2. 数组类型。如果不是全局符号表,代码将把引用转换为 zend_array 类型,然后遍历hashtable并递归子节点
 *              同时如果为黑色对象,且子节点过多触发 gc_add_garbage 功能
 *  3. 引用类型。如果对象存储不为空或者 val 的类型不是对象类型,那么就goto tail_call
 * 
 * @param ref 
 * @param flags 
 * @param additional_buffer 
 * @return int 
 */
static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_additional_buffer **additional_buffer)
{
}

gc_add_garbage 函数 - 新增垃圾回收空间

/**
 * @brief 将不在roots链上的白色元素挂接到roots链上
 *  1. 将所有白色元素放到roots链上,这当然也包括白色的子元素
 *  2. 子元素可能有很多,但受限于垃圾缓冲池的大小roots最长只有10000个,不够用怎么办呢?
 *  3. 这时就需要临时申请额外的存储空间gc_additional_buffer
 * 
 * @param ref 
 * @param additional_buffer 
 */
static void gc_add_garbage(zend_refcounted *ref, gc_additional_buffer **additional_buffer)
{
}

总结

PHP7 GC 算法 与 部分标记清除算法类似,重点是对象状态流转和缓冲区队列

接下来介绍的是性能更高的引用计数算法

RC Immix

概述

RC Immix 是 合并型引用计数法 + Immix结合.与以往的引用计数法相比,其吞吐量平均提升12%

吞吐量得到改善的原因有两个

  • 合并型引用计数法。因为没有通过写入屏障来执行计数器的增减操作,所以即使对象间的引用关系频繁发生变化,吞吐量也不会下降太多

  • 撤除了空闲链表。通过以线为单位来管理分块,只要在线内移动指针就可以进行分配了

合并型引用计数法

在合并型引用计数法中要将指针发生改动的对象和其所有子对象注册到更改缓冲区中,等到缓冲区满了,就要运行GC(类似于PHP 的unused队列)

等到GC回收时,才能从缓冲区取出对应的变量进行增量/减量

Immix

ImmixGC 构成

ImmixGC 把堆分为一定大小的块(block), 再把每一个块分成一定大小的线(line). 这个算法不是以对象为单位,而是以线为单位回收垃圾

块最合适的大小是32k字节,线最合适的大小是128字节。每个块就有32 * 1024 / 128 = 256个线

各个块由以下4个域

  • line: 线

  • mark_table: 线对应的标记位串

    • FREE(没有对象)
    • MARKED(标记完成)
    • ALLOCATED(有对象)
    • CONSERVATIVE(保守标记)
  • status: 用于表示每个块使用情况的域

    • FREE(所有线为空)
    • RECYCLABLE(一部分线为空)
    • UNAVAILABLE(没有空的线)
  • hole_ctn: 用于表示碎片化严重程度的指标

ImmixGC工作原理

分配时程序首先寻找空的线,然后安排对象。没找到空的线时候就执行GC

GC分为3个步骤执行

  • 选定备用的From 块
    • 通过hole_ctn数来判断,优先选择碎片化最严重的线作为备用From块
    • 判断标准为, “From 块中 ALLOCATED 线和 CONSERVATIVE 线的总数” <= “除From 以外的块中 FREE 线的总数”
  • 搜索阶段
    • 从根开始搜索对象,根据对象分别进行标记处理或复制处理
    • 复制处理指的是将备用 From 块里的对象复制到别的块(To 块/FREE块),并进行压缩
  • 清除阶段
    • 清除阶段中要搜索各个块的 mark_table
    • 如果 mark_table[i] 的值是 ALLOCATED,则设定 mark_table[i] = FREE

合并型引用计数法和Immix融合

RC Immix 中不仅对象有计数器,线也有计数器,这样就可以获悉线内是否存在活动对象

对象的计数器表示的是指向这个对象的引用的数量,而线的计数器表示的是这个线里存在的活动对象的数量

当对象的计数器为 0 时,对线的计数器进行减量操作。当线的计数器为 0 时,我们就可以将线整个回收再利用了

RC Immix 和合并型引用计数法一样,在更改缓冲区满了的时候都会查找更改缓冲区,这时如果发现了新对象,就会把它复制到别的空间(Immix 中的新块)去

同时通过被动碎片整理,对新的对象进行压缩,但是也会导致旧对象碎片化

而积极碎片整理。正好完善无法对旧对象进行压缩、无法回收有循环引用的垃圾的问题。原理就是决定要复制到哪个块,然后把能够通过指针从根查找到的对象全部复制过去

参考资料

  • 《垃圾回收的算法与实现》
  • 《PHP7 底层设计与源码实现》
  • 垃圾回收机制中,引用计数法是如何维护所有对象引用的?
  • php7垃圾回收机制及相关源码解读
  • [Go三关-典藏版]Golang垃圾回收+混合写屏障GC全分析
  • Taking off the gloves with reference counting Immix

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信公众号第三方平台-公众号扫码授权接入代运营

文章目录 接入目的效果展示技术积累如何成为服务商如何搭建第三方后端服务传统模式V云服务模式如何完成商家授权授权逻辑介绍 环境准备注册开发者平台-个人类型 传统模式后端代码接收公众号个人三方平台的票据根据票据获取三方平台访问令牌根据访问令牌获取预授权码通过预授权码…

如何本地搭建开源导航页配置服务Dashy并发布到公网分享好友使用

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。[点击跳转到网站] 简介 Dashy 是…

嵌入式网线连接——笔记本电脑设置

一、需求 我们调试很多设备经常需要用到网线去调试&#xff0c;当然主流是USB&#xff0c;和网线。 二、笔记本电脑端设备 有网口的&#xff0c;非常方便&#xff0c;如果没有网口&#xff0c;则需要用到USB转网口 连接指示灯&#xff1a; 绿色&#xff1a;灯亮表示连接正常…

蓝桥杯(填空题)

十四届 B组 日期统计&#xff08;暴力枚举&#xff09; 数据 5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1 5 8 6 1 8 3 0 3 7 9 2 7 0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 3 8 5 1 6 3 4 6 7 0 7 8 2 7 6 8 9 5 6 5 6 1 4 0 1 0 0 9 4 8 0 9 1 2 8 5 0 2 5 3…

一篇文章了解php7和php8新特性

PHP7新特性 ?? 运算符 php7以前用三目判断变量是否存在或是否为空 $a isset($_GET[a]) ? $_GET[a] : 1;php7新增null 合并运算符??快捷判断 $a $_GET[a] ?? 1;函数返回值类型声明 用:返回值类型的形式定义函数的返回值类型 <?phpdeclare(strict_types1); fun…

git配置多SSH

目的&#xff1a; 一台电脑可以让github、gitee等账号同时存在&#xff0c;让不同账号配置不同的密钥 第一步&#xff1a;创建不同平台的SSH公钥 执行命令&#xff1a; ssh-keygen -t rsa -C "对应仓库邮箱地址" -f ~/.ssh/id_rsa.github 如果执行上面的命令&…

Windows部署ChatGLM3步骤

一、环境要求 硬件 内存&#xff1a;> 16GB 显存: > 13GB&#xff08;4080 16GB&#xff09; 软件 python 版本推荐3.10 - 3.11 transformers 库版本推荐为 4.36.2 torch 推荐使用 2.0 及以上的版本&#xff0c;以获得最佳的推理性能 二、部署步骤 1、新建pytho…

Web服务器架构设计(学习笔记)

软件架构风格 质量属性与架构评估 Web架构综合考察 什么叫做架构风格&#xff1f;又有哪些架构风格&#xff1f;不同的架构风格的优劣如何? 有哪些层次的负载均衡实现&#xff1f;优劣如何&#xff1f; 有哪些层面的集群切片实现&#xff1f; 什么叫做小前端&#xff0c…

【收藏】工业物联网常用协议及使用场景

物联网通信协议 物联网通信协议在工业中至关重要。它们实现设备之间的实时数据传输与相互交互&#xff0c;提高了生产效率、降低成本、增强安全性。这些协议帮助监控设备运行状况、优化生产流程、预测维护需求&#xff0c;同时改善生产环境智能化。通过整合智能传感器与设备&am…

哪些工作不会被AI替代:人类能力地形图

这一轮AI&#xff0c;到底对人有多强的替代性&#xff1f;这一轮AI的可靠性&#xff0c;是之前任何一代所谓的人工智能都不能比的。 在国内&#xff0c;AI开始被用来筛选简历&#xff0c;而在国外&#xff0c;亚马逊正在用算法跟踪仓库工人的生产率&#xff0c;顺道还会给生产率…

【Vector-Map-路径规划(0)】卷首语

因为城市NOA 的开发过程中&#xff0c;十字路口这类场景非常不好处理&#xff0c;个人对路径规划没有什么基础&#xff0c;只知道深度优先&#xff0c;广度优先&#xff0c;A*&#xff0c;Dijkstra等算法&#xff0c;不知道在矢量地图中如何使用&#xff1f;因此花几天时间读几…

【Python】控制台进度条

在Python开发中&#xff0c;有时需要向用户展示一个任务的进度&#xff0c;以提供更好的交互体验。下面我将展示如何使用Python来创建一个简单的控制台进度条。 效果&#xff1a; 代码&#xff1a; import time import sys def print_progress_bar(completed, total, length…

如何使用Jellyfin+cpolar低成本部署私人影音平台并实现无公网IP远程访问

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

Redis从入门到精通(十二)Redis实战(九)GEO查询附近商户、BitMap用户签到和统计、HLL的UV统计

↑↑↑请在文章开头处下载测试项目源代码↑↑↑ 文章目录 前言4.10 附近商户4.10.1 GEO介绍4.10.2 附近商户需求分析4.10.3 实现新增商户功能4.10.4 实现查询附近商户功能 4.11 用户签到4.11.1 用户签到需求分析4.11.2 BitMap介绍4.11.3 实现用户签到4.11.4 实现用户签到统计4.…

Vuforia AR篇(二)— 扫描指定图片播放视频

目录 一、 使用Vuforia SDK创建Vuforia账号下载Vuforia SDK包导入SDK到unity中 二、使用Vuforia扫描指定图片播放视频创建ARCamera创建 License创建ImageTarget生成识别数据库播放视频 三、 效果 一、 使用Vuforia SDK 创建Vuforia账号 Vuforia官网 登录官网创建一个账号&am…

【fiddler】弱网测试

目录 一、测试目的 二、步骤 2.1打开弱网模式 ​ 2.2设置网络参数 &#xff08;1&#xff09;打开Rules→Customize Rules&#xff1b; &#xff08;2&#xff09;找到下面框出的代码&#xff0c;在这里设置弱网参数值&#xff1b; &#xff08;3&#xff09;设置完成后&a…

【每日刷题】Day10

【每日刷题】Day10 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f345; 目录 1. 环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com) 2. 21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 3. 152…

【MATLAB源码-第9期】基于matlab的DQPSK的误码率BER和误符号率SER仿真。

1、算法描述 DQPSK信号的解调与2DPSK信号的解调类似&#xff0c;也有两种方法&#xff0c;分别是极性比较法和相位比较法 极性比较法。其原理方框图如下图所示。由于DQPSK信号可以看做是两路2DPSK信号的合成&#xff0c;解 调时也可以分别按两路2DPSK信号解调&#xff0c;因此…

Docker部署SpringBoo+Vue前后端分离项目

文章目录 1. 安装Docker1. 1 卸载旧版Docker1.2 配置yum仓库1.3 安装Docker1.4 添加自启动配置1.5 配置阿里云镜像加速1.6 测试 2. 安装Nginx2.1 拉取镜像2.2 安装Nginx2.3 测试 3. 安装MySQL3.1 拉取镜像3.2 安装MySQL3.3 连接MySQL 4. 部署SpringBoot项目4.1 Maven打包4.2 编…

java数组.day16(冒泡排序,稀疏数组)

冒泡排序 冒泡排序无疑是最为出名的排序算法之一&#xff0c;总共有八大排序! 冒泡的代码还是相当简单的&#xff0c;两层循环&#xff0c;外层冒泡轮数&#xff0c;里层依次比较&#xff0c;江湖中人人尽皆知。 我们看到嵌套循环&#xff0c;应该立马就可以得出这个算法的时…