Predošlý tím identifikoval ako jeden z problémov neefektívnosť momentálneho riadiaceho kódu, ktorý sa z určitej časti podieľa na malej výkonnosti hráča. Ide hlavne o časti, ktoré sa starajú o spracovávanie komunikácie so serverom a následného ovládania hráča. Cieľom analýzy je zistiť konkrétne problémy v neefektívnosti kódu a navrhnúť vylepšenia, ktoré budú viesť k jeho optimalizácii.
Tento nástroj pomáha pri sledovaní výkonnosti a debugovaní Java aplikácií na základe monitorovania operácií na JVM úrovni. Pomocou rôznych techník sleduje čas procesora pri jednotlivých metódach a vláknach, tvorbu objektov, alokáciu pamäte apod. Yourkit ponúka hlavne prepracovaný CPU profiling, ktorý môžeme využiť na analýzu náročnosti metód a ich optimalizácie.
Profiling ponúka 3 možnosti:
Pre najpresnejšie zmeranie spotrebovaného času volanými metódami je najvhodnejšia Tracing metóda, čo vyplýva z následujúceho obrázka.
Yourkit takisto po dostatočne dlhom behu programu ponúka uloženie zozbieraných výsledkov vo forme snapshotov, ktoré budú pre analýzu vhodnejšie, než sledovanie výsledkov aktuálneho behu.
Pomocou spomínanej metódy Tracing sme zmerali celkový čas jednotlivých vykonaných metód. Výstupom sú teda zoradené metódy podľa času s ohľadom na počet ich invokácií.
Pomocou tejto štatistiky môžeme ďalej analyzovať vykonávanie programu a hlbším vnorením do metód zistiť, aké kúsky kódu sú pre CPU časovo najnáročnejšie.
Ako je z obrázku zrejmé, spracovanie komunikácie, aktualizovanie stavu hráča a príkazov prebieha iba pomocou jedného vlákna v dôsledku čoho pravdepodobne dochádza k zahadzovaniu prijatých správ, ktoré agent prijme počas spracovávania inej správy. Toto spracovanie trvá väčšinu spotrebovaného času, čomu sa však nedá vyhnúť. Veľkým zlepšením by ale mohlo byť vytvorenie nového vlákna pre spracovanie každej novej správy. V rámci ostatných metód bude optimalizácia spočívať v nahradení jednotlivých kódových konštrukcií výpočtovo menej náročnými kúskami kódu.
Táto metóda beží od začiatku pripojenia k serveru a spracováva prijaté správy o stave hráča z pohľadu servera a teda zaberá drvivú väčšinu celkového času.
private void mainLoop() throws IOException {
while(true){ // wait for input while (input.available() == -1) { Thread.yield(); }
// receive message String incoming = receive(); parser.parse(incoming);
// process message HighSkillRunner.proceed();
// transmit message transmit(outMessageBuffer.append("(syn)").toString()); outMessageBuffer = new StringBuilder(); }
}
V tejto metóde je ďalej volané spracovanie správy pomocou metódy Parser.parse(incoming). Vytvorenie nového vlákna pre každé toto volanie by mohlo priniesť zlepšenie v celkovej rýchlosti spracovávania správ a tým pádom bude mať hráč presnejšie informácie o jeho stave.
V rámci metódy dochádza k rozloženiu správy na jednotlivé informácie, ktoré server ponúka. Ďalej sa tieto informácie preložia do formy dát, ktoré sa ďalej dajú v programe spracovávať. Nakoniec sa zavolá aktualizovanie stavu objektov, ktoré ovládajú hráča.
public ParsedData parse(String message) { data = new ParsedData(); this.message = message; String[] breakDown = breakDown(); for (String perceptor : breakDown) { String perceptorId = perceptor.substring(0, perceptor.indexOf(' ')); Perceptors.processPerceptor(perceptorId, perceptor, data); } notifyObservers(); return data; }
Priestoru na optimalizáciu veľa nie je, keďže obsiahnuté metódy až na Parser.notifyObservers() podľa Yourkitu príliš náročné nie sú.
V tejto metóde sa už volajú konkrétne objekty, ktoré sa starajú o spracovávanie preložených dát a aktualizovanie stavu hráča alebo hracieho sveta a ovládajú jeho správanie.
private void notifyObservers() { synchronized (Parser.class){ for (ParsedDataObserver observer : observers) observer.processNewServerMessage(data); } }
Priamo v metóde nie je možnosť nejakého optimalizovania, to bude možné až pri spracovávaní správ objektami observer.
Táto metóda je jedna z dvoch, ktoré sú podľa Yourkitu medzi spracovávaním objektami observer časovo najnáročnejšie.
public void processNewServerMessage(ParsedData data) { if (data.ballRelativePosition != null) { calculateBallPosition(data); } calculatePlayers(data.otherplayers, data.SIMULATION_TIME); if (data.lines.size()> 0) { calculateLines(data); } try { TestFrameworkCommunication.sendMessage(new Message().WorldModel().changed(WorldModel.getInstance())); } catch(Exception e) { LOG.log(LogType.WARNING, "Chyba v spracovani spravy vo WorldModeli", e); } }
V metóde sa potom vypočítava aktuálny stav hracích tímov, ich pozície a pozícia lopty. Tieto metódy však nie sú kritické. Ďalej sa tu ešte vykonáva informovanie testovacieho frameworku o aktuálnom stave, čo je z pohľadu času kritické. Vypnutím tohto odosielania keď nie je potrebné by sme mohli ušetriť nejaký čas.
Pomocou tejto metódy sa vytvorí správa o aktuálnom stave hracieho sveta, ktorá sa odošle do testovacieho frameworku.
public Message changed(sk.fiit.jim.agent.models.WorldModel model) { message += " beginData\n"; try { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(outStream); out.writeObject(model); out.close(); byte[] data = outStream.toByteArray(); String to_send = Base64.encodeBase64String(data); outStream.close(); MessageDigest md = MessageDigest.getInstance("SHA-1"); Formatter formatter = new Formatter(); for (byte b : md.digest(data)) { formatter.format("%02x", b); } String checksum = formatter.toString(); message += checksum + "\n"; message += to_send; message += "endData"; formatter.close(); } catch (Exception e) { LOG.log(Level.WARNING, "Couldnt process the message about the world. Cause: " + e.getMessage()); } return Message.this;
V metóde je najkritickejší zápis dátového objektu do bajtového output streamu. Potom je tu náročné ešte kódovanie Base64 stringu a transformácia na jeho hash hodnotu s SHA-1. Tieto samotné metódy sa však príliš zmeniť nedajú.
V tejto metóde sa prepočítava stav tela hráča, pozícia častí a natočenie. Po jej zbehnutí by mal hráč mať informácie o svojej pozícii a orientácii.
public void processNewServerMessage(ParsedData data) { if (data.PLAYER_ID != null && data.PLAYER_ID != 0) { AgentInfo.getInstance().setPlayerId(data.PLAYER_ID); } if (data.OUR_SIDE_IS_LEFT != null) { AgentInfo.getInstance().setAssignedSide(true); AgentInfo.side = data.OUR_SIDE_IS_LEFT ? Side.LEFT : Side.RIGHT; } /* * According to values and server settings, if sides change at half * time, side is changed. Roman Moravcik (Gitmen) */ if (HALF_TIME_CHANGE_SIDES && !sidesChanged && data.GAME_TIME>= HALF_TIME) { sidesChanged = true; AgentInfo.side = AgentInfo.side == Side.RIGHT ? Side.LEFT : Side.RIGHT; LOG.log(LogType.AGENT_MODEL, "Sides changed at half time"); } deleteHistory(data); lastDataReceived = data; lastAccelerometer = data.accelerometer; updateJointPositions(data); adjustRotationsFor(data.gyroscope); updateRotations(data); updatePureBodyAcceleration(data); if(extendHistory(data)) updatePosition(data); // Following methods are used for Zero moment point updateBodyPartsPositions2(); updateCenterOfMass(); updateFeetForce(data); updateZeroMomentPoint(); updateSpeedX(data); if (data.fixedObjects != null && data.fixedObjects.size()> 0) lastTimeFlagSeen = data.SIMULATION_TIME; // End zero moment point updateHistory(data); }
Časovo náročná je tu metóda AgentModel.updateBodyPartsPositions2(), v ktorej sa nachádzajú vektorové výpočty avšak najviac času tu zaberá výpis do logu. Odstránením tohto výpisu by sme mali ušetriť nejaký čas.
Pomocou nástroja Yourkit sme mohli identifikovať metódy, ktoré sú počas behu programu časovo najnáročnejšie a bližšie pochopiť, ktoré konkrétne časti sú za to zodpovedné. Na základe týchto poznatkov dokážeme niektoré kódové konštrukcie optimalizovať a zlepšiť tým celkový výkon hráča. V prvom rade ide o zavedenie viacerých vlákien pri spracovávaní komunikácie so serverom a odstránenie niektorých funkcií, ktoré nie sú pre beh programu nevyhnutné. Všetky tieto zmeny sa budeme snažiť v kóde implementovať a tým by sa mal problém s výkonnosťou do určitej úrovne zmierniť.