我正在开发 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
答案 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约定,因此可能需要有限的编译器帮助。