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

  1. Metaclass
  2. Superclass
  3. Flags
  4. 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

  1. Flags
  2. Size
  3. Name
  4. Methods
  5. Protocols
  6. Ivars
  7. 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

  1. Flags
  2. First Subclass
  3. Next Sibling Class
  4. Methods
  5. Properties
  6. Protocols
  7. 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 structureIs byFirst SubclassandNext Subling ClassPointer, which allows the runtime to traverse all the classes currently in use
  • Demangled 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.attributeinclass_ro_tWhen,class_rw_tMust havemethods.attribute?
  • Because they can be changed at run time
  • whencategoryWhen loaded, it can add new ones to the classmethods
  • throughruntime APIAdded to the hand movement classattributeandmethods
  • class_ro_tIt’s read-only, so we need to be able toclass_rw_tTo keep track of these things
Break upclass_rw_tAnd 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 usedcategoryThen the class at this time hasclass_rw_tIf no classification is used, then the class is a pureclass_ro_tThe structure of the.
  • The optimization of class structure is actually the most important separationclass_ro_tandclass_rw_tOptimization, in fact, is rightclass_rw_tParts that are not commonly used are peeled off. If you need to use this part fromextensionRecord and slide into the class for its use

Second, the variable

Member variables and instance variables

  • inObjective-CVariables 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 notThe basic dataType and is aclassThe 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 nameVariables are generated simultaneouslysetterandgettermethods

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

SELandIMPRelationship between

  • SEL: Method Number
  • IMP: 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.cppFile 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
  • LWNameThe underlying attribute is passedobjc_setPropertyThe implementation,LWNicknameandageThis 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_setPropertyMethods. 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 theswitchconditionsPropertyImplStrategyType of callGetPropertySetFunction()
  • PropertyImplStrategyThere are two typesGetSetPropertyorSetPropertyAndExpressionGetThe 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-atomicobjc_setPropertyThe implementation,strongThe keyword cannot pass the final judgment, need to passFirst address + memory offsetIs implemented in the following way.