(iOS)

Before discussing the principle of memory alignment, let’s first introduce the three ways to obtain memory size in iOS

Three ways to get memory size

There are three ways to obtain the memory size:

  • sizeof
  • class_getInstanceSize
  • malloc_size

sizeof

  • 1.sizeofIs aThe operatorIt’s not a function
  • 2. When we use sizeof to calculate the sizeof memory,The incomingThe main object ofThe data typeThis is in the compilerCompilation phaseThe size is determined (at compile time) rather than at run time.
  • 3,sizeofWhat you getThe results ofIs the size of space occupied by the data type

class_getInstanceSize

Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc & Init & New: Alloc

malloc_size

This function gets the actual amount of memory allocated by the system

You can verify our above statement with the output of the following code

#import <Foundation/Foundation.h> #import "LGPerson.h" #import <objc/runtime.h> #import <malloc/malloc.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *objc = [[NSObject alloc] init]; NSLog(@" memory sizeof objc object type: %lu",sizeof(objc)); NSLog(@" actual memory size of objc objects: %lu",class_getInstanceSize([objc class])); NSLog(@" actual memory allocated for objc objects: %lu",malloc_size((__bridge const void*)(objc)); } return 0; } copy codeCopy the code

Here is the printout

conclusion

  • Sizeof: calculates the sizeof memory occupied by a type. It can hold basic data types, objects, and Pointers

    • For something likeintsuchThe basic dataIn terms ofsizeofWhat you get isMemory size used by the data typeDifferent data types use different amounts of memory
    • And for things like NSObject definitionInstance objectsIn terms of itsObject typeIs the essence ofPointer to a struct (struct objc_object), sosizeof(objc)Print isPointer size of object objcWe know thatThe memory size of a pointer is 8, soSizeof (objc) print is 8. Note: Here the 8 bytes are withisaThe pointer has nothing to do with it!!
    • forPointer to theSo sizeof prints 8, because the memory sizeof a pointer is 8,
  • Class_getInstanceSize: Calculates the actual memory size of the object. This varies depending on the property of the class. If a custom class has no custom property and just inherits from NSObject, then the actual memory size of the instance object of the class is 8

  • Malloc_size: Calculates the actual size of memory allocated by the object. This is done by the system. You can see from the print above that the actual size of memory allocated and the actual size of memory used are not equal. Alloc & Init & New source code analysis in the 16-byte alignment algorithm to explain this problem

There is internal alignment of structures

Next, let’s introduce today’s topic, memory alignment, by defining two structures and calculating their memory sizes

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

Here is the output

At first glance, there is no difference between the two structures. The variables and types are the same. The only difference is that they are not defined in the same order. This is actually memory byte alignment in iOS

Memory alignment rules

Each platform-specific compiler has its own default “alignment coefficient” (also called alignment modulus). Programmers can change this coefficient by precompiling the command #pragma pack(n), n=1,2,4,8,16, where n is the “alignment coefficient” you want to specify. In ios, Xcode defaults to #pragma pack(8), which means 8-byte alignment

General principles of memory alignment are mainly three points, you can see iOS- underlying principles 02: Alloc & Init & New source analysis in the description,

The memory alignment principle can be understood as the following two points:

  • [Principle 1] The alignment rules of data members can be interpreted asmin(m, n), wheremsaidThe start position of the current member.nsaidThe number of bits required by the current member. If the conditions are metM divisible n(i.e.m % n == 0),n 从 mLocation starts to store and vice versaLet's see if m plus 1 goes into n, until it is divisible, thus determining the starting position of the current member.
  • [Principle 2] Data members are structures: The structure of a data member when the structure is nested with structuresIts lengthThe size of the memory that is the largest member of an external struct. For example, if a is nested within b, then b has char, int, double, etcIts lengthFor eight
  • 【 Principle 3 】 FinallyThe memory size of the structureIt has to be in the structureMaximum member memory sizeThe insufficiency needs to be made up.

Verify alignment rules

The following table shows the memory size occupied by various data types in ios. The memory size in the structure is calculated according to the corresponding type

We can use the following figure to illustrate why two structuresMyStruct1 & MyStruct2The memory size of the print is inconsistent, as shown in the figure

Calculate the memory size of structure MyStruct1

Calculate the memory size of MyStruct1 according to memory alignment rules. The detailed procedure is as follows:

  • Variable aAccounts for:1Bytes, starting at 0, at this pointMin (0,1), i.e.,0 to store a
  • Variable bAccounts for:8Bytes, starting at 1, at this pointMin (1,8)1 doesn’t go into 8, so I keep moving backwards. I knowMin (8,8)Start at 8, i.e8-15 store b
  • Variable cAccounts for:4Bytes, starting at 16, at this pointMin (16,4)16 goes into 4, which is equal to16-19 storage c
  • The variable dAccounts for:2Bytes, starting at 20, at this pointmin(20, 2)20 goes into 2, which is 220-21 store d

Therefore, the required memory size of MyStruct1 is 15 bytes, and the maximum number of bytes in MyStruct1 is 8, so the actual memory size of MyStruct1 must be an integer multiple of 8, and 18 is rounded up to 24, mainly because 24 is an integer multiple of 8. So sizeof(MyStruct1) is 24

Calculate the memory size of structure MyStruct2

Calculate the memory size of MyStruct2 according to memory alignment rules. The detailed procedure is as follows:

  • Variable bAccounts for:8Bytes, starting at 0, at this pointMin (0,8), i.e.,0 to 7 b storage
  • Variable cAccounts for:4Bytes, starting at 8, at this pointMin (8,4)8 is divisible into 48-11 c storage
  • The variable dAccounts for:2Bytes, starting at 12, at this pointmin(12, 2)12 goes into 2, which is 212-13 store d
  • Variable aAccounts for:1Bytes, starting at 14, at this pointMin (14,1), i.e.,14 to store a

Therefore, the required memory size of MyStruct2 is 15 bytes, while the maximum number of bytes in MyStruct1 is 8, so the actual memory size of MyStruct2 must be an integer multiple of 8, and 15 is round up to 16, mainly because 16 is an integer multiple of 8. So sizeof of MyStruct2 is 16

Structure nested structure

The above two structures are simple to define the data members. The following is a more complex calculation of the memory size of the nested structures within the structure

  • First, define a structure MyStruct3, and nest MyStruct2 inside MyStruct3, as shown below
//1, struct mystruct {double b; //8 bytes int c; //4 bytes short d; //2 bytes char a; //1 byte struct mystruct STR; }Mystruct3; NSLog(@"Mystruct3: %lu", sizeof(Mystruct3)); NSLog(@"Mystruct3 member size: %lu", sizeof(mystruct3.str)); Copy the codeCopy the code

The printed result is shown below

  • Analysis of theMystruct3Memory calculation of

According to memory alignment rules, the calculation process of Mystruct3 memory size is analyzed step by step

- 'variable b' : takes up '8' bytes, starting from 0, when 'min (0, 8)', that is, '0-7' stores B '-' variable c ': takes up' 4 ', starting from 8, when 'min (8, 4)', that is, '8-11' stores C '-' variable d ': 2 bytes, starting at 12, min(12, 2), 20 is divisible by 2, that is, '12-13' stores d '-' variable a: 1 bytes, starting at 14, min(14, 1), that is, 14 stores a '-' structure member STR: STR is a struct. According to memory alignment2, a struct member is stored from an integer multiple of its internal maximum member size. In MyStruct2, the maximum member size is 8, so STR must start at an integer multiple of 8. 16 is an integer multiple of 8, complying with memory alignment rules, so '16-31 stores STR' copy codeCopy the code

Therefore, the memory size required by MyStruct3 is 32 bytes, and the maximum variable in MyStruct3 is STR, and the maximum number of member memory bytes is 8. According to the principle of memory alignment, the actual memory size of MyStruct3 must be an integer multiple of 8, and 32 is exactly an integer multiple of 8. So sizeof(MyStruct3) is 32

The memory storage is shown in the following figure

Secondary validation

Just to be sure, let’s define another structure to verify our calculation of the size of the structure’s nested memory

struct Mystruct4{ int a; // struct Mystruct5 (0,4) -- (0,1,2,3) struct Mystruct5 (0,4) -- (0,1,2,3) struct Mystruct5{ Store double B starting at 8; / / min (8, 8) - 8 bytes (8,9,10,11,12,13,14,15) short c; //1 byte, start from 16, min (16, 1) -- (16, 17)}Mystruct5; }Mystruct4; Copy the codeCopy the code

Analysis of the following

  • Variable A: 4 bytes, starting from 0, min (0, 4), that is, 0-3 stores a

  • Mystruct5: start from 4, according to memory alignment principle 2, that is, the storage starting position must be the largest integer multiple (the largest member is 8), min (4, 8) cannot be divisible, continue to move back until 8, min (8, 8) meets, start from 8 to store the variables of structure Mystruct5

    • Variable bAccounts for:8Bytes, starting at 8,Min (8,8)Phi is divisible by phi8-15 store b
    • Variable cAccounts for:2Bytes, starting at 16,Min (16,2)Phi is divisible by phi16 and 17 storage c

Therefore, the memory size required in Mystruct4 is 18 bytes. According to principle 2 of memory for it, the actual memory sizeof Mystruct4 must be an integer multiple of the maximum member b in Mystruct5, that is, it must be an integer multiple of 8, so the result of sizeof(Mystruct4) is 24

Here is a print of the results to confirm24This memory size

Memory optimization (property rearrangement)

MyStruct1 adds 9 bytes by virtue of memory byte alignment principle, while MyStruct2 only needs to complete one byte to satisfy the byte alignment rule by virtue of memory byte alignment principle and the combination of 4+2+1. A conclusion can be reached here that the memory size of the structure is related to the order of the memory size of its members

  • If it isData members in the structureIs based onMemory goes from small to largeAccording to the memory alignment rules to calculate the structure memory size, need to add a larger memory padding, namely memory placeholder, to meet the memory alignment rules.Waste memory
  • If it isData members in the structureIs based onMemory from large to smallTo calculate the structure memory size according to memory alignment rules, we just needA little memory paddingCan satisfy the stacking alignment rule, this way isUsed in apples, using space for time,The properties of a class are rearranged to optimize memory

Take the following example to illustrate attribute rearrangement in apple, that is, memory optimization

  • Define a custom CJLPerson class and define several properties,
@interface CJLPerson : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *nickName; // @property (nonatomic, copy) NSString *hobby; @property (nonatomic, assign) int age; @property (nonatomic, assign) long height; @property (nonatomic) char c1; @property (nonatomic) char c2; @end @implementation CJLPerson @end copy codeCopy the code
  • Create an instance object of CJLPerson in Main and assign values to several of its properties
int main(int argc, char * argv[]) { @autoreleasepool { CJLPerson *person = [CJLPerson alloc]; person.name = @"CJL"; person.nickName = @"C"; person.age = 18; person.c1 = 'a'; person.c2 = 'b'; NSLog(@"%@",person); } return 0; } copy codeCopy the code
  • The breakpoint debugs the Person to find the value of the property based on the CJLPerson object address

    • Find out by addressname & nickName

    • When we look for data like age at address 0x0000001200006261, we find that it is garble. The reason why we cannot find the value here is that the memory of the age, C1, and C2 attributes is rearranged in Apple, because the age type is 4 bytes, and the C1 and C2 char types are 1 byte each. By 4+1+1, it is stored in the same block of memory in an 8-byte aligned, incomplete way,

      • The age is read through0x00000012
      • The read of C1 passed0x61(A’s ASCII code is 97)
      • The read of C2 passes0x62(B’s ASCII code is 98)

The following figure shows the CJLPerson memory distribution

Note: 1, char is read as ASCII. 2, address 0x0000000000000000 indicates that there are still properties in person that have not been assigned

conclusion

So, here’s a summary of apple’s memory alignment idea:

  • Most memory is read through fixed blocks of memory,
  • Although we have memory alignment in memory, butNot all memory can be wasted, apple will automatically rearrange the properties toOptimization of memory

Byte Alignment How much byte alignment is used?

So far, we’ve mentioned both 8-byte alignment and 16-byte alignment, so which byte alignment should we use?

We can do this by looking at the source for class_getInstanceSize in ObjC4

/** * Returns the size of instances of a class. * * @param cls A class object. * * @return The size in bytes of instances of the class \e cls, Or \c 0 if \e CLS is \c Nil. */ OBJC_EXPORT size_t class_getInstanceSize(Class _Nullable CLS) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); ⬇️ size_t class_getInstanceSize(Class CLS) {if (! cls) return 0; return cls->alignedInstanceSize(); } ⬇️ // Class's ivar size rounded up to a pointer-size boundary. Uint32_t alignedInstanceSize() const {return word_align(unalignedInstanceSize()); } ⬇️ static inline uint32_t word_align(uint32_t x) {//x+7 & (~7) --> 8 bytes align return (x + WORD_MASK) & ~WORD_MASK; } // Where WORD_MASK is # define WORD_MASK 7UL copy codeCopy the code

According to the source code:

  • For aobjecttheTrue alignment 是 8-byte alignment, 8 bytes of alignment is sufficient for the object
  • The Apple system uses 16-byte aligned memory to prevent fault tolerance, mainly because the memory of the two objects will be next to each other when using 8-byte alignment, which is relatively compact, while the 16-byte alignment is relatively loose, which will facilitate the expansion of Apple in the future.

Summarize the methods of obtaining memory size mentioned above

  • class_getInstanceSize: is to use8-byte alignmentRefers to the object’s property memory size
  • malloc_size: in this paper,16-byte alignmentThe actual memory size allocated to the object must be an integer multiple of 16

Memory alignment algorithm

There are two known algorithms for alignment of 16-byte memory

  • allocSource code analysisalign16:
  • mallocSource code analysissegregated_size_to_fit

Align16:16-byte alignment algorithm

The idea of this algorithm is already mentioned in ios-Underlying Principles 02: Alloc & Init & New source analysis

static inline size_t align16(size_t x) { return (x + size_t(15)) & ~size_t(15); } copy codeCopy the code

Segregated_size_to_fit: 16-byte alignment algorithm

#define SHIFT_NANO_QUANTUM 4 #define NANO_REGIME_QUANTA_SIZE (1 << 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; // 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; } copy codeCopy the code

Algorithm principle: K + 15 >> 4 << 4, where the right shift 4 + left shift 4 is equivalent to the next 4 bits wipe zero, with K /16 * 16, is a 16 byte alignment algorithm, less than 16 is 0

Take k = 2 as an example