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

Optimize SEL search efficiency #1055

Closed
wants to merge 3 commits into from

Conversation

zhongwuzw
Copy link
Contributor

@zhongwuzw zhongwuzw commented Jan 20, 2018

Changes in this pull request

Optimize SEL search efficiency, reduced the time complexity from O (n) to O (1).

Not need tests.

Checklist

  • All tests pass. Demo project builds and runs.
  • I added tests, an experiment, or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have reviewed the contributing guide

@facebook-github-bot
Copy link
Contributor

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at [email protected]. Thanks!

@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

Copy link
Contributor

@jessesquires jessesquires left a comment

Choose a reason for hiding this comment

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

😎 Seems reasonable to me! Thanks @zhongwuzw !

cc @rnystrom - any reason you can think of not to do this?

@@ -12,30 +12,34 @@
#import <IGListKit/IGListAssert.h>

/**
Define messages that you want the IGListAdapter object to intercept. Pattern copied from
https://github.com/facebook/AsyncDisplayKit/blob/7b112a2dcd0391ddf3671f9dcb63521f554b78bd/AsyncDisplayKit/ASCollectionView.mm#L34-L53
Define messages that you want the IGListAdapter object to intercept.
Copy link
Contributor

Choose a reason for hiding this comment

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

let's keep the original comment.

sel == @selector(scrollViewDidEndDragging:willDecelerate:) ||
sel == @selector(scrollViewDidEndDecelerating:)
);
static NSSet<NSString *> *sels;
Copy link
Contributor

Choose a reason for hiding this comment

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

let's spell-out selectors

@facebook-github-bot
Copy link
Contributor

@zhongwuzw has updated the pull request. View: changes

@iglistkit-bot
Copy link

iglistkit-bot commented Jan 21, 2018

1 Warning
⚠️ All pull requests should have a milestone attached, unless marked #trivial.

Generated by 🚫 Danger

@rnystrom
Copy link
Contributor

This makes sense to me, though I'm curious @zhongwuzw did you do any instrumentation on the efficiency of this? Just curious what the cost of the linear pointer equality checks vs acquiring a lock + lookup is against each other?

I ask b/c even though this is straightforward, I'm skeptical about making changes w/out evidence that it's actually better.

@zhongwuzw
Copy link
Contributor Author

@rnystrom , 👍 , honestly, I did not test it before, just based on my theoretical knowledge. After your comment, I tested like below,

The output:

  1. Compare using pointer check:
    0.210490
    0.226435
    0.232305
    0.278115

  2. Compare using NSSet:
    1.165643
    0.947413
    1.003125
    0.999605

Conclusion:
NSSet seems not good vs pointer compare. So maybe we should still use pointer to check. But I want to update the order of these three protocol, put UIScrollViewDelegate methods to front, because the times of call are more than others. 😄

static BOOL isInterceptedSelector(SEL sel) {
    static NSSet<NSString *> *selectors;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        selectors = [NSSet setWithObjects:
                     // UICollectionViewDelegate
                     NSStringFromSelector(@selector(collectionView:didSelectItemAtIndexPath:)),
                     NSStringFromSelector(@selector(collectionView:willDisplayCell:forItemAtIndexPath:)),
                     NSStringFromSelector(@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)),
                     NSStringFromSelector(@selector(collectionView:didHighlightItemAtIndexPath:)),
                     NSStringFromSelector(@selector(collectionView:didUnhighlightItemAtIndexPath:)),
                     // UICollectionViewDelegateFlowLayout
                     NSStringFromSelector(@selector(collectionView:layout:sizeForItemAtIndexPath:)),
                     NSStringFromSelector(@selector(collectionView:layout:insetForSectionAtIndex:)),
                     NSStringFromSelector(@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)),
                     NSStringFromSelector(@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)),
                     NSStringFromSelector(@selector(collectionView:layout:referenceSizeForFooterInSection:)),
                     NSStringFromSelector(@selector(collectionView:layout:referenceSizeForHeaderInSection:)),
                     // UIScrollViewDelegate
                     NSStringFromSelector(@selector(scrollViewDidScroll:)),
                     NSStringFromSelector(@selector(scrollViewWillBeginDragging:)),
                     NSStringFromSelector(@selector(scrollViewDidEndDragging:willDecelerate:)),
                     NSStringFromSelector(@selector(scrollViewDidEndDecelerating:)),
                     nil];
    });
    return [selectors containsObject:NSStringFromSelector(sel)];
}

static BOOL isInterceptedSelector1(SEL sel) {
    return (
            // UICollectionViewDelegate
            sel == @selector(collectionView:didSelectItemAtIndexPath:) ||
            sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
            sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
            sel == @selector(collectionView:didHighlightItemAtIndexPath:) ||
            sel == @selector(collectionView:didUnhighlightItemAtIndexPath:) ||
            // UICollectionViewDelegateFlowLayout
            sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
            sel == @selector(collectionView:layout:insetForSectionAtIndex:) ||
            sel == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) ||
            sel == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) ||
            sel == @selector(collectionView:layout:referenceSizeForFooterInSection:) ||
            sel == @selector(collectionView:layout:referenceSizeForHeaderInSection:) ||
            // UIScrollViewDelegate
            sel == @selector(scrollViewDidScroll:) ||
            sel == @selector(scrollViewWillBeginDragging:) ||
            sel == @selector(scrollViewDidEndDragging:willDecelerate:) ||
            sel == @selector(scrollViewDidEndDecelerating:)
            );
}

- (void)benchmark{
    NSSet *selectors;
    selectors = [NSSet setWithObjects:
                 // UICollectionViewDelegate
                 NSStringFromSelector(@selector(collectionView:didSelectItemAtIndexPath:)),
                 NSStringFromSelector(@selector(collectionView:willDisplayCell:forItemAtIndexPath:)),
                 NSStringFromSelector(@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)),
                 NSStringFromSelector(@selector(collectionView:didHighlightItemAtIndexPath:)),
                 NSStringFromSelector(@selector(collectionView:didUnhighlightItemAtIndexPath:)),
                 // UICollectionViewDelegateFlowLayout
                 NSStringFromSelector(@selector(collectionView:layout:sizeForItemAtIndexPath:)),
                 NSStringFromSelector(@selector(collectionView:layout:insetForSectionAtIndex:)),
                 NSStringFromSelector(@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)),
                 NSStringFromSelector(@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)),
                 NSStringFromSelector(@selector(collectionView:layout:referenceSizeForFooterInSection:)),
                 NSStringFromSelector(@selector(collectionView:layout:referenceSizeForHeaderInSection:)),
                 // UIScrollViewDelegate
                 NSStringFromSelector(@selector(scrollViewDidScroll:)),
                 NSStringFromSelector(@selector(scrollViewWillBeginDragging:)),
                 NSStringFromSelector(@selector(scrollViewDidEndDragging:willDecelerate:)),
                 NSStringFromSelector(@selector(scrollViewDidEndDecelerating:)),
                 nil];
    
    NSTimeInterval startInterval = [[NSDate date] timeIntervalSince1970];
    
    for (int i = 0; i < 100000; i++) {
        for (NSString *selStr in selectors) {
            SEL sel = NSSelectorFromString(selStr);
            BOOL intercepted = isInterceptedSelector1(sel);
        }
    }
    
    NSTimeInterval endInterval = [[NSDate date] timeIntervalSince1970];
    NSLog(@"time consume is %f", endInterval - startInterval);

}

@jessesquires
Copy link
Contributor

nice 😎

Copy link
Contributor

@rnystrom rnystrom left a comment

Choose a reason for hiding this comment

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

Awesome, thanks so much for running the test! I'd be happy to accept an update reordering to the most-used selectors.

@facebook-github-bot
Copy link
Contributor

@zhongwuzw has updated the pull request. View: changes

@zhongwuzw
Copy link
Contributor Author

zhongwuzw commented Jan 24, 2018

@rnystrom , I found the main performance overhead is NSString compare for NSSet approach(I use std::unordered_set to store SEL pointer, time consumption can down to 0.4 based tests as before), secondly is Set, cause of hash computation(number of selectors is less, so use hash set is less than or compare), dispatch_once maybe not too big effect.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

@rnystrom is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants