草庐IT

ios - 为什么 NSOperationQueue 在主线程上处理大量任务时比 GCD 或 performSelectorOnMainThread 更快?

coder 2023-09-27 原文

例如,我有 100 次 for 循环。并且需要更新UIImageView,最后2个方法一样慢。为什么?它们有什么区别?

//fastest
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                       [btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
                        [scrollView addSubview:btnThumb];
                   }];
//slowly
                     dispatch_async(dispatch_get_main_queue(), ^
                    {
                        [btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
                        [scrollView addSubview:btnThumb];
                    });       
//slowly
                   [btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
                   [self performSelectorOnMainThread:@selector(testMethod:) withObject:[NSArray arrayWithObjects:scrollView, btnThumb, nil] waitUntilDone:NO];

    -(void) testMethod:(NSArray*)objs
    {

        UIScrollView *scroll = [objs objectAtIndex:0];
        UIButton *btn = [objs lastObject];
        [scroll addSubview:btn];
    }

最佳答案

为免后人对此有任何疑问,让我们考虑以下测试工具,它构建在一个简单的空应用程序模板中。它使用每种机制执行 1000 次操作,并且还有一个运行循环观察器,因此我们可以看到我们排队的异步任务如何与主运行循环的旋转相关联。它记录到控制台,但异步执行,因此 NSLog 的成本不会混淆我们的测量。它还在将 NSOperations/dispatch_asyncs/performSelectors 任务排入队列时故意阻塞主线程,因此排入队列的行为也不会受到干扰。这是代码:

#import "NSAppDelegate.h"

dispatch_queue_t gLogQueue;
#define NSLogAsync(...) dispatch_async(gLogQueue, ^{ NSLog(__VA_ARGS__); });

@implementation NSAppDelegate
{
    dispatch_group_t g;
    NSUInteger numOps;
    useconds_t usleepDuration;
}

static void MyCFRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // parameters of test
    numOps = 1000;
    usleepDuration = 1000;

    // Set up a serial queue so we can keep the cost of calling NSLog more or less out of our test case.
    gLogQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    // Group allows us to wait for one test to finish before the next one begins
    g = dispatch_group_create();

    // Insert code here to initialize your application
    CFRunLoopObserverRef rlo = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, YES, 0, MyCFRunLoopObserverCallBack, NULL);
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), rlo, kCFRunLoopCommonModes);
    CFRelease(rlo);

    NSCondition* cond = [[NSCondition alloc] init];


    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSTimeInterval start = 0, end = 0;

        // pause the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            [cond lock];
            [cond signal];
            [cond wait];
            [cond unlock];
        });

        // wait for the main thread to be paused
        [cond lock];
        [cond wait];

        // NSOperationQueue
        for (NSUInteger i = 0; i < numOps; ++i)
        {
            dispatch_group_enter(g);
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLogAsync(@"NSOpQ task #%@", @(i));
                usleep(usleepDuration); // simulate work
                dispatch_group_leave(g);
            }];
        }

        // unpause the main thread
        [cond signal];
        [cond unlock];

        // mark start time
        start = [NSDate timeIntervalSinceReferenceDate];
        // wait for it to be done
        dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
        end = [NSDate timeIntervalSinceReferenceDate];
        NSTimeInterval opQDuration = end - start;
        NSLogAsync(@"NSOpQ took: %@s", @(opQDuration));

        // pause the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            [cond lock];
            [cond signal];
            [cond wait];
            [cond unlock];
        });

        // wait for the main thread to be paused
        [cond lock];
        [cond wait];

        // Dispatch_async
        for (NSUInteger i = 0; i < numOps; ++i)
        {
            dispatch_group_enter(g);
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLogAsync(@"dispatch_async main thread task #%@", @(i));
                usleep(usleepDuration); // simulate work
                dispatch_group_leave(g);
            });
        }

        // unpause the main thread
        [cond signal];
        [cond unlock];

        // mark start
        start = [NSDate timeIntervalSinceReferenceDate];
        dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
        end = [NSDate timeIntervalSinceReferenceDate];
        NSTimeInterval asyncDuration = end - start;
        NSLogAsync(@"dispatch_async took: %@s", @(asyncDuration));

        // pause the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            [cond lock];
            [cond signal];
            [cond wait];
            [cond unlock];
        });

        // wait for the main thread to be paused
        [cond lock];
        [cond wait];

        // performSelector:
        for (NSUInteger i = 0; i < numOps; ++i)
        {
            dispatch_group_enter(g);
            [self performSelectorOnMainThread: @selector(selectorToPerfTask:) withObject: @(i) waitUntilDone: NO];
        }

        // unpause the main thread
        [cond signal];
        [cond unlock];

        // mark start
        start = [NSDate timeIntervalSinceReferenceDate];
        dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
        end = [NSDate timeIntervalSinceReferenceDate];
        NSTimeInterval performDuration = end - start;
        NSLogAsync(@"performSelector took: %@s", @(performDuration));

        // Done.
        dispatch_async(dispatch_get_main_queue(), ^{
            CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), rlo, kCFRunLoopCommonModes);
            NSLogAsync(@"Done. NSOperationQueue: %@s dispatch_async: %@s performSelector: %@", @(opQDuration), @(asyncDuration), @(performDuration));
        });
    });
}

- (void)selectorToPerfTask: (NSNumber*)task
{
    NSLogAsync(@"performSelector task #%@", task);
    usleep(usleepDuration); // simulate work
    dispatch_group_leave(g);
}

static NSString* NSStringFromCFRunLoopActivity(CFRunLoopActivity activity)
{
    NSString* foo = nil;
    switch (activity) {
        case kCFRunLoopEntry:
            foo = @"kCFRunLoopEntry";
            break;
        case kCFRunLoopBeforeTimers:
            foo = @"kCFRunLoopBeforeTimers";
            break;
        case kCFRunLoopBeforeSources:
            foo = @"kCFRunLoopBeforeSources";
            break;
        case kCFRunLoopBeforeWaiting:
            foo = @"kCFRunLoopBeforeWaiting";
            break;
        case kCFRunLoopAfterWaiting:
            foo = @"kCFRunLoopAfterWaiting";
            break;
        case kCFRunLoopExit:
            foo = @"kCFRunLoopExit";
            break;
        default:
            foo = @"ERROR";
            break;

    }
    return foo;
}

static void MyCFRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    NSLogAsync(@"RLO: %@", NSStringFromCFRunLoopActivity(activity));
}


@end

在此代码的输出中,我们看到以下内容(删除了不相关/重复的部分):

RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
NSOpQ task #0
RLO: kCFRunLoopExit
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
NSOpQ task #1
RLO: kCFRunLoopExit

... pattern repeats ...

RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
NSOpQ task #999
RLO: kCFRunLoopExit
NSOpQ took: 1.237247049808502s
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
dispatch_async main thread task #0
dispatch_async main thread task #1

... pattern repeats ...

dispatch_async main thread task #999
dispatch_async took: 1.118762016296387s
RLO: kCFRunLoopExit
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
performSelector task #0
performSelector task #1

... pattern repeats ...

performSelector task #999
performSelector took: 1.133482992649078s
RLO: kCFRunLoopExit
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
Done. NSOperationQueue: 1.237247049808502s dispatch_async: 1.118762016296387s performSelector: 1.133482992649078

这向我们展示了在主队列中排队的 NSOperation 在运行循环的每一遍中都会执行一个。 (顺便说一句,这将允许为每个操作绘制 View ,因此如果您像 OP 一样在这些任务中更新 UI 控件,这将允许它们绘制。)使用 dispatch_async(dispatch_get_main_queue(), ...)-[performSelectorOnMainThread:...] 所有排队的 block /选择器都被一个接一个地调用,而不让 View 绘制或类似的东西。 (如果您在排队任务时没有强行暂停主运行循环,您有时可以看到在排队过程中运行循环旋转一两次。)

最后,结果与我预期的差不多:

  • NSOperationQueue:1.2372s
  • dispatch_async:1.1188s
  • 执行选择器:1.1335s

NSOperationQueue 总是比较慢,因为旋转运行循环不是免费的。在此测试工具中,运行循环甚至任何实质性的事情,而且它已经比 dispatch_async 慢了 10%。如果它正在做任何实质性的事情,比如重绘 View ,它会慢很多。至于 dispatch_asyncperformSelectorOnMainThread: 两者都在运行循环的一次旋转中执行所有排队的项目,因此差异非常小。我预计这取决于消息发送开销和管理 performSelector... 的目标和参数上的保留/释放。

因此,与问题的含义相反,NSOperationQueue 客观上不是三种机制中最快的,而是最慢的。我怀疑在 OP 的情况下,NSOperationQueue 出现 更快,因为它的“第一次可见变化的时间”会短得多,而对于 dispatch_asyncperformSelector 所有排队的操作都将被执行,只有 then View 才会重绘并显示新状态。在病态的情况下,我希望这意味着只看到最后一帧,尽管如果你在排队时不阻塞主线程,你可能会得到一些可见的帧(但你实际上会丢弃大部分地面上的框架。)

无论哪种异步执行机制客观上最快,它们都是制作动画的非常蹩脚的方法。

关于ios - 为什么 NSOperationQueue 在主线程上处理大量任务时比 GCD 或 performSelectorOnMainThread 更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14207036/

有关ios - 为什么 NSOperationQueue 在主线程上处理大量任务时比 GCD 或 performSelectorOnMainThread 更快?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  8. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  9. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  10. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

随机推荐