Objetivo C iOS – objc_object :: sidetable_retain / release masticar el time de CPU en mi loop?

Estoy trabajando en un juego Spritekit Tower Defense. ARC está habilitado. (Y tengo la intención de ejecutar este código en segundo plano, aunque en este momento solo se está ejecutando en el hilo principal).

En mi ciclo de actualización (que se ejecuta hasta 60 veces por segundo) invoco un método llamado getTargetsForTowers . Después de perfilar este método, he encontrado dos elementos en la list que están masticando mi time de CPU: objc_object::sidetable_retain/release , y estoy tratando de descubrir cuáles son.

Me gustaría comprender más acerca de lo que es esto y si puedo mejorar el performance networkinguciéndolos o deshaciéndolos por completo.

Hay 300 enemigos y 446 torres en mi escenario de testing. La mayor parte del time de CPU se informa en el bucle de la torre.

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

Capturas de pantalla de Time Profile (después de 60 segundos de ejecución):

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

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

(He estado leyendo sobre bloques, arco, references fuertes / débiles, etc., así que traté de hacer que las variables (como CCSKTower * tower) __weak, que deshacían esos dos elementos, pero que agregaron un montón de nuevos elementos relacionados con la retención / creación / destrucción de las variables débiles, y creo que consumieron más time de CPU que antes).

Agradecería cualquier comentario sobre esto. Gracias.

EDITAR :

Hay otro método que también me gustaría mejorar, que es:

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

Lo he revisado repetidamente y no hay nada que realmente pueda ver. Tal vez no hay nada más que se pueda hacer.

Capturas de pantalla:

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

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

Una cuestión es que cree una nueva reference a tower.target , pero solo use esa reference una vez. Entonces, simplemente reescribiendo esa sección debería mejorar su desempeño, ej.

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

Según su comentario, parece que no hay forma de evitar una retención / liberación si accede a una propiedad en tower.target . Entonces probemos la cirugía radical. Específicamente, intente agregar una propiedad distanceToEnd a la torre, para realizar un seguimiento de la distanceToEnd para el objective actual de la torre. El código resultante se vería así.

 - (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 } } } } } } } 

Mi printing es que no hay mucho que hacer con el método getTilesForEnemy . En cuanto a la image del Running Time para getTilesForEnemy , está claro que la carga se distribuye de manera bastante uniforme entre los diversos componentes del método, con solo tres elementos por encima del 10%. El elemento superior getVectorForPoint ni siquiera está en el bucle más interno. El segundo elemento insertObject aparentemente es el resultado de la llamada addObject en el bucle interno, pero no hay nada que hacer para esa llamada, es necesario generar el resultado final.

En el siguiente nivel (ver la image de wvry.png ), puedes ver que getTilesForEnemy es ahora el 15.3% del time total gastado en getTargetsForTowers . Entonces, incluso si fuera posible networkingucir getVectorForPoint del 17.3% al 7.3%, no habría una networkingucción significativa en el time de ejecución. Los ahorros en getTilesForEnemy serían del 10%, pero debido a que getTilesForEnemy es solo el 15.3% del time en getTargetsForTowers , el ahorro general sería solo del 1.53%.

Conclusión, porque los componentes de getTilesForEnemy están equilibrados y por debajo del 20%, y porque getTilesForEnemy es solo el 15.3% del método de nivel superior, no se obtendrán ahorros significativos al tratar de optimizar getTilesForEnemy .

Entonces, una vez más, la única opción es la cirugía radical, y esta vez me refiero a una reescritura total del algorithm. Dicha acción solo se debe tomar si la aplicación aún no se ajusta a las especificaciones. Te has topado con las limitaciones de ARC y NSArray. Ambas tecnologías son extremadamente potentes y flexibles, y son perfectas para el desarrollo de alto nivel. Sin embargo, ambos tienen una sobrecarga significativa que limita el performance. Entonces, la pregunta es: "¿Cómo escribes getTargetsForTowers sin usar ARC y NSArray's?". La respuesta es usar matrices de estructuras C para representar los objects. El pseudo código de nivel superior resultante sería algo como esto

 copy the enemy information into an array of C structs copy the tower information into an array of C structs (note that the target for a tower is just an 'int', which is the index of an enemy in the enemy array) for ( each enemy in the enemy array ) { create an array of C structs for the tiles for ( each tile ) for ( each tower in the tile ) update the tower target if needed } copy the updated tower information back into the NSArray of tower objects 

Para su segundo método, esta parte parece poco clara e ineficiente:

  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)) { 

Por ejemplo, no tiene sentido girar el bucle y si su x está fuera de límites. Podrías hacer esto:

  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)) { 

Lo más importante es que el punto del primer ciclo es comenzar con un mínimo y boostlo a un máximo, y la instrucción if está ahí para asegurarse de que x es al less un mínimo y menor que un máximo, por lo que no hay ninguna razón para tiene un for () y un if (). No sé cómo se verían los valores para topLeft.dx y gameHalfCols, así que no puedo decirte cuál es la mejor manera de hacerlo.

Pero, por ejemplo, si topLeft.dx es siempre integral, podría decir:

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

También podrías mejorar la 'y' de esta manera. Esto no significa que haya less líneas de código, sino que también evita que los loops giren un montón de veces extra sin efecto: las declaraciones 'if' solo hacen que los loops giren rápidamente hacia sus extremos, pero incluyendo la lógica dentro de 'for' ellos mismos los hace solo bucle sobre los valores que realmente usarás en los cálculos.

Para ampliar mis comentarios a una respuesta completa:

El comportamiento normal y correcto de Objective-C al devolver una propiedad de object es retain y luego autorelease . Eso es porque, de lo contrario, este código (imagina que estás en el mundo antes de ARC):

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

De lo contrario, sería inválido. object se ha desasignado, por lo que si someResult no se ha retenido y autoelegido, se convertirá en un puntero colgante. ARC hace este tipo de un poco less directo (la reference fuerte en someResult habría conservado el número más allá de la duración del object pero luego habría sido autoreleased para el return ) pero el principio permanece y, en cualquier caso, si un individuo .m file se ha comstackdo con ARC, no se supone que afecte a las personas que llaman.

(aparte: tenga en count que el weak no solo es fuerte sin retenciones: tiene costos relacionados porque el time de ejecución tiene que establecer un enlace desde el object a la reference débil para saber si lo encuentra de nuevo y nada si el object comienza a desasignarse)

Supongamos que querías crear un nuevo tipo de propiedad que no sea strong y que no sea unsafe_unretained pero que esté definido para que el object devuelto sea seguro para su uso mientras el propietario original esté vivo pero no sea seguro después. Por lo tanto, es un set strong , pero una unsafe_unretained .

No se ha probado pero creo que el medio correcto para hacerlo sería:

 // we can't write want a synthesised getter that doesn't attempt to retain // or autorelease, so we'd better flag up the pointer as potentially being // unsafe to access @property (nonatomic, unsafe_unretained) NSNumber *someResult; ... @implementation TYWhatever { NSNumber *_retainedResult; // a strong reference, since // I haven't said otherwise — // this reference is not publicly exposed } - (void)setSomeResult:(NSNumber *)number { // set the unsafe and unretained version, // as the default setter would have _someResult = number; // also take a strong reference to the object passed in, // to extend its lifecycle to match ours _retainedResult = number; } 

Va a ser bastante detallado al agregar más properties, pero lo que está haciendo es contrario a las convenciones normales de Objective-C, por lo que es probable que la ayuda del comstackdor sea limitada.