【问题标题】:Mysterious performance degrade with Open GL + infinite loopOpen GL + 无限循环导致神秘的性能下降
【发布时间】:2015-01-01 20:12:41
【问题描述】:

我正在开发一个模拟器作为一个附带/有趣的项目,但我遇到了一些性能问题并且无法弄清楚它们来自哪里。

应用程序主要由一个用于显示的 GLKView 和一个用于 cpu 仿真的无限循环的单独线程组成。这是一个示例,其中取出了仍然显示问题的所有实际仿真代码:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    GLKView *glView = [[GLKView alloc] initWithFrame:self.view.bounds];
    glView.delegate = self;
    glView.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:glView.context];
    [self.view addSubview:glView];
    glView.enableSetNeedsDisplay = NO;
    CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:glView selector:@selector(display)];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

     dispatch_get_main_queue(), ^{
        dispatch_async(dispatch_queue_create("yeah", DISPATCH_QUEUE_SERIAL), ^{
            CFTimeInterval lastTime = 0;
            CFTimeInterval time = 0;
            int instructions = 0;
            while(1) {
                // here be cpu emulation
                if (lastTime == 0) {
                    lastTime = CACurrentMediaTime();
                } else {
                    CFTimeInterval newTime = CACurrentMediaTime();
                    time += newTime - lastTime;
                    lastTime = newTime;
                }
                if (++instructions == 1000) {
                    printf("%f\n", 1/(time * 1000));
                    time = 0;
                    instructions = 0;
                }
            }
        });
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    // Here be graphics
}

@end

像这样,无限循环基本上只是计算它的迭代次数并以 MHz 为单位打印出它的频率。

所以,问题是,当应用程序启动时,循环运行在大约 9-15 MHz(在 iPhone6 上),如果我查看 Xcode 调试导航器中的 GPU 报告,我可以看到 CPU 帧时间为 0.2ms 然后,运行几秒钟后,循环下降到 1-5 MHz,CPU 帧时间增加到 0.6ms

如果我禁用 GLKView 更新,那么循环永远不会变慢

我也尝试使用不同的线程 API(gdc、NSThread、pthread),但这似乎没有任何影响

我的问题是,我在某处做错了吗?这只是 GLKView 没有完全初始化几秒钟的情况,因此使用的 cpu 比正常情况少,我得到了速度提升? 我可以通过其他方式构造代码以在循环中获得最大性能吗?

更新 我做了一些更多的测试,并注意到使用 CAEAGLLayer 而不是 GLKView 时也存在问题,而且它不会在模拟器上发生,仅在设备上发生。 我还尝试使用带有 NSOpenGLView 的 OS X 应用程序,但它也没有发生......

更新 2 我尝试在一段时间后而不是立即启动线程,如果延迟大于通常发生下降所需的时间,则线程启动速度已经减慢......不太确定该怎么做......

金属更新 我尝试使用 Metal 而不是 OpenGL,使用 Xcode 中的简单库存模板,它也正在发生...

【问题讨论】:

  • 您不应该在任何线程上创建无限循环。您应该安排一个每帧运行一次的任务,并且每帧只执行一次它的任务。有很多方法可以做到这一点,但在使用显示链接更新/委托或 nstimer 之类的东西之前,首先检查 glkit 中是否内置了一些东西。可能的指针:stackoverflow.com/questions/13653113/…
  • 抱歉,我确实需要一个无限循环,这就是模拟器的工作方式。每帧执行一项任务每秒钟最多可以给我 60 次“操作”,我需要的远不止这些(大约 1000000 倍左右……)对于屏幕更新,这很好,但不适用于 cpu 仿真
  • 我认为您需要阅读更多有关仿真、多线程和同步工作原理的信息。假设您的模拟机器的 cpu 以 1 mhz 运行,并且您有一个命令队列,每个命令都需要已知数量的 cpu 周期来执行。然后,您将运行 CPU 可以在给定时间范围内执行的那些(例如 1500 条)命令,并在主机上模拟它们。然后你开始一个新的循环。这是一个非常简化的模型。您将不需要无限循环,并且必须有同步点(通常以模拟机器的 vsync 速率)。
  • 这是一种方法,但不是唯一的方法。无限循环绝对是一个可行的选择(例如linklink)。归根结底,这并不是问题的重点,而更多的是架构选择,虽然我可能很容易改变我的代码以另一种方式运行,但我还没有准备好把它放在教条上“不要仅仅因为",除非有某些实际原因导致问题(我认为不是)
  • 您链接到的代码适用于桌面(win32)。考虑到您正在为移动设备开发,其中操作系统会采取措施防止应用程序消耗过多的 CPU 功率(耗尽电池),因此您的无限循环很可能是问题所在,因为从操作系统的角度来看,它看起来像一个线程锁定并可能需要限制或终止。由于您仅在 ios 上观察到这种情况,因此可能会证实我的观点(更多的预感)。至少尝试每隔一段时间(即每 1000 次迭代)产生一次线程,以测试它是否会改变观察到的运行时行为

标签: ios objective-c multithreading performance opengl-es


【解决方案1】:

操作系统可以降低 CPU 频率以减少能耗/节省电池。如果你的线程没有使用太多的 CPU 能力,那么操作系统会认为现在是降低频率的好时机。另一方面,在台式计算机上,还有许多其他线程/进程正在运行(并且阈值可能非常不同),这可能就是它似乎在模拟器/桌面应用程序中工作的原因。

检测到您的线程没有消耗太多 CPU 时间的原因可能有多种。一个是您调用 printf,并且内部可能有某种锁使您的线程等待(CACurrentMediaTime 也可能)。另一个可能与 GLKView 更新有关,虽然我不确定如何。

【讨论】:

  • 我感觉很奇怪,它会限制一个绝对不是空闲或浪费周期的线程,而是在做实际工作。我也考虑过你的第一个假设,但我的实际应用程序既没有 printf 也没有 CACurrentMediaTime (我添加它们是为了调试),而且据我所知,没有系统调用,因为它都是自定义代码。我仍然认为它可能与 OpenGL 有关,因为如果我禁用更新它运行良好...
  • 嗯,确实很奇怪。当 OpenGL 代码很清晰时也会发生这种情况,就像在您的示例中一样?
  • 如果 OpenGL 驱动程序抢占了您的线程,您可以使用 Xcode 的分析器查看。似乎它还包含能量诊断功能,也可能很有用。 developer.apple.com/library/mac/documentation/DeveloperTools/…
  • 我尝试运行一些 Instruments,但看起来我并没有从中得到太多...从能量一个 i.imgur.com/eyaNBDK.png 它看起来确实像图形使用在一段时间后翻了一番(我认为第一个高峰是由于应用程序启动...)从 OpenGL 一个 i.imgur.com/VZZPZEa.png 我不知道,看起来应用程序没有按预期做任何事情,只是一堆多余的清除调用...
  • 多核跟踪呢?
【解决方案2】:

所以,我仍然没有弄清楚为什么会发生这种情况,但我设法找到了一种解决方法,使用由 CGBitmapContext 支持的 CALayer 而不是使用 OpenGL,灵感来自 https://github.com/lmmenge/MeSNEmu

@interface GraphicLayer : CALayer
{
    CGContextRef _context;
}
@end

@implementation GraphicLayer

-(id)init
{
    self = [super init];
    if (self) {
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        _context = CGBitmapContextCreate(NULL, 418, 263, 8, 418 * 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
        CFRelease(colorSpace);
    }
    return self;
}

- (void)display
{
    CGImageRef CGImage = CGBitmapContextCreateImage(_context);
    self.contents = (__bridge id)(CGImage);
    CGImageRelease(CGImage);
}

@end

@interface GraphicView : UIView
@end

@implementation GraphicView

+ (Class)layerClass
{
    return [GraphicLayer class];
}

- (void)drawRect:(CGRect)rect
{
}

@end

使用这个循环不会减慢(要么有一个无限循环,要么在每一帧上执行一堆操作),但我不完全确定为什么......

【讨论】:

  • 您是否在其他类型的 iphone 上进行了测试?也许iphone 6有一个特殊的功能,当GPU运行东西时会降低CPU的时钟速度,而位图上下文不会触发这个。这有点牵强,但我见过启用 Wifi 的设备会自动降低 GPU 频率。
猜你喜欢
  • 2013-09-16
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-27
  • 2021-05-07
相关资源
最近更新 更多