iOS上的函数/方法级评测

iOS上的函数/方法级评测,ios,performance,profiling,Ios,Performance,Profiling,是否有任何解决方案可以使用最新的Xcode(7.x)在iOS上进行函数/方法级评测 我的意思是,编译器在代码中注入性能日志。当苹果还在使用GCC时,我确实在iOS上成功地使用了-pg和gprof,但似乎没有与clang相当的功能 我需要找出在某个场景中谁在微阻塞我的主线程(应用程序仍会做出反应,但阻塞会使滚动变得紧张)。不幸的是,Instruments的工具不能胜任这项任务:采样(停止应用程序并记录堆栈跟踪)对于我的需要来说太粗糙/不精确 所以我需要一种方法来精确地找出哪种方法在什么时间运行,运

是否有任何解决方案可以使用最新的Xcode(7.x)在iOS上进行函数/方法级评测

我的意思是,编译器在代码中注入性能日志。当苹果还在使用GCC时,我确实在iOS上成功地使用了
-pg
gprof
,但似乎没有与clang相当的功能

我需要找出在某个场景中谁在微阻塞我的主线程(应用程序仍会做出反应,但阻塞会使滚动变得紧张)。不幸的是,Instruments的工具不能胜任这项任务:采样(停止应用程序并记录堆栈跟踪)对于我的需要来说太粗糙/不精确


所以我需要一种方法来精确地找出哪种方法在什么时间运行,运行多长时间。唯一真正的方法是让编译器注入必要的代码(这就是
-pg
正在做的),或者对Objective-C运行时进行一些创造性的攻击。已经有什么解决办法了吗?

MikeDunlavey建议设置计时器对我来说是正确的解决办法。在我的例子中,一个真正的计时器是不起作用的,所以我使用了一个高优先级的后台线程和一个
NSCondition
(一个较低级别的机制也可以起作用)。每次运行循环迭代都会触发该条件。如果主线程花费的时间太长,则条件中止

这是我使用过的非常粗糙的代码。由于我的问题是在非常繁忙的启动,这足以让我发现我的问题;如果您想在“正常”运行时使用它来检测问题,则需要对其进行增强,以处理runloop的等待部分

不过,我没能抓住这个解决方案的所有问题;滚动仍然有点紧张,但比以前好多了

- (void)startWatchdog
{
    static NSDate * lastIteration;
    static BOOL hasHandled = NO;
    NSCondition * condition = [[NSCondition alloc] init];

    lastIteration = [NSDate date];

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL,
        kCFRunLoopBeforeTimers | kCFRunLoopBeforeWaiting, true, 0,
        ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            NSDate * now = [NSDate date];

            if (activity == kCFRunLoopBeforeTimers) {
                if (!hasHandled) {
                    [condition lock];
                    [condition signal];
                    [condition unlock];
                }

                lastIteration = now;
                hasHandled = NO;

            } else {
                // About to wait for events. We might want tell the watchdog method
                // that it's OK that there's going to be a timeout.
                [condition lock];
                [condition signal];
                [condition unlock];
                hasHandled = YES;
            }
        }
    );

    NSThread * watchdog = [[NSThread alloc] initWithTarget:self selector:@selector(watchdog:) object:condition];
    [watchdog setThreadPriority:1];
    [watchdog start];

    CFRunLoopRef runloop = [[NSRunLoop currentRunLoop] getCFRunLoop];
    CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
    CFRunLoopAddObserver(runloop, observer, (CFStringRef)UITrackingRunLoopMode);
}

- (void)watchdog:(NSCondition *)condition
{
    @autoreleasepool {
        while (1) {
            BOOL wasHandled;
            NSDate * start = [NSDate date];

            [condition lock];
            wasHandled = [condition waitUntilDate:[start dateByAddingTimeInterval:0.2]];
            [condition unlock];

            if (!wasHandled) {
                NSLog(@"-- Timeout: %f", [[NSDate date] timeIntervalSinceDate:start]);
            }
        }
    }
}

@MikeDunlavey建议设置计时器对我来说是正确的解决方案。在我的例子中,一个真正的计时器是不起作用的,所以我使用了一个高优先级的后台线程和一个
NSCondition
(一个较低级别的机制也可以起作用)。每次运行循环迭代都会触发该条件。如果主线程花费的时间太长,则条件中止

这是我使用过的非常粗糙的代码。由于我的问题是在非常繁忙的启动,这足以让我发现我的问题;如果您想在“正常”运行时使用它来检测问题,则需要对其进行增强,以处理runloop的等待部分

不过,我没能抓住这个解决方案的所有问题;滚动仍然有点紧张,但比以前好多了

- (void)startWatchdog
{
    static NSDate * lastIteration;
    static BOOL hasHandled = NO;
    NSCondition * condition = [[NSCondition alloc] init];

    lastIteration = [NSDate date];

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL,
        kCFRunLoopBeforeTimers | kCFRunLoopBeforeWaiting, true, 0,
        ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            NSDate * now = [NSDate date];

            if (activity == kCFRunLoopBeforeTimers) {
                if (!hasHandled) {
                    [condition lock];
                    [condition signal];
                    [condition unlock];
                }

                lastIteration = now;
                hasHandled = NO;

            } else {
                // About to wait for events. We might want tell the watchdog method
                // that it's OK that there's going to be a timeout.
                [condition lock];
                [condition signal];
                [condition unlock];
                hasHandled = YES;
            }
        }
    );

    NSThread * watchdog = [[NSThread alloc] initWithTarget:self selector:@selector(watchdog:) object:condition];
    [watchdog setThreadPriority:1];
    [watchdog start];

    CFRunLoopRef runloop = [[NSRunLoop currentRunLoop] getCFRunLoop];
    CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
    CFRunLoopAddObserver(runloop, observer, (CFStringRef)UITrackingRunLoopMode);
}

- (void)watchdog:(NSCondition *)condition
{
    @autoreleasepool {
        while (1) {
            BOOL wasHandled;
            NSDate * start = [NSDate date];

            [condition lock];
            wasHandled = [condition waitUntilDate:[start dateByAddingTimeInterval:0.2]];
            [condition unlock];

            if (!wasHandled) {
                NSLog(@"-- Timeout: %f", [[NSDate date] timeIntervalSinceDate:start]);
            }
        }
    }
}

-pg和gprof从来没有这样做过。他们所做的只是计算呼叫数,并对PC进行采样(这对阻塞是盲目的)。你不需要精确。您需要的是在正确的时间(即进程被阻止时)堆叠样本。大多数堆栈采样分析器在阻塞时不进行采样,因此这是不好的。(缩放可能有用?)我会试试。它可能需要相当数量的样本,但当你看到它时,你会认出正确的样本。@MikeDunlavey:谢谢你澄清了关于
gprof
,我上次使用它已经有几年了。您的手动中断方法很有趣,尽管我对问题的性质持怀疑态度,但我还是会尝试它(我希望经常在主线程上中断;问题是当主线程上的方法花费太长时间时;还不知道您的方法将如何帮助我)。我已经在使用1ms采样的仪器,但它不能告诉我这是否是一个新的运行循环迭代(实际上,这是我需要的基本信息)。有时游戏程序员也有类似的问题,很少有东西超出时间预算。因此,这不是一个整体性能问题,而是一个令人烦恼的问题。在这种情况下,我建议在“帧”的开头设置一个计时器,设置为在帧正常结束后过期。然后在框架完成后重置它。因此,它只在帧代码花费的时间太长时才会中断。然后在中断处理程序中放置一个断点。也许你的情况需要类似的东西?这并不简单,但没有剖析器能发现这样的问题。@MikeDunlavey:这实际上是一个非常有用的想法!我必须找到使用nsrunlop执行此操作的最佳方法,但我肯定可以使其正常工作。谢谢!祝你好运这很重要。让我们知道结果如何-pg和gprof从来没有这样做过。他们所做的只是计算呼叫数,并对PC进行采样(这对阻塞是盲目的)。你不需要精确。您需要的是在正确的时间(即进程被阻止时)堆叠样本。大多数堆栈采样分析器在阻塞时不进行采样,因此这是不好的。(缩放可能有用?)我会试试。它可能需要相当数量的样本,但当你看到它时,你会认出正确的样本。@MikeDunlavey:谢谢你澄清了关于
gprof
,我上次使用它已经有几年了。您的手动中断方法很有趣,尽管我对问题的性质持怀疑态度,但我还是会尝试它(我希望经常在主线程上中断;问题是当主线程上的方法花费太长时间时;还不知道您的方法将如何帮助我)。我已经在使用1ms采样的仪器,但它不能告诉我这是否是一个新的运行循环迭代(实际上,这是我需要的基本信息)。有时游戏程序员也有类似的问题,很少有东西超出时间预算。因此,这不是一个整体性能问题,而是一个令人烦恼的问题。在这种情况下,我建议在“帧”的开头设置一个计时器,设置为在帧正常结束后过期。然后在框架完成后重置它。因此,它只在帧代码花费的时间太长时才会中断。然后在中断处理程序中放置一个断点。也许你的情况需要类似的东西?这并不简单,但没有剖析器能发现这样的问题。@MikeDunlavey:这实际上是一个非常有用的想法!