The technical points described in this paper are relatively extreme and novel package size optimization techniques. The paper will analyze the impact of OC code on package size from the level of binary and assembly instructions. The following three aspects will be discussed:
- Analyze the impact of OC code on package size at the binary level
- Code tips for optimizing package size
- Summarize the benefits of various TIPS
Analyze the effect of encoding on package size from the binary file level
Taking analyzing attributes as an example, this paper introduces an analysis method of analyzing the effect of encoding on package size from the binary file level.
At the bottom of the article, there are qr codes of Douyin Livestream.
Experimental background: The real machine iphone11, iOS13.5.1, release, build setting default Settings, linkmap file using ARM64 experiment.
O Level comparison
Compare the ViewControler with no properties, one property, or two properties:
@interface ViewController : UIViewController
@property (nonatomic, strong) NSString *myString1;
@property (nonatomic, strong) NSString *myString2;
@end
Copy the code
Viewcontroller.o occupies bytes
- No attribute 0.36K
- An attribute of 0.67K
- Two properties 0.92 K
When a class decreases by an attribute, it decreases by 0.25K, assuming that the class has other attributes.
Therefore, we can conclude that an attribute occupies 0.25K.
See tips for which section symbols in linkmap file belong
# Sections:
# Address Size Segment Section
0x10000623C 0x00000224 __TEXT __text
0x100006460 0x00000090 __TEXT __stubs
Copy the code
The address range of __text. text is 0x10000623C-0x100006460, so the addresses corresponding to Symbols in the linkmap file, if within this range, belong to __text.text.
0x10000623C 0x00000034 [ 1] -[ViewController viewDidLoad]
0x100006270 0x00000010 [ 1] -[ViewController myString1]
0x100006280 0x00000014 [ 1] -[ViewController setMyString1:]
0x100006294 0x00000010 [ 1] -[ViewController myString2]
0x1000062A4 0x00000014 [ 1] -[ViewController setMyString2:]
0x1000062B8 0x00000040 [ 1] -[ViewController .cxx_destruct]
0x1000062F8 0x00000008 [ 2] -[AppDelegate application:didFinishLaunchingWithOptions:]
0x100006300 0x0000008C [ 2] -[AppDelegate application:configurationForConnectingSceneSession:options:]
0x10000638C 0x00000004 [ 2] -[AppDelegate application:didDiscardSceneSessions:]
0x100006390 0x00000080 [ 3] _main
0x100006410 0x00000004 [ 4] -[SceneDelegate scene:willConnectToSession:options:]
0x100006414 0x00000004 [ 4] -[SceneDelegate sceneDidDisconnect:]
0x100006418 0x00000004 [ 4] -[SceneDelegate sceneDidBecomeActive:]
0x10000641C 0x00000004 [ 4] -[SceneDelegate sceneWillResignActive:]
0x100006420 0x00000004 [ 4] -[SceneDelegate sceneWillEnterForeground:]
0x100006424 0x00000004 [ 4] -[SceneDelegate sceneDidEnterBackground:]
0x100006428 0x00000010 [ 4] -[SceneDelegate window]
0x100006438 0x00000014 [ 4] -[SceneDelegate setWindow:]
0x10000644C 0x00000014 [ 4] -[SceneDelegate .cxx_destruct]
0x100006460 0x0000000C [ 5] _NSStringFromClass
Copy the code
The symbols 0x10000623C through 0x100006460 belong to __text.__text.
Explore what symbols an attribute produces
The section size is in bytes
__TEXT section:
section | __text | __objc_methname | __objc_methtype | __objc_classname | __cstring | _unwind_info |
---|---|---|---|---|---|---|
There is no attribute | 412 | 3396 | 2831 | 112 | 144 | 100 |
An attribute | 468 | 3431 | 2843 | 112 | 183 | 100 |
Two attributes | 548 | 3466 | 2843 | 114 | 222 | 112 |
__DATA section:
Section | __objc_const | __objc_ivar |
---|---|---|
There is no attribute | 4904 | 4 |
An attribute | 5040 | 8 |
Two attributes | 5136 | 12 |
Analysis:
$1 represents a comparison between no attribute and one attribute
$2 represents a comparison of one and two properties
The TEXT. The TEXT analysis
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__TEXT | __text | 412 | 468 | 548 | 56 | 80 |
Linkmap File difference:
// ViewController has no properties. The difference is that there is no -[viewController.cxx_destruct] symbol // a property: 16 + 20 + 20 = 56(corresponding to $1 above) 0x100006318 0x00000010 [1] -[ViewController myString1] 0x100006328 0x00000014 [1] -[ViewController setMyString1:] 0x10000633C 0x00000014 [1] -[ViewController. cxx_destruct] // Two properties 16 + 20 + 16 + 20 136-56 = 80(corresponding to $2 above) 0x100006270 0x00000010 [1] -[ViewController myString1] 0x100006280 0x00000014 [ 1] -[ViewController setMyString1:] 0x100006294 0x00000010 [ 1] -[ViewController myString2] 0x1000062A4 0x00000014 [ 1] -[ViewController setMyString2:] 0x1000062B8 0x00000040 [ 1] -[ViewController .cxx_destruct]Copy the code
Confusion:.cxx_destruct has a different size. Here are two screenshots to answer this question, because the internal implementation of.cxx_destruct is different, resulting in a different number of instructions in the assembly.
An attribute.cxx_destruct
Internal implementation of assembly code:
Two attributes.cxx_destruct
Internal implementation of assembly code:
TEXT. Objc_methname analysis
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__TEXT | __objc_methname | 3396 | 3431 | 3466 | 35 | 35 |
0x1000065FC 0x0000000A [1] Literal String: myString1 0x100006606 0x0000000E [1] Literal String: setMyString1: 0x100006614 0x0000000E [ 1] literal string: .cxx_destruct 0x100006622 0x0000000B [ 1] literal string: _myString1 // Two attributes 0x1000065A4 0x0000000A [1] Literal String: myString1 0x1000065AE 0x0000000E [1] Literal String: setMyString1: 0x1000065BC 0x0000000A [ 1] literal string: myString2 0x1000065C6 0x0000000E [ 1] literal string: setMyString2: 0x1000065D4 0x0000000E [ 1] literal string: .cxx_destruct 0x1000065E2 0x0000000B [ 1] literal string: _myString1 0x1000065ED 0x0000000B [ 1] literal string: _myString2Copy the code
For time to erode, there is no time for emotional arguments.
// Text.text
0x1000062B8 0x00000040 [ 1] -[ViewController .cxx_destruct]
0x10000644C 0x00000014 [ 4] -[SceneDelegate .cxx_destruct]
// TEXT.objc_methname
0x1000065D4 0x0000000E [ 1] literal string: .cxx_destruct
<<dead>> 0x0000000E [ 4] literal string: .cxx_destruct
Copy the code
All the unnecessary Symbols in the.o file are Dead for time to view.
For example, [4] literal String:.cxx_destruct is an unnecessary symbol. [4] means scenedelegate. o is unnecessary because there is no need for a symbol with the same name for the objc_methName section.
// linkfile map, [4] Scenedelegate. o [4] /Users/xxxx/Library/Developer/Xcode/DerivedData/PacketSize-athynpqwkehwhtfsynewxvtjyvdz/Build/Intermediates.noindex/Pack etSize.build/Release-iphoneos/PacketSize.build/Objects-normal/arm64/SceneDelegate.oCopy the code
Xcode contains Dead Stripped Symbols.
TEXT.objc_methtype
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__TEXT | __objc_methtype | 2831 | 2843 | 2843 | 12 | 0 |
0x1000073CF 0x00000008 [1] Literal String: @16@0:8 0x1000073D7 0x0000000B [1] Literal String: v24@0:8@16 0x1000073E2 0x0000000C [1] Literal String: @"NSString" // In the same dead-stripped Symbols also have @16@0:8, v24@0:8@16Copy the code
TEXT. Cstrings analysis
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__TEXT | __cstring | 144 | 183 | 222 | 39 | 39 |
// An attribute 10 + 29 = 39 0x100007EE2 0x0000000A [1] Literal String: myString1 0x100007EEC 0x0000001D [1] Literal String: T@"NSString",&,N,V_myString1 // Two attributes 0x100007EAF 0x0000000A [1] Literal String: myString1 0x100007EB9 0x0000001D [ 1] literal string: T@"NSString",&,N,V_myString1 0x100007ED6 0x0000000A [ 1] literal string: myString2 0x100007EE0 0x0000001D [ 1] literal string: T@"NSString",&,N,V_myString2Copy the code
TEXT. Objc_classname analysis
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__TEXT | __objc_classname | 112 | 112 | 114 | 0 | 2 |
Two attributes, two bytes more than the others, we don't know how these two bytes are aligned, byte alignment? 0x100007322 0x0000000F [ 1] literal string: ViewController 0x100007331 0x00000002 [ 1] literal string: 0x100007333 0x0000000C [ 2] literal string: AppDelegate 0x10000733F 0x00000016 [ 2] literal string: UIApplicationDelegate 0x100007355 0x00000009 [ 2] literal string: NSObject 0x10000735E 0x0000000E [ 4] literal string: SceneDelegate 0x10000736C 0x00000016 [ 4] literal string: UIWindowSceneDelegate 0x100007382 0x00000010 [ 4] literal string: UISceneDelegate 0x100007392 0x00000002 [ 4] literal string:Copy the code
DATA. Objc_const analysis
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__DATA | __objc_const | 4904 | 5040 | 5136 | 136 | 96 |
No attribute 0x100008110 0x00000020 [1] __OBJC_$_INSTANCE_METHODS_ViewController an attribute 40 + 24 + (104-32) = 136 0x100008178 0x00000028 [ 1] __OBJC_$_INSTANCE_VARIABLES_ViewController 0x100008110 0x00000068 [ 1] __OBJC_$_INSTANCE_METHODS_ViewController 0x1000081A0 0x00000018 [1] + (72 - 40) + (40 - 24) = 96 0x100008110 0x00000098 [ 1] __OBJC_$_INSTANCE_METHODS_ViewController 0x1000081A8 0x00000048 [ 1] __OBJC_$_INSTANCE_VARIABLES_ViewController 0x1000081F0 0x00000028 [ 1] __OBJC_$_PROP_LIST_ViewControllerCopy the code
DATA. Objc_ivar analysis
segment | section | There is no attribute | An attribute | Two attributes | The $1 | $2 |
---|---|---|---|---|---|---|
__DATA | __objc_ivar | 4 | 8 | 12 | 4 | 4 |
One attribute 0x1000094B0 0x00000004 [1] _OBJC_IVAR_$_ViewController._myString1 Two attributes 0x100009510 0x00000004 [1] _OBJC_IVAR_$_ViewController._myString1 0x100009514 0x00000004 [ 1] _OBJC_IVAR_$_ViewController._myString2Copy the code
Conclusion: From the above introduction, we have mastered the analysis method of “analyzing the effect of encoding on package size from the binary file level”. With this method, we can continue to analyze the effect of “function, direct, block” on package size.
Code Tips for optimizing package size
Property dynamic
Explore the effect of adding @dynamic to attributes on binaries in the same way as above, using the symbol analysis of the Link File Map:
@property (nonatomic, copy) NSString *myString1; @endCopy the code
The TEXT. The TEXT analysis
segment | section | There is no dynamic | A dynamic | To compare |
---|---|---|---|---|
__TEXT | __text | 412 | 468 | 56 |
// Add dynamic modifier without the following part // 16 + 20 + 20 = 56 0x100006318 0x00000010 [1] -[ViewController myString1] 0x100006328 0x00000014 [ 1] -[ViewController setMyString1:] 0x10000633C 0x00000014 [ 1] -[ViewController .cxx_destruct]Copy the code
Adding @dynamic modifiers does not generate getters, setters, and instance variables. There is no need to execute nil code in dealloc.
TEXT. Objc_methname analysis
segment | section | There is no dynamic | A dynamic | To compare |
---|---|---|---|---|
__TEXT | __objc_methname | 3431 | 3396 | 35 |
// 10 + 14 + 11 = 35 0x1000065FC 0x0000000A [1] Literal String: myString1 0x100006606 0x0000000E [ 1] literal string: setMyString1: 0x100006622 0x0000000B [ 1] literal string: _myString1Copy the code
Note Adding @dynamic does not generate getters and setters, and there is no instance variable, so there is no need to generate the corresponding constant symbol.
TEXT. Objc_methtype analysis
segment | section | There is no dynamic | A dynamic | To compare |
---|---|---|---|---|
__TEXT | __objc_methtype | 2831 | 2843 | 12 |
// Add dynamic modifier without the following symbol // But this cannot be counted when calculating the benefit of adding dynamic, because an item is normally 0x1000073E2 0x0000000C [1] literal String of these symbols: @"NSString"Copy the code
TEXT. Cstrings analysis
segment | section | There is no dynamic | A dynamic | To compare |
---|---|---|---|---|
__TEXT | __cstring | 173 | 183 | 10 |
An attribute 0x100007EEC 0x0000001D [1] Literal String: T@"NSString",&,N,V_myString1 Dynamic attribute 0x100007EF9 0x00000013 [1] Literal String: T@"NSString",&,D,NCopy the code
[T@”NSString”,&,N,V_myString1] and [T@”NSString”,&,D,N] are property type strings. Property type stringApple is described in the official documentation
Developer.apple.com/library/arc… // An introduction to attribute type strings
DATA. Objc_const analysis
segment | section | There is no dynamic | A dynamic | To compare |
---|---|---|---|---|
__DATA | __objc_const | 4928 | 5040 | 112 |
A property 0x100008110 0x00000068 [1] __OBJC_$_INSTANCE_METHODS_ViewController 0x100008178 0x00000028 [1] __OBJC_$_INSTANCE_VARIABLES_ViewController Dynamic attribute 0x100008110 0x00000020 [1] __OBJC_$_INSTANCE_METHODS_ViewController (104 - 32) + 40 = 112Copy the code
Instance method list has no getters, setters for myString1.
DATA. Objc_ivar analysis
segment | section | There is no dynamic | A dynamic | To compare |
---|---|---|---|---|
__DATA | __objc_ivar | 4 | 8 | 4 |
The dynamic property does not have the following symbol 0x1000094B0 0x00000004 [1] _OBJC_IVAR_$_ViewController._myString1Copy the code
Without instance variables, there is naturally no _OBJC_IVAR_$_viewController._mystring1 symbol.
Conclusion: Optimizing a property with @dynamic yields (229-12)= 217B, and 12 is __objc_methtype not counted; The application scenario is to add @dynamic to the Model class.
Method calls, function calls, direct method calls
The experimental data
Experimental methods:
Void func(ViewController *mSelf) {[mSelf method]; } @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self test]; } - (void)method { NSLog(@""); } - (void)test {// if (void)test {// if (void)test {// if (void)test; #define Test func(self); #define Test [self method]; - (void)test __attribute__((objc_direct));Copy the code
Assembler instructions using functions:
Assembler instructions using methods:
The assembly instructions in the above two screenshots are basically the same, indicating that when calling the func(self) function, because the internal implementation of the function is too simple, it is compiled and optimized, and directly optimized into the introverted function.
Modify the implementation of func functions to avoid compilation optimizations:
// Without affecting the calling logic, Void func(ViewController *mSelf) {if ([mSelf isKindOfClass:[ViewController class]]) {[mSelf method]; }}Copy the code
Another set of experiments with different lengths of method names were conducted:
Short method name: method
Rectangular dharma name: methodmethodmethodmethodmethodmethodmethodmethod
Short method name | Rectangular dharma name | |
---|---|---|
The control group | 0.52 K. | 0.61 K. |
The method call | 23.98 K. | 24.07 K. |
A function call | 16.30 K. | 16.34 K. |
Direct method call | 16.11 K. | 16.16 K. |
How do short method names and long method names affect binary size?
Look at text.text for the short method name and the rectangular method name respectively
// Short method name 0x100004560 0x00005FB4 __TEXT __TEXT 0x1000045CC 0x00005DDC [1] -[ViewController test] // Rectangular method name 0x100004534 0x00005FB4 __TEXT __TEXT 0x1000045A0 0x00005DDC [1] -[ViewController test] Size is 0x00005FB4Copy the code
A short way:
Long way:
Short method names and long method names have very little effect on binary size. TEXT. TEXT is the same size because method calls operate mainly on function Pointers.
So the main effect is text.objc_methname
// Short method name 0x10000A664 0x00000D96 __TEXT __objc_methName // Rectangular method name 0x10000A638 0x00000DC0 __TEXT __objc_methNameCopy the code
What is the difference between a function call and a method call in this experiment?
Compare method calls with function calls in binary:
// Difference between function and method // function call 0x100006448 0x000040CC __TEXT __TEXT // method call 0x100004560 0x00005FB4 __TEXT __TEXTCopy the code
The difference is in text.text.
Assembly of function calls:
Compilation of method calls:
Method calls require more instructions, so change method calls to function calls, and the more places you call, the bigger the package size gains.
The difference between a function call and a direct method call?
// Difference between function and Direct // Function call 0x100006448 0x000040CC __TEXT __TEXT 0x10000A664 0x00000D96 __TEXT __objc_methName 0x10000BF90 0x00000070 __TEXT __unwind_info 0x10000C0F0 0x00001358 __DATA __objc_const 0x10000D448 0x00000038 __DATA __objc_selrefs 0x10000D480 0x00000018 __DATA __objc_classRefs // Direct call 0x1000064C4 0x00004060 __TEXT __TEXT 0x10000A674 0x00000D8F __TEXT __objc_methname 0x10000BF9C 0x00000064 __TEXT __unwind_info 0x10000C0F0 0x00001340 __DATA __objc_const 0x10000D430 0x00000028 __DATA __objc_selrefs 0x10000D458 0x00000010 __DATA __objc_classrefs // The direct method calls text. TEXT do not have a [ViewController method] symbol, but it does have a [ViewController method] symbol when there are more implementations inside the method. __objc_selrefs is missing a symbol because method is too simple and the compiler optimised it to be inline. But when the method implementation gets complicated, there's an extra method call, __objc_selrefs = 0x10000A664 0x00000006 [1] Literal String class 0x10000A66A 0x0000000F [ 1] literal string: isKindOfClass: // Function call instance method list size is larger than direct method call, Cause Direct has no instance method // function call 0x10000C138 0x00000050 [1] __OBJC_$_INSTANCE_METHODS_ViewController // Direct method call 0x10000C138 0x00000038 [ 1] __OBJC_$_INSTANCE_METHODS_ViewControllerCopy the code
Conclusion: Direct method and function call yield are basically the same, each call optimization yield is 3.93 B.
How can function calls, direct method calls, reduce binary file sizes?
Normal method call:
Function call:
Direct method call:
If self does not exist, goto 0x1000063AC ->ret,ret is return; if self does not exist, goto 0x1000063AC ->ret is return. - (void)method { NSLog(@""); } If self exists, NSLog is executed because the method method implementation is too simple and is optimized by the compiler to be inline.Copy the code
When the method implementation is simple, using the Direct modifier can optimize the call efficiency, but the package size benefit can be negative. Because many calls to the direct method are inlined.
Back to the original problem: the reason function calls, direct method calls, reduce the size of binary files is because there are fewer instructions than method calls.
To Block
Experiment content:
- (void)blockTest {[self.blockProvider blockInterface1:1]; } - (void)blockTest { [self.blockProvider blockInterface:^{ }]; }Copy the code
What symbols do block calls produce?
// The difference between one block call and two block calls // __data. const (uninitialized constant) This takes 32 bytes 0x100008070 0x00000020 [1] ___block_descriptor_32_e5_V8? 0l 0x100008090 0x00000020 [ 1] ___block_literal_global 0x1000080B0 0x00000020 [ 1] ___block_literal_global.12 // Struct Block_layout {void *isa; struct Block_layout {void *isa; int flags; int reserved; void (*invoke)(void *, ...) ; struct Block_descriptor *descriptor; /* Imported variables. */ };Copy the code
Each additional block invocation creates an additional ___block_literal_global symbol, occupying 32B.
0x1000060A4 0x00000044 [1] -[ViewController blockTest] // A block is called 0x100006094 0x00000048 [1] -[ViewController blockTest] 0x1000060DC 0x00000004 [ 1] ___27-[ViewController blockTest]_block_invoke // Block // two block calls // If there are two block calls, ___29-[ViewController viewDidLoad] _block_INVOke_2 // 36 + 4 = 40b more with one block than with no block // 24 more with two blocks than with one + 4 = 28bCopy the code
Each additional block is invoked with an additional _block_INVOke_2 symbol, occupying 4b.
Is there a difference between an assembly instruction at the point of a block call and a method call?
Method call:
- (void)blockTest
{
[self.blockProvider blockInterface1:1];
}
Copy the code
Block call:
The instruction at the call, the block call, is just one more instruction than the method call, four more bytes (as calculated by maplinkfile).
Go to Block optimization example
Implementation of methods with block arguments:
- (void)blockInterface:(void(^)(void))block {[self shill]; ! block ? : block(); } - (void)shill { NSString *string = @"123"; if ([string isKindOfClass:[NSString class]]) { NSLog(@"123"); #define CallBlock(blockProvider, Block)\ [blockProvider shill]; \ BLOCKCopy the code
Experimental comparison:
- (void)blockTest { [self.blockProvider blockInterface:^{ NSLog(@"block"); }]; } - (void)blockTest { CallBlock(self.blockProvider,{ NSLog(@"block"); })}Copy the code
/ / to Block, 0x100006064 0x00000050 [1] -[ViewController blockTest] // Call the method containing the block // text.text 0x10000604C 0x00000048 [ 1] -[ViewController blockTest] 0x100006094 0x0000001C [ 1] ___27-[ViewController blockTest]_block_invoke // DATA.const 0x100008070 0x00000020 [ 1] ___block_descriptor_32_e5_v8? 0l 0x100008090 0x00000020 [ 1] ___block_literal_globalCopy the code
To call a method that contains a block:
To Block:
Conclusion: The main benefit of removing a Block is to reduce the ___block_literal_global and _block_invoke symbols, but removing a Block is equivalent to inlining the implementation of a Block, The main size appropriation of _block_invoke is the implementation of block. The difference between this inline and _Block_invoke is approximately 0x0000001C(28) – (0x00000050-0x00000048) = 20B;
___block_literal_global fixed 20b;
So the payoff from going to the block is 40b.
conclusion
- The influence of encoding on package size is analyzed from binary file level.
- Attribute, method call, function call, direct method call, block call under assembly difference, let us in the usual coding, can have a general impression, the code corresponding assembly is about what;
- Come up with some revenue data:
At sign dynamic optimizes properties | Functionalization or direct modification | To Block |
---|---|---|
217B | 3.93 b | 40b |
Join us
We are bytedance’s live broadcasting team. The live broadcasting team is responsible for providing live broadcasting services for bytedance’s core App. Live broadcasting is the head business of Bytedance with rapid growth. Douyin products have huge advantages in terms of volume, and there is still a large space for the growth of live broadcast business. You will participate in the live broadcast business research and development of famous products such as Douyin, Douyin Lite and Douyin Volcano. The functions and creativity you develop will affect hundreds of millions of users. You will be exposed to highly complex architectures, leading technologies for live connection and interaction, cross-platform technologies, and more.
Base: Beijing, Shanghai, Guangzhou, Shenzhen and Hangzhou
Fly book scan code to join douyin live inside the tweet group, douyin live business introduction and interview tip👇
Scan the QR code to learn more about the position:
(Tiktok Live research and development post internal push qr code)
(Tiktok live iOS post push qr code)
Introduction and Interview of Douyin Live Streaming client business
Bytedance. Feishu. Cn/docx/doxcn9…