【问题标题】:Function/method-level profiling on iOSiOS 上的函数/方法级别分析
【发布时间】:2016-03-10 11:51:32
【问题描述】:

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

我的意思是等同于GCC's -pg,它使编译器将性能日志记录注入您的代码。早在 Apple 还在使用 GCC 的时候,我确实在 iOS 上成功使用了 -pggprof,但似乎没有与 clang 等效的东西。

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

所以我需要一种方法来找出哪个方法在什么时间运行了多长时间,准确地。唯一真正做到这一点的方法是让编译器注入必要的代码(这就是-pg 正在做的事情),或者可能是对 Objective-C 运行时的一些创造性黑客攻击。是否已经有任何解决方案?

【问题讨论】:

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

标签: ios performance profiling


【解决方案1】:

@MikeDunlavey 设置计时器的建议对我来说是正确的解决方案。在我的情况下,一个真正的计时器不起作用,所以我使用了一个具有高优先级的后台线程和一个NSCondition(像pthread_cond_timedwait 这样的较低级别的机制也可以工作)。每次运行循环迭代都会触发条件。如果主线程耗时过长,则条件中止。

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

不过,我无法解决此解决方案的所有问题;滚动仍然有点紧张,但比以前好多了。

- (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]);
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-29
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 2012-12-08
    • 2013-01-26
    相关资源
    最近更新 更多