WWDC2020 runtime optimization
-
Video watching address: developer.apple.com/videos/play… (Best opened in Safari)
-
LLVM source address: github.com/apple/llvm-… There is no need to change any code, and no need to learn any new API. This time, it is mainly about runtime’s memory optimization. And in this environment we don’t have to change the APP to run faster and more efficiently than before.
-
Class structure
- Metaclass
- Superclass
- Flags
- Method cache
Clean Memory and Dirty Memory
-
Clean Memory
-
Clean Memory Memory that does not change after loading
-
Class_ro_t is clean memory because it’s read-only and it doesn’t align with memory
-
Clean Memory can be removed to save more memory space, because if you need clean memory, the system can reload it from disk
-
Class_ro_t Memory structure
Flags
Size
Name
Methods
Protocols
Ivars
Properties
-
Dirty Memory
-
Dirty Memory refers to the Memory that changes while a program is running.
-
The structure of a class becomes Dirty Memory once it is used because new data is written to it at runtime. Such as adding a method to a class or loading a subclass or superclass of a class,
-
This is class_rw_t.
Class_rw_t structure
Flags
First Subclass
Next Sibling Class
Methods
Properties
Protocols
Demangled Name
First Subclass, Next Subling Class
: contains information that is generated only at run timeFirst Subclass, Next Subling Class
, all classes will become oneTree structure
Is byFirst Subclass
andNext Subling Class
Pointer, which allows the runtime to traverse all the classes currently in useDemangled Name
: This field is used infrequently and is only used in Swift.
conclusion
Dirty memory is much more valuable than clean memory and must be present as long as it is running. By separating out the data that will not be changed, you can store most of the class data in clean memory to continuously improve the performance of your program.
Class_rw_t optimization
When a class is first used, Runtime allocates extra storage for it. The runtime allocation is class_rw_t. Class_rw_t is used to read and write data. In this data structure, new data is stored that will only be generated at run time
Question: Whymethods
.attribute
inclass_ro_t
When,class_rw_t
Must havemethods
.attribute
?
- Because they can be changed at run time
- when
category
When loaded, it can add new ones to the classmethods
- through
runtime API
Added to the hand movement classattribute
andmethods
class_ro_t
It’s read-only, so we need to be able toclass_rw_t
To keep track of these things
Break upclass_rw_t
And extract theclean memory
If only 10% of the classes need to be modified or added when reading and writing properties and methods, then 90% of the classes can be said to be unmodified, so you can split class_rw_t as shown in the figure
Result: The size of class_rw_t is halved
For classes that need to modify memory and need additional information, we can allocate one of these extended records and slide it into the class for its use. The illustration below
conclusion
- When a class is used
category
Then the class at this time hasclass_rw_t
If no classification is used, then the class is a pureclass_ro_t
The structure of the. - The optimization of class structure is actually the most important separation
class_ro_t
andclass_rw_t
Optimization, in fact, is rightclass_rw_t
Parts that are not commonly used are peeled off. If you need to use this part fromextension
Record and slide into the class for its use
Second, the variable
Member variables and instance variables
- in
Objective-C
Variables written within the braces of a class declaration are called member variables, for exampleint a
.NSObject *obj
- Member variables are variables that are used within a class without contact with the outside world
The instance variables
- The data type of the variable is not
The basic data
Type and is aclass
The variable is called an instance variable, for exampleNSObject *obj
- Member variables contain instance variables
The difference between member variables and attributes
- Member variables: Declarations of variables with no other operations at the bottom
- Properties: The system will automatically add them at the bottom level
_ the property name
Variables are generated simultaneouslysetter
andgetter
methods
Attribute to deposit
- To explore the source code
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif union { const uint8_t * ivarLayout; Class nonMetaclass; }; explicit_atomic<const char *> name; // With ptrauth, this is signed if it points to a small list, but // may be unsigned if it points to a big list. void *baseMethodList; protocol_list_t * baseProtocols; // Const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties;Copy the code
Three, coding
SEL
andIMP
Relationship between
SEL
: Method NumberIMP
: The address of a function pointer
Official type code
-
Long double is not supported in Objective-C. @encode(long double) returns d, which is the same as the encoding value of double
-
To obtain the Type encoding diagram, go to Xcode –> Command + Shift +0–> search for ivar_getTypeEncoding–> Click Type Encodings
202107 -- 31 22:40:39.479316+0800Alloc_ exploration [3177:69006] char --> c
202107 -- 31 22:40:39.479534+0800Alloc_ exploration [3177:69006] int --> i
202107 -- 31 22:40:39.479708+0800Alloc_ exploration [3177:69006] short --> s
202107 -- 31 22:40:39.479851+0800Alloc_ exploration [3177:69006] long --> q
202107 -- 31 22:40:39.480077+0800Alloc_ exploration [3177:69006] long long --> q
202107 -- 31 22:40:39.480296+0800Alloc_ exploration [3177:69006] float --> f
202107 -- 31 22:40:39.480427+0800Alloc_ exploration [3177:69006] double --> d
Copy the code
- The return value in the comparison table is consistent with the encoding type
Four, objc_setProperty and memory offset
- Define NBPerson class
@interface NBPerson : NSObject
{
NSString * newName;
NSObject * objc;
}
@property(nonatomic.copy)NSString * name;
@property(nonatomic.strong)NSString * nickName;
@property(nonatomic.assign)NSInteger age;
@end
@implementation NBPerson
@end
Copy the code
- generate
.cpp
File and find.
// @implementation NBPerson
static NSString * _I_NBPerson_name(NBPerson * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_NBPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long.id.bool.bool);
static void _I_NBPerson_setName_(NBPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NBPerson, _name), (id)name, 0.1); }
static NSString * _I_NBPerson_nickName(NBPerson * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_NBPerson$_nickName)); }
static void _I_NBPerson_setNickName_(NBPerson * self, SEL _cmd, NSString *nickName) { (*(NSString((* *)char *)self + OBJC_IVAR_$_NBPerson$_nickName)) = nickName; }
static NSInteger _I_NBPerson_age(NBPerson * self, SEL _cmd) { return(* (NSInteger((*)char *)self + OBJC_IVAR_$_NBPerson$_age)); }
static void _I_NBPerson_setAge_(NBPerson * self, SEL _cmd, NSInteger age) { (*(NSInteger((*)char *)self + OBJC_IVAR_$_NBPerson$_age)) = age; }
// @end
Copy the code
LWName
The underlying attribute is passedobjc_setProperty
The implementation,LWNickname
andage
This is done by memory offset
Find the LLVM source LLVM – project – next/clang/lib/CodeGen CGObjCMac. CPP code
llvm::FunctionCallee getSetPropertyFn() {
CodeGen::CodeGenTypes &Types = CGM.getTypes();
ASTContext &Ctx = CGM.getContext();
// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
CanQualType Params[] = {
IdType,
SelType,
Ctx.getPointerDiffType()->getCanonicalTypeUnqualified(),
IdType,
Ctx.BoolTy,
Ctx.BoolTy};
llvm::FunctionType *FTy =
Types.GetFunctionType(
Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
}
Copy the code
CGM.CreateRuntimeFunction(FTy, "objc_setProperty")
, to create aobjc_setProperty
Methods. Reasoning from the bottom to the top, global searchgetSetPropertyFn()
To find the source code
llvm::FunctionCallee GetPropertySetFunction() override {
return ObjCTypes.getSetPropertyFn();
}
Copy the code
- Search for GetPropertySetFunction() and find it in cgobject.cpp
PropertyImplStrategy strategy(CGM, propImpl);
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {// Omit the code
}
case PropertyImplStrategy::GetSetProperty:
case PropertyImplStrategy::SetPropertyAndExpressionGet: {
llvm::FunctionCallee setOptimizedPropertyFn = nullptr;
llvm::FunctionCallee setPropertyFn = nullptr;
if (UseOptimizedSetter(CGM)) {// Omit the code
}
else {
setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();
if(! setPropertyFn) {CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
return; }}Copy the code
- According to the
switch
conditionsPropertyImplStrategy
Type of callGetPropertySetFunction()
PropertyImplStrategy
There are two typesGetSetProperty
orSetPropertyAndExpressionGet
The next step is to know when to assign a value to the policy
/// Pick an implementation strategy for the given property synthesis.
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM.const ObjCPropertyImplDecl *propImpl) {
const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();
IsCopy = (setterKind == ObjCPropertyDecl::Copy);
IsAtomic = prop->isAtomic();
HasStrong = false; // doesn't matter here.
// Evaluate the ivar's size and alignment.
ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
QualType ivarType = ivar->getType();
auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
IvarSize = TInfo.Width;
IvarAlignment = TInfo.Align;
// If we have a copy property, we always have to use setProperty.
// If the property is atomic we need to use getProperty, but in
// the nonatomic case we can just use expression.
if (IsCopy) {
Kind = IsAtomic ? GetSetProperty : SetPropertyAndExpressionGet;
return;
}
Copy the code
// Handle retain.
if (setterKind == ObjCPropertyDecl::Retain) {
// In GC-only, there's nothing special that needs to be done.
if (CGM.getLangOpts().getGC() == LangOptions::GCOnly) {
// fallthrough
// In ARC, if the property is non-atomic, use expression emission,
// which translates to objc_storeStrong. This isn't required, but
// it's slightly nicer.
} else if (CGM.getLangOpts().ObjCAutoRefCount && ! IsAtomic) {// Using standard expression emission for the setter is only
// acceptable if the ivar is __strong, which won't be true if
// the property is annotated with __attribute__((NSObject)).
// TODO: falling all the way back to objc_setProperty here is
// just laziness, though; we could still use objc_storeStrong
// if we hacked it right.
if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)
Kind = Expression;
else
Kind = SetPropertyAndExpressionGet;
return;
// Otherwise, we need to at least use setProperty. However, if
// the property isn't atomic, we can use normal expression
// emission for the getter.
} else if(! IsAtomic) { Kind = SetPropertyAndExpressionGet;return;
// Otherwise, we have to use both setProperty and getProperty.
} else {
Kind = GetSetProperty;
return; }}Copy the code
- LLVM source code process:
objc_setProperty
->getSetPropertyFn
->GetPropertySetFunction
->PropertyImplStrategy
->IsCopy(determine the copy keyword)
- Conclusion: The setter for a property that uses the copy keyword is used at the bottom level regardless of whether the property is atomic or non-atomic
objc_setProperty
The implementation,strong
The keyword cannot pass the final judgment, need to passFirst address + memory offset
Is implemented in the following way.