In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article will explain in detail how to understand the high-performance cycle of Objective-C. The content of the article is of high quality, so the editor will share it with you for reference. I hope you will have some understanding of the relevant knowledge after reading this article.
A common task of Cocoa programming is to loop through a collection of objects (for example, a NSArray, NSSet, or NSDictionary). This seemingly simple problem has a wide range of solutions, many of which have subtle considerations for performance issues.
The pursuit of speed
First of all, there is a disclaimer: compared to other issues, the raw speed of an Objective-C method is one of the issues you need to consider when programming-the difference is that this problem is not comparable to other more important issues, such as the clarity and readability of the code.
But the secondary importance of speed does not prevent us from understanding it. You should always understand how performance considerations will affect the code you are writing, and you will know how to do it in rare cases where problems occur.
Also, in circular scenarios, it doesn't matter which technology you choose most of the time, whether in terms of readability or clarity, so you might as well choose the fastest one. There is no need to choose ones that are slower than required.
With this in mind, you have the following options:
The classic way of circulation
For (NSUInteger I = 0; I < [array count]; iTunes +) {id object = array [I];... }
This is a simple and familiar way to loop through an array; it is also quite poor in terms of performance. The problem with this code is that every time the loop is performed, we call the array count method. The total number of arrays does not change, so it is superfluous to call it every time. C compilers generally optimize code like this, but the dynamic language nature of Objective-C means that calls to this method are not automatically optimized. So, to improve performance, it's worth saving this total in a variable before the loop starts, like this:
NSUInteger count = [array count]; for (NSUInteger I = 0; I < count; iTunes +) {id object = array [I];... }
NSEnumerator
NSEnumerator is an optional way to loop through collections. All collections have one or more enumerated methods that return a NSEnumerator entity each time they are called. A given NSEnumerator will contain a pointer to * objects in the collection, and a nextObject method will return the current object and grow the pointer. You can call it repeatedly until it returns nil, which indicates that you have reached the end of the collection:
Id obj = nil;NSEnumerator * enumerator = [array objectEnumerator]; while ((obj = [enumerator nextObject])); {... }
The performance of NSEnumerator is comparable to that of native for loops, but it is more practical because it abstracts the concept of indexes, which means that it is applied to structured data, such as linked lists, or even infinite sequences and data streams, where the number of data entries is unknown or undefined.
Quick enumeration
Fast enumeration was introduced in Objective-C 2.0 as a more convenient (and significantly faster) alternative to traditional NSEnumerator. It does not make the enumeration class obsolete because it is still applied to injection reverse enumerations, or when you need to make changes to the collection (more on that later).
Quick enumeration adds a new enumeration method that looks like this:
-(NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *) state objects: (id *) stackbuf count: (NSUInteger) len
If you're thinking, "that doesn't look very comfortable!", I don't blame you. But the new method brings a new loop syntax, for. In cycle. This is the use of new enumeration methods behind the scenes, and importantly, syntax and performance are less worrying than using traditional for loops or NSEnumerator methods:
For (id object in array) {… }
Enumerated block
With the birth of blocks, Apple added a fourth enumeration mechanism based on block syntax. This is certainly rarer than fast enumeration, but one advantage is that both objects and indexes return, while other enumeration methods only return objects.
Another key feature of enumeration blocks is selectable concurrent enumeration (enumerating objects in several concurrent threads). This is not always useful, depending on what you do in your own loop, but in scenarios where you have a lot of work to do and you don't care much about the enumeration order, it can lead to significant performance improvements on multi-core processors (all Mac and iOS devices now have multi-core processors).
Benchmark test
So what happens when these methods are added up, and will the performance be better? Here is a simple benchmark command line application that compares the performance of enumerating a data in many different ways. We have run it with ARC off to eliminate any hidden retention or exclusion processing that interferes with the final result. Running on a fast Mac machine, all these methods run so fast that we actually have to use an array of 10000000 (10 million) objects to measure the results. If you decide to test on an iPhone, the wisest thing to do is to use a much smaller quantity!
To compile this code:
Save the code in a file named benchmark.m
Compile the application in the terminal:
Clang-framework Foundation benchmark.m-o benchmark
Run the program:. / benchmark
# import int main (int argc, const char * argv []) {@ autoreleasepool {static const NSUInteger arrayItems = 100000000; NSMutableArray * array = [NSMutableArray arrayWithCapacity:arrayItems]; for (int I = 0; I < arrayItems; iTunes +) [array addObject:@ (I)]; array = [array copy]; CFTimeInterval start = CFAbsoluteTimeGetCurrent (); / / Naive for loop for (NSUInteger I = 0; I < [array count] CFTimeInterval forLoop +) {id object = array [I];} CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent (); NSLog (@ "For loop:% g", forLoop-start); / / Optimized for loop NSUInteger count = [array count]; for (NSUInteger I = 0; I < count; iTunes +) {id object = array [I];} CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent () NSLog (@ "Optimized for loop:% g", forLoopWithCountVar-forLoop); / / NSEnumerator id obj = nil; NSEnumerator * enumerator = [array objectEnumerator]; while ((obj = [enumerator nextObject])) {} CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent (); NSLog (@ "Enumerator:% g", enumeratorLoop-forLoopWithCountVar); / / Fast enumeration for (id object in array) {} CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent () NSLog (@ "For... In loop:% g ", forInLoop-enumeratorLoop); / / Block enumeration [array enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) {}]; CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent (); NSLog (@" Enumeration block:% g ", enumerationBlock-forInLoop); / / Concurrent enumeration [array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) {}] CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent (); NSLog (@ "Concurrent enumeration block:% g", concurrentEnumerationBlock-enumerationBlock);} return 0;}
The results are shown below:
$For loop: 0.119066$ Optimized for loop: 0.092441$ Enumerator: 0.123687$ For... In loop: 0.049296$ Enumeration block: 0.295039$ Concurrent enumeration block: 0.199684
Ignore the specific length of time. We are interested in their relative size compared with other methods. If we arrange them in order and put them in front quickly, I will get the following result:
For... In cycle-fastest.
Optimization of for cycle-better than for. In is twice as slow.
There is no optimized for loop-better than for. In is two and a half times slower.
Enumerator-about the same as a loop without optimization.
Concurrent enumeration blocks-better than for … In is about 6 times slower.
Enumerated blocks-better than for... In is almost six times slower.
For... In is the winner. Obviously they call it fast enumeration for a reason! Concurrent enumerations seem a little faster than single-threaded enumerations, but you don't need to interpret it any more: we're enumerating a very large array of objects. the cost of concurrent execution of smaller data far outweighs the benefits.
Concurrent execution mainly has the advantage when your loop requires a lot of execution time. If you have a lot of things to run in your own loop, consider trying parallel enumerations, as long as you don't care about the enumeration order (but weigh whether it becomes happier with action, don't speculate empty-handed).
Other collection types Other Collection Types
What about other combination types, such as NSSet and NSDictionary? NSSet is unordered, so there is no concept of fetching objects by index. We can also do a benchmark test:
$Enumerator: 0.421863$ For... In loop: 0.095401$ Enumeration block: 0.302784$ Concurrent enumeration block: 0.390825
The result is consistent with that of NSArray; for... In won again. How's NSDictionary? NSDictionary is a little different because we have another key and value object that needs to be iterated. It is possible to iterate over keys or values individually in a dictionary, but typically we need both. Here we have a piece of benchmark code suitable for operating NSDictionary:
# import int main (int argc, const char * argv []) {@ autoreleasepool {static const NSUInteger dictItems = 10000; NSMutableDictionary * dictionary = [NSMutableDictionary dictionaryWithCapacity:dictItems]; for (int I = 0; I < dictItems; ionization +) dictionary [@ (I)] = @ (I); dictionary = [dictionary copy]; CFTimeInterval start = CFAbsoluteTimeGetCurrent (); / / Naive for loop for (NSUInteger I = 0; I < [dictionary count]) ITunes +) {id key = [dictionary allKeys] [I]; id object = dictionary [key];} CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent (); NSLog (@ "For loop:% g", forLoop-start); / / Optimized for loop NSUInteger count = [dictionary count]; NSArray * keys = [dictionary allKeys]; for (NSUInteger I = 0; I < count; iTunes +) {id key = keys [I] Id object = dictionary [key];} CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent (); NSLog (@ "Optimized for loop:% g", forLoopWithCountVar-forLoop); / / NSEnumerator id key = nil; NSEnumerator * enumerator = [dictionary keyEnumerator]; while ((key = [enumerator nextObject])) {id object = dictionary [key];} CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent (); NSLog (@ "Enumerator:% g", enumeratorLoop-forLoopWithCountVar) / / Fast enumeration for (id key in dictionary) {id object = dictionary [key];} CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent (); NSLog (@ "For... In loop:% g ", forInLoop-enumeratorLoop); / / Block enumeration [dictionary enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL * stop) {}]; CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent (); NSLog (@" Enumeration block:% g ", enumerationBlock-forInLoop); / / Concurrent enumeration [dictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent usingBlock: ^ (id key, id obj, BOOL * stop) {}] CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent (); NSLog (@ "Concurrent enumeration block:% g", concurrentEnumerationBlock-enumerationBlock);} return 0;}
NSDictionary fills much more slowly than NSArray or NSSet, so we reduced the number of data entries to 10000 to avoid machine locking. So you should ignore how the results are so much lower than those NSArray, because we are using 1000 loops with fewer objects:
$For loop: 2.25899$ Optimized for loop: 0.00273103$ Enumerator: 0.00496799$ For... In loop: 0.001041$ Enumeration block: 0.000607967$ Concurrent enumeration block: 0.000748038
Unoptimized loops are spectacularly slow here, because each time we copy the key array. We get faster speed by saving key arrays and totals in variables. The consumption of lookup objects now dominates other factors, so use a for loop, NSEnumerator or for. There is little difference in in. But for the enumerated block method, it returns both the key and the value in one method, so it is now the fastest choice.
Reverse gear
Based on what we have seen, if all other factors are the same, you should try to use the for...in loop when iterating through the array, while when traversing the dictionary, you should choose the enumeration block. There are also scenarios where this is not possible, such as when we need to go back to enumerating, or when we want to change the collection while we are traversing.
To enumerate a data back, we can call the reverseObjectEnumerator method to get a NSEnumerator to traverse the array from tail to head. NSEnumerator, like NSArray itself, supports fast enumeration protocols. That means we can still use for in this way. In without loss of speed and simplicity:
For (id object in [array reverseObjectEnumerator]) {… }
(unless you're whimsical, there is no equivalent for NSSet or NSDictionary, and it doesn't make sense to enumerate a NSSet or NSDictionary backwards, because keys are unordered.)
If you want to use enumerated blocks, NSEnumerationReverse you can try it, like this:
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) {… }]
Change Mutation
It is possible to apply the same loop technique to the collection in the change; the performance is roughly the same. However, when you try to modify circular arrays or dictionaries, you may often face exceptions like this:
'* Collection XYZ was mutated while being enumerated.'
Like our optimized for loop, the performance of all these loop techniques depends on saving the total data in advance, which means that if you start to add or remove a piece of data in the middle of the loop, the data is incorrect. But what you often want to do when adding, replacing, or removing a piece of data in a loop. So what is the solution to this problem?
Our classic for loop works well because it does not depend on the resident total constant; we just need to remember that if we add or remove a piece of data, we need to increase or decrease the index. But we have learned that the for loop is not a fast solution. Our optimized for loop is a reasonable choice, as long as we remember to increase or decrease technical variables on demand, as well as indexes.
We can still use for... In, but only if we first create a copy of the array. This will work, for example:
For (id object in [array copy]) {/ / Do something that modifies the array, e.g. [array removeObject:object];}
If we benchmark different technologies (taking into account the cost of replicating the array if necessary so that we can change the data in the original array), we find that replication offsets for... Previous benefits of the in loop:
$For loop: 0.111422$ Optimized for loop: 0.08967$ Enumerator: 0.313182$ For... In loop: 0.203722$ Enumeration block: 0.436741$ Concurrent enumeration block: 0.388509
Modifying the fastest count of an array as we traverse it seems to require the use of an optimized for loop.
For a NSDictionary, we don't need to copy the entire dictionary to use NSEnumerator or quick enumerations; we can just use the allKeys method to get a copy of all the keys. This will all work well:
/ / NSEnumerator id key = nil; NSEnumerator * enumerator = [[items allKeys] objectEnumerator]; while ((key = [enumerator nextObject])) {id object = items [key]; / / Do something that modifies the value, e.g. Dictionary [key] = newObject;} / / Fast enumeration for (id key in [dictionary allkeys]) {id object = items [key]; / / Do something that modifies the value, e.g. Dictionary [key] = newObject;}
However, the same technique does not work when using the enumerateKeysAndObjectsUsingBlock method. If we loop through a dictionary for benchmarking and create a backup of the key or the dictionary as a whole as needed, we get the following result:
$For loop: 2.24597$ Optimized for loop: 0.00282001$ Enumerator: 0.00508499$ For... In loop: 0.000990987$ Enumeration block: 0.00144804$ Concurrent enumeration block: 0.00166804
Here we can see for... The in loop is one of the fastest. That's because the cost of keying objects in for...in loops is now outweighed by the cost of copying dictionaries before calling enumerated block methods.
When enumerating a NSArray:
Use for (id object in array) if it is a sequential enumeration
Use for (id object in [array reverseObjectEnumerator]) if enumerating in reverse order
Use for (NSInteger I = 0; I < count; iTunes +) if you need to know its index value or if you need to change the array
Try [array enumerateObjectsWithOptions:usingBlock:] if your code benefits from parallel execution
When enumerating a NSSet:
Using for (id object in set) most of the time
Use for (id object in [set copy]) if you need to modify the collection (but it will be slow)
Try [array enumerateObjectsWithOptions:usingBlock:] if your code benefits from parallel execution
When enumerating a NSDictionary:
Using for (id object in set) most of the time
Use for (id object in [set copy]) if you need to modify the dictionary
Try [array enumerateObjectsWithOptions:usingBlock:] if your code benefits from parallel execution
These methods may not be the fastest, but they are very clear and easy to read. So remember, sometimes you have to choose between unclean code and fast code, and you'll find that you can get it in both worlds.
On how to understand the Objective-C high-performance cycle to share here, I hope the above content can be of some help to you, can learn more knowledge. If you think the article is good, you can share it for more people to see.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.