IOS underlying principles + reverse article summary

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 the code

Here is the printout

conclusion

  • sizeof: calculateMemory size occupied by type, where you can putBasic data types, objects, Pointers
    • For basic data such as int, sizeof captures the memory sizeof the data type, which varies by type

    • For instance objects like NSObject, the object type is essentially a pointer to a struct (struct objc_object), so sizeof(objc) prints the sizeof the pointer to objc, and we know that a pointer has a memory sizeof 8, So sizeof(objc) print is 8. Note: the 8 bytes here have nothing to do with the ISA pointer!!

    • For Pointers, sizeof prints 8, because the memory sizeof a pointer is 8,

  • class_getInstanceSize: calculateThe actual memory size of the objectThis requiresDepending on the properties of the classIf the custom class has no custom properties and just inherits from NSObject, then the actual memory size of the instance object of the class is 8, which can be simply interpreted as8-byte alignment
  • malloc_size: calculateThe actual memory allocated by the object, this is done by the system, you can see from the above print results, the actual allocated and the actual occupied memory size is not equal, this problem can be passedIOS – Underlying principles 02: Alloc & Init & New source code analysisIn the16-byte alignmentAlgorithm to explain the 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 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),nmLocation 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 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, which complies with memory alignment rules, so '16-31 stores STR'Copy 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 code

Analysis of the following

  • Variable aAccounts for:4Bytes, from the0To start,Min (0,4), i.e.,0-3 store a
  • Structure Mystruct5From:4First, according to memory alignment principle two, namelyStorage start location must be the largest integer multiple (maximum member is 8)And min (4,8) is not divisible, so I keep going back until I get to 8, and min (8, 8) satisfies,Store the variables of the structure Mystruct5 from 8
    • 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 the data members in the structure are defined in order from small to large memory, and the size of the structure is calculated according to the memory alignment rules, a larger memory padding is needed to meet the memory alignment rules, which is a waste of memory

  • If it is a structure in the data member is defined according to the memory from big to small order, according to the memory alignment rules to calculate the structure of the body size, we only need to supplement the small amount of memory padding can satisfy the stockpiling alignment rules, this approach is used in apple, use of space, in time, will take place in the class attribute rearrangement, to achieve the goal of optimization of 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 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 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

    • As we go through0x0000001200006261Address, age, etc., and it’s garbled, and the reason why I can’t find the value here isApple has rearranged memory for age, C1, and C2 attributes, because the age type is 4 bytes, and the C1 and C2 char types are 1 byte each4 + 1 + 1In accordance with8-byte alignment, the way to fill the shortageStored in the same block of memory,
      • 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; } # define WORD_MASK 7ULCopy the code

According to the source code:

  • For aobjecttheTrue alignment8-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 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 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