Alineación de memoria de iOS

prefacio

Creo que todo el mundo tiene experiencia de compra en tiendas de conveniencia y supermercados.Cuando va de compras, especialmente cuando quiere comprar una variedad de productos, la mayoría de ustedes elegirá ir a un supermercado grande. Los estantes de las tiendas de conveniencia suelen estar llenos. Puede haber refrigerios, necesidades diarias y bebidas en el mismo estante. Cuando compras algo en la tienda, generalmente llamas directamente al jefe. Es muy problemático encontrarlo tú mismo. El mismo estante en un gran supermercado generalmente contiene el mismo tipo de productos. Incluso si un estante no es suficiente, entonces colóquelo en otro estante. La eficiencia de compra también será mayor.

Por lo general, un byte es la unidad básica de memoria, pero cuando la CPU opera datos, a menudo accede a ellos en unidades de bloques . Si no hay un conjunto de reglas para restringir el almacenamiento de memoria, algunos datos se distribuirán en diferentes bloques, por lo que la CPU estará muy cansada y molesta (baja eficiencia) para encontrar estos datos. Una vez que los bytes están alineados, la CPU no necesita correr para encontrar datos (lo que reduce la cantidad de accesos) y la eficiencia mejora relativamente.

Factores que influyen en la memoria de objetos

Luego, analice el código, primero declare una clase:

@interface SLPerson : NSObject
@property(strong,nonatomic)NSString * name;
@property(assign,nonatomic)int age;
@end

Estudie el tamaño de la memoria del tipo de objeto, el tamaño real de la memoria del objeto y el tamaño de la memoria asignada al sistema:

    SLPerson * p = [SLPerson alloc];
    p.name = @"person";
    p.age = 18;
    
    SLPerson * p1;
    
    NSLog(@"p对象类型的内存大小--%lu",sizeof(p));
    NSLog(@"p对象实际的内存大小--%lu",class_getInstanceSize([p class]));
    NSLog(@"p系统分配的内存大小--%lu",malloc_size((__bridge const void *)(p)));
    NSLog(@"==================");
    NSLog(@"p1对象类型的内存大小--%lu",sizeof(p1));
    NSLog(@"p1对象实际的内存大小--%lu",class_getInstanceSize([p1  class]));
    NSLog(@"p1系统分配的内存大小--%lu",malloc_size((__bridge const void *)(p1)));
2022-07-01 10:38:26.985044+0800 alloc分析[29679:2671728] p对象类型的内存大小--8
2022-07-01 10:38:26.985110+0800 alloc分析[29679:2671728] p对象实际的内存大小--24
2022-07-01 10:38:26.985138+0800 alloc分析[29679:2671728] p系统分配的内存大小--32
2022-07-01 10:38:26.985164+0800 alloc分析[29679:2671728] ==================
2022-07-01 10:38:26.985186+0800 alloc分析[29679:2671728] p1对象类型的内存大小--8
2022-07-01 10:38:26.985207+0800 alloc分析[29679:2671728] p1对象实际的内存大小--0
2022-07-01 10:38:26.985231+0800 alloc分析[29679:2671728] p1系统分配的内存大小--0

analizar

  • Memoria de tipo de objeto: p y p1 son esencialmente punteros, por lo que tanto sizeof (p) como sizeof (p1) son 8 bytes
  • La memoria real del objeto: determinada por el tamaño de las variables miembro de la clase, nombre (8), edad (4) y es un puntero (8), 8+4+8=20, ¿eh? ? ? No, claramente genera 24
  • Memoria asignada del sistema: 32, esto. . . . . . ¿Se está haciendo más grande? Más tarde exploraremos malloc para el análisis.
  • En cuanto a p1, no se inicializa, por lo que el tamaño que ocupa el contenido del objeto es 0

A continuación, usamos instrucciones LLDB para ver la visualización de los atributos del objeto p en la memoria

 Podemos ver que el contenido de p consta de tres bytes de 8. Aunque age solo ocupa 4 bytes, los primeros 4 bytes también se rellenan con 0, es decir, el tamaño del contenido del objeto está alineado en 8 bytes. También explica por qué el contenido de p suma 20 bytes, pero ocupa 24 bytes.

A continuación, agregamos un método de instancia y un método de clase a la clase SLPerson

@interface SLPerson : NSObject
@property(strong,nonatomic)NSString * name;
@property(assign,nonatomic)int age;

-(void)test1;
+(void)test2;
@end

misma salida

2022-07-01 10:41:39.059577+0800 alloc分析[29700:2673212] p对象类型的内存大小--8
2022-07-01 10:41:39.059640+0800 alloc分析[29700:2673212] p对象实际的内存大小--24
2022-07-01 10:41:39.059668+0800 alloc分析[29700:2673212] p系统分配的内存大小--32
2022-07-01 10:41:39.059689+0800 alloc分析[29700:2673212] ==================
2022-07-01 10:41:39.059712+0800 alloc分析[29700:2673212] p1对象类型的内存大小--8
2022-07-01 10:41:39.059733+0800 alloc分析[29700:2673212] p1对象实际的内存大小--0
2022-07-01 10:41:39.059758+0800 alloc分析[29700:2673212] p1系统分配的内存大小--0

Resumir

  • Las variables miembro y los atributos afectarán el tamaño de la memoria del objeto de instancia de la clase.
  • Agregar un método no tiene efecto en el tamaño de la memoria del objeto de instancia de la clase y el método no existe en el objeto.
  • Durante el proceso de agregar variables miembro, debido a que los tipos de datos de las variables miembro son inconsistentes, alinee con la variable miembro del tipo de datos más grande. Para las clases heredadas de objetos NSObject, la alineación de bytes predeterminada es de 8 bytes .

Alineación de memoria de estructura

La esencia de un objeto es en realidad una estructura , y el alineamiento de la memoria se puede considerar como el alineamiento de la memoria de una estructura. A continuación, exploraremos el alineamiento de la memoria de una estructura.

sin anidar

Los resultados anteriores muestran que las variables miembro de las dos estructuras son exactamente iguales, pero el orden de declaración es diferente y el tamaño de salida también es diferente.

analizar 

A continuación, usamos f(x,y) para simular el almacenamiento de variables miembro, donde x representa la posición inicial de la variable miembro e y representa el tamaño de la variable miembro

SLStruct1:

  • a: ocupa 8 bytes, a = f(0,8)
  • b: ocupa 4 bytes, (0+8)%4 = 0, b = f(8,4)
  • c: ocupa 2 bytes, (8+4)%2 = 0, c = f(12,2)
  • d: ocupa 1 byte, (12+2)%1 = 0, d = f(14,1)

SLStruct2:

  • a: ocupa 8 bytes, a = f(0,8)
  • d: ocupa 1 byte, (0+8)%1 = 0, b = f(8,1)
  • b: ocupa 4 bytes, (8+1)%4 = 1, necesita avanzar 3 posiciones para llegar a 12, 12 es divisible por 4, c = f(12,4)
  • d: ocupa 2 bytes, (12+4)%2 = 0, d = f(16,2)

Resumir

El tamaño de memoria total de SLStruct1 es de 15 bytes, el tamaño de memoria total de SLStruct2 es de 18 bytes y el tamaño de memoria máximo de las variables miembro en las dos estructuras es de 8 bytes, por lo que el tamaño de memoria real de SLStruct1 y SLStruct2 debe ser un múltiplo entero de 8 , se completa y redondea automáticamente, por lo que el tamaño real de SLStruct1 es de 16 bytes y el tamaño real de SLStruct2 es de 24 bytes.

anidado

A continuación, creamos otra estructura con una estructura anidada en su interior.

struct SLStruct3{
    long    a; // 8
    int     b; // 4
    short   c; // 2
    char    d; // 1
    struct SLStruct2 lwStr;
}SLStruct3;


打印结果
2022-07-01 11:24:29.010493+0800 哦哦、[2895:94727] SLStruct1-----16
SLStruct2-----24
SLStruct3-----40
Program ended with exit code: 0

analizar 

Del mismo modo, analicemos SLStruct3

SLStruct1:

  • a: ocupa 8 bytes, a = f(0,8)
  • b: ocupa 4 bytes, (0+8)%4 = 0, b = f(8,4)
  • c: ocupa 2 bytes, (8+4)%2 = 0, c = f(12,2)
  • d: ocupa 1 byte, (12+2)%1 = 0, d = f(14,1)
  • lwStr: el miembro más grande de SLStruct2 ocupa 8 bytes, por lo que (14+1)%8 = 7, debe avanzar 1 posición para llegar a 16, 16 es divisible por 8, c = f(16,18)

Resumir

El tamaño de memoria combinado de SLStruct3 es bytes 34. El tamaño de memoria máximo ocupado por las variables miembro a y lwStr en la estructura es bytes 8, por lo que el tamaño de memoria real de SLStruct3 debe ser un múltiplo entero de 8, que se llena y redondea automáticamente Entonces, el tamaño real de SLStruct3 es de 40 bytes.

Reglas de alineación de estructuras

A través del análisis del ejemplo anterior, resuma cómo la estructura calcula el tamaño

  1. Los miembros de struct y union se almacenan en orden. La posición inicial del primer miembro se coloca en x = 0, y la posición inicial de los miembros posteriores debe almacenarse en un múltiplo entero del tamaño del miembro.
  2. Suponiendo que hay un miembro de estructura s anidado en el miembro, la posición inicial de s debe determinarse de acuerdo con el tamaño del miembro más grande en s. Por ejemplo, el miembro más grande en s ocupa 8 bytes, luego la posición inicial de s debe almacenarse en un múltiplo entero de 8 posiciones.
  3. Después de calcular la posición del último miembro y agregar el tamaño del miembro (que se supone que es r), depende de si r es un múltiplo entero del miembro más grande de la estructura, y si es insuficiente, debe llenarse.
  4. Por lo tanto, el cálculo del tamaño del contenido del objeto de la clase también se basa en esta rutina, no es más que el objeto de la clase calcula una isa más (ocupando 8 bytes) que la estructura al principio , y luego otros miembros (atributos) se descargan más tarde .

exploración malloc

El método malloc_size no se proporciona directamente. Aquí descargué la biblioteca libmalloc-317.40.8 para explorar. Al explorar alloc antes, después de pasar _class_createInstanceFromZoneel método, calloc es el sistema para abrir memoria, por lo que comenzamos con calloc.

1. Llame al método calloc

2. Saltar a calloc

void *
calloc(size_t num_items, size_t size)
{
	return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

3. Saltar a _malloc_zone_calloc

_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
		malloc_zone_options_t mzo)
{
	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

	void *ptr;
	if (malloc_check_start) {
		internal_check();
	}
	ptr = zone->calloc(zone, num_items, size);

	if (os_unlikely(malloc_logger)) {
		malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
				(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
	}

	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
	if (os_unlikely(ptr == NULL)) {
		malloc_set_errno_fast(mzo, ENOMEM);
	}
	return ptr;
}

4. Vaya a zone->calloc, descubra que no puede ingresarlo y use ensamblado.

5. Búsqueda global default_zone_calloc

static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	zone = runtime_default_zone();
	
	return zone->calloc(zone, num_items, size);
}

6. Es zone->calloc nuevamente, continúa compilando

7. Búsqueda global nano_calloc

static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
	size_t total_bytes;

	if (calloc_get_size(num_items, size, 0, &total_bytes)) {
		return NULL;
	}

	if (total_bytes <= NANO_MAX_SIZE) {
		void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
		if (p) {
			return p;
		} else {
			/* FALLTHROUGH to helper zone */
		}
	}
	malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
	return zone->calloc(zone, 1, total_bytes);
}

8. Ingrese _nano_malloc_check_clear, en este momento nos enfocamos en size_t y encontramos un buen método segregated_size_to_fit

static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
	MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);

	void *ptr;
	size_t slot_key;
	size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
	mag_index_t mag_index = nano_mag_index(nanozone);

	nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);

	ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
	if (ptr) {

。。。。。。。

9 Ingrese segregated_size_to_fit, en este momento, finalmente encontramos el algoritmo central en el código fuente de libmalloc

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
	size_t k, slot_bytes;

	if (0 == size) {
		size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
	}
	k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
	slot_bytes = k << SHIFT_NANO_QUANTUM;							// multiply by power of two quanta size
	*pKey = k - 1;													// Zero-based!

	return slot_bytes;
}

Al mismo tiempo vemos dos definiciones de macro

#define SHIFT_NANO_QUANTUM		4
#define NANO_REGIME_QUANTA_SIZE	(1 << SHIFT_NANO_QUANTUM)	// 16

Se puede concluir que cuando llamamos a calloc(1, 20), el algoritmo final es a través de la operación (20 + 16 - 1) >> 4 << 4, el resultado es 48, es decir, la alineación de memoria se alinea de acuerdo a 16 bytes (PD: desplazar un bit a la derecha equivale a dividir por 2, y desplazar un bit a la izquierda equivale a multiplicar por 2)

Resumir

  1. class_getInstanceSize : obtiene el tamaño de memoria ocupado por el objeto de instancia ( alineación de 8 bytes)
  2. malloc_size : Obtenga el tamaño de memoria real asignado por el sistema ( alineación de 16 bytes)

Supongo que te gusta

Origin blog.csdn.net/weixin_38016552/article/details/125542716
Recomendado
Clasificación