Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster collection operations #748

Merged
merged 6 commits into from
Jan 22, 2018
Merged

Faster collection operations #748

merged 6 commits into from
Jan 22, 2018

Conversation

Adlai-Holler
Copy link
Member

@Adlai-Holler Adlai-Holler commented Jan 16, 2018

The idea here is, standardize on ASArrayByFlatMapping and make that macro faster and return an immutable array.

Immutable arrays mean (1) free copying, and (2) free zero-arrays and optimized singleObjectArrays, which means less retaining/releasing. Yay!

I haven't profiled this yet. Just want to put it out there.

@@ -265,7 +261,8 @@ - (ASLayout *)filteredNodeLayoutTree
}
}

ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts];
NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()];
ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this method is one of our hottest methods, and we copy the resulting array so I'm hopeful this will be a nice bump.

@TextureGroup TextureGroup deleted a comment Jan 16, 2018
@TextureGroup TextureGroup deleted a comment Jan 16, 2018
@@ -60,6 +60,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This disables the main thread checker for unit tests and there are cases where we expect to call UIKit methods off-main during unit tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I'm a bit curious which things we perform off-main intentionally in tests? I wonder if there is a way to do that in-situ, since the general existence of this checker does have value (e.g. if you were to accidentally call UIKit on main in a regression sense, then a unit test could catch it)

/**
* The total number of elements in this map.
*/
@property (readonly) NSUInteger count;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required by the new implementation of ASArrayByFlatMapping, in addition to NSFastEnumeration conformance.

CFArrayCallBacks cb = kCFTypeArrayCallBacks; \
cb.retain = NULL; \
result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \
} \
Copy link
Member Author

@Adlai-Holler Adlai-Holler Jan 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to factor this out a bit and clean it up, but here's the approach:

  • We need to safely retain the result of the work (if the work's result isn't retained e.g. [NSObject new]. Only ARC knows!)
  • We want to transfer that retain into the array, instead of having it retain and then we release.
  • The way is, use __bridge_retained to tell ARC "make sure this is a +1, and I'll take responsibility for it."
  • Then when you create the array, use CFArray and provide NULL for the retain callback. Boom, CFArray won't retain, but it will release when the array goes away.
  • Fast paths for 0- or 1- element collections e.g. __NSArray0 and __NSSingleObjectArray.
  • Not possible with id [] because ARC will always release the elements when the symbol goes out of scope – there is no CF_CONSUMED for c-arrays.
  • Down from (2N retains, N) releases to (N retains, 0 releases). Plus we get an immutable result, so free copying.

TODO: If someone calls CFArrayCreateCopy, there's no short-circuit and the callbacks will be copied as well, so the objects will get overreleased. Same if they use -mutableCopy. The solution is to create a retain callback that checks for the presence of a pthread_key that we will set during array creation. If the key is there, skip the retain. Otherwise, retain normally.

@TextureGroup TextureGroup deleted a comment Jan 16, 2018
@Adlai-Holler
Copy link
Member Author

Note that, as much as I love this diff, copy-on-write support was added to CoreFoundation for arrays, sets, and dictionaries. So copying a mutable array gives you an __NSFrozenArrayM, and copying that gives you the same instance back. The mechanics of the copy-on-write are unclear because it's written in Objective-C (and thus not open source) but it's definitely (attempted to be) running.

@appleguy
Copy link
Member

This is extremely cool.

It could be enough to avoid 1 frame drop in several common scenarios, either faster devices that are near the threshold or dialing back the severity of an inevitable drop on a slower device.

There is an awful lot of runtime traffic in common codepaths that run on main — just thousands of very short method calls, which do contend with background threads, especially when any part of the system is using dynamically-implemented methods. This should reduce the surface area for contention (and thus risk for the longer kinds of sporadic stalls / priority inversion and rapid priority donation thrash), and also reduce total CPU time even when un-contended.

I'd like to study the code and profile this in more detail, but if you develop confidence in it, then don't wait on me to land.

Copy link
Contributor

@maicki maicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is pretty cool!

cb.retain = NULL; \
result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \
} \
result; \
})

/**
* Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil.
*/
#define ASPointerTableByFlatMapping(collection, decl, work) ({ \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this one not yet used? Just wanted to say, I think this #define-based approach is very powerful as an optimization, in its ability to avoid wrapping and retains that most other mechanisms lead to. We should use it judiciously, but I bet there are a few more cases where it is justified and impactful.

@nguyenhuy
Copy link
Member

Merging per approvals from @maicki and @appleguy. Thanks all!

@ghost
Copy link

ghost commented Jan 22, 2018

1 Warning
⚠️ Please ensure license is correct for ASCollectionGalleryLayoutDelegate.mm:

//
//  ASCollectionGalleryLayoutDelegate.mm
//  Texture
//
//  Copyright (c) 2014-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under the BSD-style license found in the
//  LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
//  grant of patent rights can be found in the PATENTS file in the same directory.
//
//  Modifications to this file made after 4/13/2017 are: Copyright (c) through the present,
//  Pinterest, Inc.  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//

    

Generated by 🚫 Danger

@nguyenhuy nguyenhuy merged commit 5c13403 into master Jan 22, 2018
Adlai-Holler added a commit that referenced this pull request Jan 22, 2018
Adlai-Holler added a commit that referenced this pull request Jan 22, 2018
bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
* Faster collection operations

* Fix a few things

* Put the stupid semicolon

* Address warning

* Cut down retain/releases during collection operations

* Update CHANGELOG.md
bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants