Grupo de liberación automática de memoria en iOS

piscina de liberación automática

El subproceso principal de la aplicación iOS crea un grupo de liberación automática al comienzo de cada ciclo de ejecución y libera el grupo de liberación automática al final del ciclo de ejecución. Si la aplicación crea una gran cantidad de objetos temporales dentro de un ciclo de ejecución, el grupo de liberación automática puede reducir los picos de memoria.

for (int i = 0; i < 1000000; i++) {
    
    

    NSString *string = [NSString stringWithFormat:@"hello world!"];
}

Si ejecuta el código anterior, puede ver un aumento obvio de memoria a corto plazo usando XCode.

for (int i = 0; i < 1000000; i++) {
    
    
    @autoreleasepool {
    
    
        NSString *string = [NSString stringWithFormat:@"hello world!"];
    }
}    

Después de agregar el grupo de liberación automática, el uso de memoria del programa no aumenta significativamente.En este momento, el grupo de liberación automática libera la memoria variable creada temporalmente.

Tiempo de empuje y pop de la piscina de liberación automática

El push/pop de autoreleasePool está relacionado con runloop.

Cuando runloop ingresa al estado kCFRunLoopEntry, llame al método objc_autoreleasePoolPush

Cuando runloop ingresa al estado kCFRunLoopBeforeWaiting, llame al método objc_autoreleasePoolPop y al método objc_autoreleasePoolPush

Cuando runloop ingresa al estado kCFRunLoopBeforeExit, llama al método objc_autoreleasePoolPop.

Estructuras de datos de grupo de liberación automática

struct AutoreleasePoolPageData
{
    
    
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
    
    
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){
    
     .ptr = OBJC_VM_MAX_ADDRESS }.ptr == OBJC_VM_MAX_ADDRESS, "OBJC_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

	magic_t const magic;   //校验page是否完整
	__unsafe_unretained id *next;   //指向下一个存放数据的地址
	objc_thread_t const thread;     //当前线程
	AutoreleasePoolPage * const parent;  //父结点
	AutoreleasePoolPage *child;       //子结点
	uint32_t const depth;   //page深度
	uint32_t hiwat;   //最大入栈数量

	AutoreleasePoolPageData(__unsafe_unretained id* _next, objc_thread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
		: magic(), next(_next), thread(_thread),
		  parent(_parent), child(nil),
		  depth(_depth), hiwat(_hiwat)
	{
    
    
	}
};

AutoreleasePoolPageData es la clase base de AutoreleasePoolPage .

El grupo de publicación automática es una estructura de datos de una lista doblemente vinculada. El puntero principal apunta al nodo principal y el secundario apunta al nodo secundario. El tamaño de los datos de cada página es de 4096 bytes, y cada página es un nodo del lista doblemente enlazada. next representa la posición en la que se pueden insertar datos en la página actual.

Principio de implementación del pool de liberación automática

proceso de empuje

void *
objc_autoreleasePoolPush(void)
{
    
    
    return AutoreleasePoolPage::push();
}

Objective-C llama internamente a este método cuando el código agrega un objeto al grupo de liberación automática.

 static inline void *push() 
    {
    
    
        ReturnAutoreleaseInfo info = getReturnAutoreleaseInfo();
        moveTLSAutoreleaseToPool(info);

        id *dest;
        if (slowpath(DebugPoolAllocation)) {
    
    
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
    
    
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == (id *)EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

Este método llama principalmente al método autoreleaseFast.

 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);
        }
    }

El método autoreleaseFast se utiliza para obtener la página activa actual. Si la página activa no está llena, inserte los datos directamente. Si la página activa está llena, reasigne un espacio de página e insértelo. Si no se obtiene la página, significa que el grupo de liberación automática actual no se ha inicializado y es necesario inicializar la liberación automática.

id *add(id obj)
    {
    
    
        ASSERT(!full());
        unprotect();
        id *ret;

        ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;

     done:
        protect();
        return ret;
    }

Después de simplificar el método add, puede ver que el método add en realidad coloca obj en la posición del siguiente puntero y coloca next++ El valor de retorno de la función es la dirección de almacenamiento de obj.

static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
    
    
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        ASSERT(page == hotPage());
        ASSERT(page->full()  ||  DebugPoolAllocation);

        do {
    
    
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

La función autoreleaseFullPage es para encontrar una página que no está llena, llame a la función de agregar para insertar el objeto obj

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
    
    
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        ASSERT(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
    
    
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
    
    
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
    
    
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }

        

        // 初始化首页
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // 增加boundry标识
        if (pushExtraBoundary) {
    
    
            page->add(POOL_BOUNDARY);
        }
        
        // 添加obj
        return page->add(obj);
    }

Si actualmente no hay una página de grupo de lanzamiento automático, debe inicializar AutoreleasePoolPage y luego llamar al método add para agregar obj. El rol de setHotPage aquí es identificar la página actual.

proceso emergente

void objc_autoreleasePoolPop(void *ctxt)
{
    
    
    AutoreleasePoolPage::pop(ctxt);
}

El grupo de liberación automática llamará a este método cuando aparezca.

    static inline void
    pop(void *token)
    {
    
    
        // We may have an object in the ReturnAutorelease TLS when the pool is
        // otherwise empty. Release that first before checking for an empty pool
        // so we don't return prematurely. Loop in case the release placed a new
        // object in the TLS.
        while (releaseReturnAutoreleaseTLS())
            ;

        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
    
    
            // 获取hotPage
            page = hotPage();
            if (!page) {
    
    
                // 自动释放池没有使用过,清空placeholder
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
    
    
            page = pageForPointer(token);
        }

        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
    
    
            if (stop == page->begin()  &&  !page->parent) {
    
    
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
    
    
                
                return badPop(token);
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
    
    
            return popPageDebug(token, page, stop);
        }
        //清理自动释放池
        return popPage<false>(token, page, stop);
    }

Al hacer estallar, juzgará si está marcado como EMPTY_POOL_PLACEHOLDER, POOL_BOUNDARY, etc. Esta función finalmente llama a la función popPage.

template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
    
    
        if (allowDebug && PrintPoolHiwat) printHiwat();
        //释放当前页面
        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
    
    
            //debug使用
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
    
    
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
    
    
            // 如果页面实际占用空间小于一半,将child结点销毁
            if (page->lessThanHalfFull()) {
    
    
                page->child->kill();
            }
            如果页面实际占用空间大于一半,将child结点保留
            else if (page->child->child) {
    
    
                page->child->child->kill();
            }
        }
    }
    void releaseUntil(id *stop) 
    {
    
    
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage

        do {
    
    
            while (this->next != stop) {
    
    
             
                AutoreleasePoolPage *page = hotPage();

                while (page->empty()) {
    
    
                    page = page->parent;
                    setHotPage(page);
                }

                page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;

                // create an obj with the zeroed out top byte and release that
                id obj = (id)entry->ptr;
                int count = (int)entry->count;  // grab these before memset
#else
                id obj = *--page->next;
#endif
                memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
                page->protect();

                if (obj != POOL_BOUNDARY) {
    
    
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                    // release count+1 times since it is count of the additional
                    // autoreleases beyond the first one
                    for (int i = 0; i < count + 1; i++) {
    
    
                        objc_release(obj);
                    }
#else
                    objc_release(obj);
#endif
                }
            }

            // Stale return autorelease info is conceptually autoreleased. If
            // there is any, release the object in the info. If stale info is
            // present, we have to loop in case it autoreleased more objects
            // when it was released.
        } while (releaseReturnAutoreleaseTLS());

        setHotPage(this);

#if DEBUG
        // 检查子结点是否清空
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
    
    
            ASSERT(page->empty());
        }
#endif
    }

La función releaseUntil borrará un grupo de liberación automática, y la función objc_release en realidad se llama cuando se libera el objeto.

Supongo que te gusta

Origin blog.csdn.net/u011608357/article/details/128439012
Recomendado
Clasificación