PHP7垃圾回收算法

前提

本文为了梳理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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Android】【root remount】【3】remount 文件详细信息获取

前言 我们在root & remount 设备后&#xff0c;push相关文件到systm 、vendor、product 等目录进行调试&#xff0c;那么我们push的文件被保存在什么地方呢&#xff1f; 以及我们FWS、app侧如何过去push 的文件信息呢&#xff1f; remount push 文件保存 push 文件保存的…

python 有哪些函数

Python内置的函数及其用法。为了方便记忆&#xff0c;已经有很多开发者将这些内置函数进行了如下分类&#xff1a; 数学运算(7个) 类型转换(24个) 序列操作(8个) 对象操作(7个) 反射操作(8个) 变量操作(2个) 交互操作(2个) 文件操作(1个) 编译执行(4个) 装饰器(3个) …

硬件 - 施密特比较器

文章目录 1 . 概要2 . 施密特比较器电路3 . 施密特比较器计算4 . 小结 【全文大纲】 : https://blog.csdn.net/Engineer_LU/article/details/135149485 1 . 概要 1 . 比较器主要作用是比较输入的两个信号&#xff0c;而有时候两个信号的值都差不多大的时候一点抖动&#xff0c;…

PHP自助建站系统,小白也能自己搭建网站

无需懂代码&#xff0c;用 自助建站 做企业官网就像做PPT一样简单&#xff0c;您可以亲自操刀做想要的效果&#xff01; 自助建站是一款简单、快捷、高效的工具&#xff0c;可以帮助您制作响应式网站。我们的自助建站系统&#xff0c;将传统的编码工作转化为直观的拖拽操作和文…

【计算机毕业设计】校园网书店系统——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

Quanto: PyTorch 量化工具包

量化技术通过用低精度数据类型 (如 8 位整型 (int8)) 来表示深度学习模型的权重和激活&#xff0c;以减少传统深度学习模型使用 32 位浮点 (float32) 表示权重和激活所带来的计算和内存开销。 减少位宽意味着模型的内存占用更低&#xff0c;这对在消费设备上部署大语言模型至关…

基于JAVA的校园失物招领平台

采用技术 基于JAVA的校园失物招领平台的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 管理员功能 论坛管理 失物认领管理 寻物启事管理 用户管理 失物…

如何查询运行的服务器的整机功耗?

要在Linux服务器上安装Powerstat&#xff0c;您可以根据所使用的Linux发行版选择适当的命令。 对于Ubuntu/Debian系统&#xff0c;您可以使用以下命令安装Powerstat&#xff1a; sudo apt-get install powerstat 对于Redhat/CentOS系统&#xff0c;您应该使用以下命令&#x…

ReLU Strikes Back: Exploiting Activation Sparsity in Large Language Models

iclr 2024 oral reviewer 评分 688 1 intro 目前LLM社区中通常使用GELU和SiLU来作为替代激活函数&#xff0c;它们在某些情况下可以提高LLM的预测准确率 但从节省模型计算量的角度考虑&#xff0c;论文认为经典的ReLU函数对模型收敛和性能的影响可以忽略不计&#xff0c;同时…

专为苹果系统设计的精美可视化图表 | 开源日报 No.219

danielgindi/Charts Stars: 27.3k License: Apache-2.0 Charts 是为 iOS/tvOS/OSX 提供美观图表的开源项目&#xff0c;是跨平台 MPAndroidChart 在苹果设备上的实现。该项目提供了以下主要功能和优势&#xff1a; 支持 iOS、tvOS 和 macOS 平台使用 Swift 编写&#xff0c;可…

FFmpeg: 简易ijkplayer播放器实现--01项目简介

文章目录 项目介绍流程图播放器实现过程界面展示 项目介绍 此项目基于FFmeg中 ffplay.c进行二次开发&#xff0c;实现基本的功能&#xff0c;开发软件为Qt 项目优势&#xff1a; 参考ijkplayer播放器&#xff0c;实现UI界面和播放器核心进行解耦&#xff0c;容易添加其他功能…

互联网轻量级框架整合之MyBatis核心组件

在看本篇内容之前&#xff0c;最好先理解一下Hibernate和MyBatis的本质区别&#xff0c;这篇Hibernate和MyBatis使用对比实例做了实际的代码级对比&#xff0c;而MyBatis作为更适合互联网产品的持久层首选必定有必然的原因 MyBatis核心组件 MyBatis能够成为数据持久层首选框&a…

利用图和侧信息的核概率矩阵

文章信息 本周阅读的论文是一篇2012年发表在《Proceedings of the 2012 SIAM International Conference on Data Mining》上关于概率矩阵分解的文章&#xff0c;题目为《Kernelized Probabilistic Matrix Factorization Exploiting Graphs and Side Information》。 摘要 我们提…

【STM32篇】DRV8425驱动步进电机

【STM32篇】4988驱动步进电机_hr4988-CSDN博客 在上篇文章中使用了HR4988实现了步进电机的驱动&#xff0c;在实际运用过程&#xff0c;HR4988或者A4988驱动步进电机会存在电机噪音太大的现象。本次将向各位友友介绍一个驱动简单且非常静音的一款步进电机驱动IC。 1.DRV8425简介…

头歌-机器学习实验 第8次实验 决策树

第1关&#xff1a;什么是决策树 任务描述 本关任务&#xff1a;根据本节课所学知识完成本关所设置的选择题。 相关知识 为了完成本关任务&#xff0c;你需要掌握决策树的相关基础知识。 引例 在炎热的夏天&#xff0c;没有什么比冰镇后的西瓜更能令人感到心旷神怡的了。现…

Fast-Planner(五)详解TopologyPRM

本文上接Fast-Planner第一篇文章的内容&#xff0c;本文主要详解这一系列的第二篇Robust Real-time UAV Replanning Using Guided Gradient-based Optimization and Topological Paths中的TopologyPRM即其代码。如有问题&#xff0c;欢迎各位大佬评论指出&#xff0c;带着我一起…

C语言面试题之返回倒数第 k 个节点

返回倒数第 k 个节点 实例要求 1、实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点&#xff1b;2、返回该节点的值&#xff1b; 示例&#xff1a;输入&#xff1a; 1->2->3->4->5 和 k 2 输出&#xff1a; 4 说明&#xff1a;给定的 k 保证是有效的。实…

Unity 获取RenderTexture像素颜色值

拿来吧你~ &#x1f9aa;功能介绍&#x1f32d;Demo &#x1f9aa;功能介绍 &#x1f4a1;不通过Texture2D 而是通过ComputerShader 提取到RenderTexture的像素值&#xff0c;效率有提升哦&#xff01; &#x1f4a1;通过扩展方法调用&#xff0c;方便快捷&#xff1a;xxxRT.G…

无人机低空数字摄影测量系统

一、 系统概述 系统完全基于IDL设计实现&#xff0c;包括界面布局到人机交互再到底层核心函数功能。整体设计框架基于数字摄影测量的专业处理流程&#xff0c;实现了数据输入、数据预处理、影像信息检测、空间定向、地形三维建模、专题信息提取、成果输出与更新等功能。同时为…

【linux】yum 和 vim

yum 和 vim 1. Linux 软件包管理器 yum1.1 什么是软件包1.2 查看软件包1.3 如何安装软件1.4 如何卸载软件1.5 关于 rzsz 2. Linux编辑器-vim使用2.1 vim的基本概念2.2 vim的基本操作2.3 vim命令模式命令集2.4 vim底行模式命令集2.5 vim操作总结补充&#xff1a;vim下批量化注释…