preface

In the previous article “OC Underlying Principles -01- Alloc Underlying Exploration”, we mentioned a 16-byte alignment algorithm in ALIGN16. Does this mean that the system is using 16-byte alignment as the criterion when allocating memory at the bottom? Let’s begin to explore the memory alignment principle in earnest.

Three ways to get memory size

We need to know the 3 ways to get memory size in iOS, and then start exploring those 3 ways.

  • sizeof()
  • class_getInstanceSize()
  • malloc_size()

Let’s take a look at some code to analyze the similarities and differences between these three methods of obtaining memory size.

LHYStudent *baseStu = [[LHYStudent alloc]init]; Basestu. name = @" 中 国 cabbage "; BaseStu. Sex = @" male "; baseStu.age = 28; //int basestu. height = 199.9; //double NSLog(@"% @n-sizeof print: %lu\ n-class_getInstancesize print: %lu\ n-malloc_size print: %lu",baseStu,sizeof(baseStu),class_getInstanceSize([LHYStudent class]),malloc_size((__bridge const void *)(baseStu)));Copy the code

If we look at the print results, we will see that the memory sizes printed by the three methods are different.

Why is that? Then we need to understand the meaning of these three ways to get memory is what?

sizeof()

  • Is an operator that determines the length of a data type or expression, not a function;
  • This returns the number of bytes of memory used by an object or type.
  • The compiler handles sizeof() atCompilation phaseCarry on.

class_getInstanceSize()

This method has been briefly understood in the alloc exploration analysis _class_createInstanceFromZone source code implementation.

  • This method is an API provided by the Runtime, so we need to introduce it when calling this methodobjc/runtime.h;
  • The essence is to get the created objectAt least,Required memory size, 8 bytes aligned.

malloc_size()

This method gets the memory size that heap space is actually allocated to the object and is 16-byte aligned.

To understand the nature of the above three methods, we also need to understand the size of memory bytes occupied by each data type, as shown in the following figure:

Now we can start to analyze why the printed results are 8, 40 and 48 respectively.

  • sizeof()

    BaseStu is an object type, and the essence of an object type is a pointer to a structure. Pointers take up 8 bytes in memory, so sizeof prints 8.

  • class_getInstanceSize()

    Memory space required for the LHYStudent member variable: 8+8+4+8+8(ISA)=36, then 8-byte alignment, that is, 40.

  • malloc_size()

    The amount of memory actually allocated to objects in the heap, and aligned with 16 bytes, we can see that the amount actually allocated is not equal to the amount actually needed. Later, there will be a detailed malloc source code analysis article to analyze the malloc process.

Internal alignment of the structure

In order to facilitate the specific principles of memory alignment, let’s define two simple constructs and explore them by analyzing the size of memory occupied by the constructs.

struct LHYStruct1 { char a; //1 byte double b; //8 bytes int c; //4 bytes short d; } / / 2 bytes struct1; struct LHYStruct2 { double d; //8 bytes int c; //4 bytes short b; //2 bytes char a; / / 1 byte} struct2; NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));Copy the code

Take a look at the print result:

By printing the results, we find that the number and type of variables in the two structures are the same, but in different order, but the amount of memory used is completely different, which is due to the principle of memory alignment.

Memory alignment rule

  1. The first data member of a struct or union is stored at offset 0, and the starting location of each data member is the size of the member or its children (as long as the member has children, such as arrays). Structure, etc.) (e.g., if int is 4 bytes, start with the address that is a multiple of 4)
  2. If a struct contains a struct member, the member is stored from an integer multiple of the size of the largest element in the struct (char, int, double).
  3. The total memory size of a structure must be an integer multiple of the maximum memory size of its internal members.

Let’s analyze the above 👆 example according to the memory alignment rules.

Struct1 memory size calculation

Variable A is of type CHAR, occupying 1 byte, and is discharged from 0, i.e. [0] variable B is of type double, occupying 8 bytes. According to the principle of memory alignment, it needs to be discharged from a multiple of 8, obviously 1 is not a multiple of 8, until 8 is found and discharged, i.e. [8-15], the middle [1-7] are completed. Variable C is of type int, occupying 4 bytes, and discharged from 16, which is exactly a multiple of 4, that is, variable D [16-19] is of type short, occupying 2 bytes, and discharged from 20, that is, the largest variable B in [20-21] struct1 requires 8 bytes. Struct1 physical memory must be a multiple of 8, 22 rounded up to 24, so sizeof(struct1) is 24.

Struct2 memory size calculation

Variable D is of type double, occupying 8 bytes and discharging from 0, i.e. [0-7] Variable C is of type int, occupying 4 bytes and discharging from 8, 8 is a multiple of 4, i.e. [8-11] variable B is of type short, occupying 2 bytes and discharging from 12, 12 is also a multiple of 2. That is, variable A [12-13] is char type, occupying 1 byte, discharging from 14, i.e., the largest variable B in [14] struct1 requires 8 bytes, and the actual memory of struct1 must be a multiple of 8, rounded up to 16 by 15. So sizeof(struct2) results in 16 stuct2 memory distribution concept diagram as follows:

After analyzing this, let’s go to the next level and explore the structure nested structure, how does the memory space calculate the size of memory?

Structure nested structure

Without further comment, let’s define a struct nested within a struct, as follows:

struct LHYStruct2 { double d; int c; short b; char a; }struct2; struct LHYStruct3 { double d; int c; short b; char a; struct LHYStruct2 stru; }struct3; NSLog(@"struct3 memory size: %lu\n struct3 stru member memory size: %lu",sizeof(struct3),sizeof(struct3.stru));Copy the code

Let’s look at the output:

Variable D is of type double, occupying 8 bytes and discharging from 0, i.e. [0-7] variable C is of type int, occupying 4 bytes and discharging from 8, 8 is exactly a multiple of 4. That is, variable B [8-11] is of type short and occupies 2 bytes and is emitted from 12, which is also a multiple of 2. That is, variable A [12-13] is of type char and occupies 1 byte and is emitted from 14. That is, stru [14] is the structure type. The bytes required for variable B, which occupies the largest amount of memory in STRU, are 8, so we must start storing stru with a multiple of 8. Obviously 15 is not a multiple of 8.

Struct internal memory needs to be an integer multiple of the number of bytes of the largest variable in the internal member. The largest member of struct3 is not a stru.The number of bytes it takes up is zero16And the32It is a16Multiple of phi, which is exactly memory alignment. So I’m just going to add another variable of type double to struct2 to verify this point.

struct LHYStruct2 { double d; int c; short b; char a; double e; }struct2; struct LHYStruct3 { double d; int c; short b; char a; struct LHYStruct2 stru; }struct3; NSLog(@"struct3 memory size: %lu\n struct3 stru member memory size: %lu",sizeof(struct3),sizeof(struct3.stru));Copy the code

Take a look at the print result:

From the point of this print results, we draw a conclusion that when the structure nested structure, as members of the data members of the structure of the biggest memory size as members of the external structure of the biggest memory size, rather than as a data member structure size as members of the external structure of the biggest memory size.

Therefore, in the first example of structure nesting, the maximum variable in struct3 is stru, and the maximum memory byte of its member is 8. According to the principle of memory alignment, the actual memory size of struct3 must be an integer multiple of 8, and 32 is exactly an integer multiple of 8, sosizeof(struct3)As a result of the32.

struct3The concept diagram of memory distribution is as follows:

Memory optimization

I don’t know if I noticed before, but when a class has multiple attributes, memory is not exactly aligned with byte alignment, so let’s write a demo to verify that.

LHYStudent *baseStu = [[LHYStudent alloc]init]; Basestu. name = @" 中 国 cabbage "; BaseStu. Sex = @" male "; baseStu.age = 28; BaseStu. Height = 199.9; baseStu.a = 'a'; baseStu.b = 'b'; baseStu.hobby = @"coding"; NSLog(@"-------%@",baseStu);Copy the code

Print the value of the property based on the address of baseStu through LLDB debugging.

We found that the system rearranged the A/B/AGE fields in the same memory block. This should be an internal optimization of apple’s system to avoid memory waste while improving read efficiency and secure access.

16-byte alignment algorithm

There are two types of byte alignment algorithms we know of.

  1. We talked about ALIGN16 in the last article, so I won’t go into it here.
Return (x + size_t(15)) &~ size_t(15); return (x + size_t(15)) &~ size_t(15); return (x + size_t(15)) &~ size_t(15); }Copy the code
  1. Segregated_size_to_fit algorithm
#define SHIFT_NANO_QUANTUM 4 #define SHIFT_NANO_QUANTUM 4 #define SHIFT_NANO_QUANTUM 4 SHIFT_NANO_QUANTUM) // 16 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; 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; }Copy the code

These two lines of code are the heart of the algorithm, so let’s translate it. k = (size + NANO_REGIME_QUANTA_SIZE – 1) >> SHIFT_NANO_QUANTUM; K = [(size + 16-1) right shift 4 bits] slot_bytes = k << SHIFT_NANO_QUANTUM; Slot_bytes = k Move 4 bits to the left For verification, assume that size=20 20+16-1=35 -> 0010 0011 Move 4 bits to the right —–> 0000 0010 and then move 4 bits to the left > 0010 0000 = 32

conclusion

  • Inside objects, between properties, the real alignment is8 bytesAlign, the largest known type consumes only 8 bytes of memory.
  • Outside of objects, between objects, is16 bytesAlignment to improve memory read tolerance.
  • Some types of data will be rearranged in the system for memory optimization.