Objective C iOS - objc_object :: sidetable_retain / release占用循环中的CPU时间?

时间:2014-04-01 01:19:23

标签: ios objective-c

我正在开发 Spritekit 塔防游戏。 ARC 已启用。 (我打算在后台运行此代码,但目前它只是在主线程上运行。)

在我的更新循环中(每秒运行60次)我调用了一个名为getTargetsForTowers的方法。在分析了这种方法后,我发现列表中的两个项目正在缩短我的CPU时间:objc_object::sidetable_retain/release,我试图找出它们是什么。

我想更多地了解这是什么,以及我是否可以通过减少它们或完全摆脱它们来提高性能。

在我的测试场景中有300个敌人和446个塔。塔循环中报告了大部分CPU时间。

- (void)getTargetsForTowers {
    NSArray *enemiesCopy = [enemiesOnMap copy];
    for (CCUnit *enemy in enemiesCopy) {
        float edte = enemy.distanceToEnd;
        CGPoint enemyPos = enemy.position;
        [self calculateTravelDistanceForEnemy:enemy];
        if (enemy.actualHealth > 0) {
            NSArray *tiles = [self getTilesForEnemy:enemy];
            for (CCTileInfo *tile in tiles) {
                NSArray *tileTowers = tile.towers;
                for (CCSKTower *tower in tileTowers) {
                    BOOL hasTarget = tower.hasTarget;
                    BOOL passes = !hasTarget;
                    if (!passes) {
                        CCUnit *tg = tower.target;
                        float tdte = tg.distanceToEnd;
                        passes = edte < tdte;
                    }
                    if (passes) {
                        BOOL inRange = [self circle:tower.position withRadius:tower.attackRange collisionWithCircle:enemyPos collisionCircleRadius:1];
                        if (inRange) {
                            tower.hasTarget = YES;
                            tower.target = enemy;
                        }
                    }
                }
            }
        }
    }
}

时间档案的截图(运行60秒后):

image one http://imageshack.com/a/img22/2258/y18v.png

image two http://imageshack.com/a/img833/7969/7fy3.png

(我一直在阅读关于块,弧,强/弱引用等等,所以我尝试制作变量(例如CCSKTower *塔)__ weak,它确实摆脱了这两个项目,但是添加了一大堆与保留/创建/销毁弱变量相关的新项目,我认为它们消耗的CPU时间比以前多。)

我很感激对此的任何意见。感谢。

修改

我想改进的另一种方法是:

- (NSArray *)getTilesForEnemy:(CCUnit *)enemy {
    NSMutableArray *tiles = [[NSMutableArray alloc] init];

    float enemyWidthHalf = enemy.size.width/2;
    float enemyHeightHalf = enemy.size.height/2;

    float enemyX = enemy.position.x;
    float enemyY = enemy.position.y;

    CGVector topLeft = [self getVectorForPoint:CGPointMake(enemyX-enemyWidthHalf, enemyY+enemyHeightHalf)];
    CGVector topRight = [self getVectorForPoint:CGPointMake(enemyX+enemyWidthHalf, enemyY+enemyHeightHalf)];
    CGVector bottomLeft = [self getVectorForPoint:CGPointMake(enemyX-enemyWidthHalf, enemyY-enemyHeightHalf)];
    CGVector bottomRight = [self getVectorForPoint:CGPointMake(enemyX+enemyWidthHalf, enemyY-enemyHeightHalf)];

    CCTileInfo *tile = nil;
    for (float x = topLeft.dx; x < bottomRight.dx+1; x++) {
        for (float y = bottomLeft.dy; y < topRight.dy+1; y++) {
            if (x > -(gameHalfCols+1) && x < gameHalfCols) {
                if (y < gameHalfRows && y > -(gameHalfRows+1)) {
                    int xIndex = (int)(x+gameHalfCols);
                    int yIndex = (int)(y+gameHalfRows);
                    tile = tileGrid[xIndex][yIndex];
                    if (tile != nil) {
                        [tiles addObject:tile];
                    }
                }
            }
        }
    }
    return tiles;
}

我反复看过它,我真的看不到任何东西。也许没有什么可以做的。

截图:

image three http://imagizer.imageshack.us/v2/640x480q90/59/4cle.png

image four http://imagizer.imageshack.us/v2/640x480q90/811/98su.png

3 个答案:

答案 0 :(得分:4)

一个问题是您创建了对tower.target的新引用,但只使用该引用一次。因此,只需重写该部分即可改善您的表现,例如

if (!passes) {
    float tdte = tower.target.distanceToEnd;
    passes = edte < tdte;
}

根据您的评论,如果您访问tower.target上的某个媒体资源,似乎无法避免保留/释放。所以,让我们尝试一下根治性手术。具体来说,尝试向塔中添加distanceToEnd属性,以跟踪塔的当前目标的distanceToEnd。生成的代码看起来像这样。

- (void)getTargetsForTowers {

    // initialization to copy 'distanceToEnd' value to each tower that has a target
    for ( CCSKTower *tower in towersOnMap )
        if ( tower.hasTarget )
            tower.distanceToEnd = tower.target.distanceToEnd;

    NSArray *enemiesCopy = [enemiesOnMap copy];
    for (CCUnit *enemy in enemiesCopy) {
        float edte = enemy.distanceToEnd;
        CGPoint enemyPos = enemy.position;
        [self calculateTravelDistanceForEnemy:enemy];
        if (enemy.actualHealth > 0) {
            NSArray *tiles = [self getTilesForEnemy:enemy];
            for (CCTileInfo *tile in tiles) {
                NSArray *tileTowers = tile.towers;
                for (CCSKTower *tower in tileTowers) {
                    if ( !tower.hasTarget || edte < tower.distanceToEnd ) {
                        BOOL inRange = [self circle:tower.position withRadius:tower.attackRange collisionWithCircle:enemyPos collisionCircleRadius:1];
                        if (inRange) {
                            tower.hasTarget = YES;
                            tower.target = enemy;
                            tower.distanceToEnd = edte;   // update 'distanceToEnd' on the tower to match new target
                        }
                    }
                }
            }
        }
    }
}

我的印象是getTilesForEnemy方法没有太多工作要做。查看Running Time的{​​{1}}图片,可以清楚地看到负载在方法的各个组件中相当均匀地分布,只有三个项目超过10%。顶部项getTilesForEnemy即使在最里面的循环中也是如此。第二项getVectorForPoint显然是内循环中insertObject调用的结果,但是对于该调用没有什么可做的,它需要生成最终结果

在上一级(请参阅addObject图片),您可以看到wvry.png现在占getTilesForEnemy总时间的15.3%。因此,即使可以将getTargetsForTowers从17.3%减少到7.3%,也不会显着减少运行时间。 getVectorForPoint节省的费用为10%,但由于getTilesForEnemy只占getTilesForEnemy的15.3%,因此整体节省仅为1.53%。

结论,因为getTargetsForTowers的组件是平衡的并且低于20%,并且因为getTilesForEnemy只是高级方法的15.3%,所以通过尝试优化{{{ 1}}。

所以再次唯一的选择是根治性手术,这次我的意思是完全重写算法。只有在应用仍然无法达到规格时,才应采取此类操作。您已经遇到了ARC和NSArray的限制。这两种技术都非常强大和灵活,非常适合高级开发。但是,它们都有很大的开销限制了性能。所以问题就变成了,#34;如何在不使用ARC和NSArray的情况下编写getTilesForEnemy?&#34;。答案是使用C结构数组来表示对象。由此产生的顶级伪代码将是这样的

getTilesForEnemy

答案 1 :(得分:1)

对于你的第二种方法,这部分似乎不清楚且效率低下:

    for (float x = topLeft.dx; x < bottomRight.dx+1; x++) {
        for (float y = bottomLeft.dy; y < topRight.dy+1; y++) {
            if (x > -(gameHalfCols+1) && x < gameHalfCols) {
                if (y < gameHalfRows && y > -(gameHalfRows+1)) {

例如,如果x超出界限,则旋转y循环没有意义。你可以这样做:

    for (float x = topLeft.dx; x < bottomRight.dx+1; x++) {
        if (x > -(gameHalfCols+1) && x < gameHalfCols) {
            for (float y = bottomLeft.dy; y < topRight.dy+1; y++) {
                if (y < gameHalfRows && y > -(gameHalfRows+1)) {

更重要的是,第一个for循环的要点是以某个最小值开始x并将其增加到某个最大值,并且if语句用于确保x至少是某个最小值且小于某个最大值,因此有没有理由同时拥有for()和if()。我不知道topLeft.dx和gameHalfCols的值是什么样的,所以我不能告诉你最好的方法。

但是,例如,如果topLeft.dx始终是整数,您可能会说:

    for (float x = MAX(topLeft.dx, ceil(-(gameHalfCols+1))); x < bottomRight.dx+1 && x < gameHalfCols; x++) {
        for (float y = ...

你可以用这种方式同样改善'y'。这不仅仅是更少的代码行,它还可以防止循环旋转一堆额外的时间而不起作用:'if'语句只是让循环快速旋转到它们的末端,但是包含'for'本身内部的逻辑使它们只循环你实际在计算中使用的值。

答案 2 :(得分:0)

将我的评论扩展为完整答案:

返回对象属性时,正常,正确的Objective-C行为是retain,然后是autorelease。那是因为否则这样的代码(想象你在ARC之前的世界):

TYTemporaryWorker *object = [[TYTemporaryWorker alloc] initWithSomeValue:value];
NSNumber *someResult = object.someResult;
[object release];
return someResult;

否则将无效。 object已被解除分配,因此如果someResult未被保留并自动释放,则它将成为悬空指针。 ARC使得这种方式稍微不那么直接(someResult中的强引用会保留超过object生命周期的数字,但之后它会针对return自动释放,但原则是在任何情况下,是否使用ARC编译单个.m文件不会影响调用者。

(除了:注意weak不仅没有保留就强大 - 是否有相关成本,因为运行时必须建立从对象到弱引用的链接,以便知道再找到它{{{ 1}}如果对象开始解除分配)

假设您要创建一种非nil且不是strong的新属性,而是定义为返回的对象可以安全使用,只要原来的主人活着但后来不安全。所以它是unsafe_unretained集,但是strong得到。

这是未经测试但我认为正确的方法是:

unsafe_unretained

当您添加更多属性时,它会变得非常冗长,但您正在做的是违反正常的Objective-C约定,因此可能需要有限的编译器帮助。