2010-05-17 13 views
2

Ok donc après avoir passé deux jours à essayer de comprendre le problème, et à lire des articles dizillion, j'ai finalement décidé de demander conseil (ma première fois ici).Ralentissements lors de la lecture d'un flux d'entrée d'urlconnection (même avec octets [] et tampons)

Maintenant à la question à portée de main - Je suis en train d'écrire un programme qui va analyser les données api d'un jeu, à savoir les journaux de combat. Il y aura BEAUCOUP d'entrées dans la base de données (plus de 20 millions) et donc la vitesse d'analyse pour chaque page de journal de bataille importe un peu. Les pages à analyser ressemblent à ceci: http://api.erepublik.com/v1/feeds/battle_logs/10000/0. (voir le code source si vous utilisez chrome, il n'affiche pas la page à droite). Il a 1000 entrées à succès, suivies d'une petite information de combat (la dernière page aura < 1000 évidemment). En moyenne, une page contient 175000 caractères, codage UTF-8, format xml (v 1.0). Le programme s'exécutera localement sur un bon PC, la mémoire est pratiquement illimitée (de sorte que la création d'octets [250000] est tout à fait correcte).

Le format ne change jamais, ce qui est assez pratique.

Maintenant, j'ai commencé comme d'habitude:

//global vars,class declaration skipped 

    public WebObject(String url_string, int connection_timeout, int read_timeout, boolean redirects_allowed, String user_agent) 
        throws java.net.MalformedURLException, java.io.IOException { 
       // Open a URL connection 
       java.net.URL url = new java.net.URL(url_string); 
       java.net.URLConnection uconn = url.openConnection(); 
       if (!(uconn instanceof java.net.HttpURLConnection)) { 
        throw new java.lang.IllegalArgumentException("URL protocol must be HTTP"); 
       } 
       conn = (java.net.HttpURLConnection) uconn; 
       conn.setConnectTimeout(connection_timeout); 
       conn.setReadTimeout(read_timeout);  
       conn.setInstanceFollowRedirects(redirects_allowed); 
       conn.setRequestProperty("User-agent", user_agent); 
      } 
    public void executeConnection() throws IOException { 
      try { 
       is = conn.getInputStream(); //global var 
       l = conn.getContentLength(); //global var   
      } catch (Exception e) { 
      //handling code skipped 
      } 
    } 

//getContentStream and getLength methods which just return'is' and 'l' are skipped 

Voici où la partie amusante a commencé. J'ai exécuté un profilage (using System.currentTimeMillis()) pour savoir ce qui prend longtemps, et ce qui ne marche pas. L'appel à cette méthode ne prend que 200ms sur avg

public InputStream getWebPageAsStream(int battle_id, int page) throws Exception { 
    String url = "http://api.erepublik.com/v1/feeds/battle_logs/" + battle_id + "/" + page; 
    WebObject wobj = new WebObject(url, 10000, 10000, true, "Mozilla/5.0 " 
      + "(Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)"); 
    wobj.executeConnection(); 
    l = wobj.getContentLength(); // global variable 
    return wobj.getContentStream(); //returns 'is' stream 
} 

200ms est très attendue d'une opération de réseau, et je suis très bien avec elle. MAIS quand j'analyse le inputStream d'une façon ou d'une autre (le lire en chaîne/utiliser java XML parser/le lire dans un autre ByteArrayStream) le processus prend plus de 1000ms!

par exemple, ce code prend 1000ms si je passe le courant i ai ('est') au-dessus de getContentStream() directement à cette méthode:

public static Document convertToXML(InputStream is) throws ParserConfigurationException, IOException, SAXException { 
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 
     Document doc = db.parse(is); 
     doc.getDocumentElement().normalize(); 
     return doc; 
    } 

ce code aussi dure environ 920ms IF initial InputStream « est » est passé dans (donT lu dans le code lui-même - il extrait uniquement les données dont j'ai besoin en comptant directement les caractères, ce qui peut être fait grâce au format d'alimentation api rigide):

public static parsedBattlePage convertBattleToXMLWithoutDOM(InputStream is) throws IOException { 
     // Point A 
     BufferedReader br = new BufferedReader(new InputStreamReader(is)); 
     LinkedList ll = new LinkedList(); 
     String str = br.readLine(); 
     while (str != null) { 
      ll.add(str); 
      str = br.readLine(); 
     }   
     if (((String) ll.get(1)).indexOf("error") != -1) { 
      return new parsedBattlePage(null, null, true, -1); 
     } 
     //Point B 
     Iterator it = ll.iterator(); 
     it.next(); 
     it.next();  
     it.next(); 
     it.next(); 
     String[][] hits_arr = new String[1000][4]; 
     String t_str = (String) it.next(); 
     String tmp = null; 
     int j = 0; 
     for (int i = 0; t_str.indexOf("time") != -1; i++) { 
      hits_arr[i][0] = t_str.substring(12, t_str.length() - 11); 
      tmp = (String) it.next(); 
      hits_arr[i][1] = tmp.substring(14, tmp.length() - 9); 
      tmp = (String) it.next(); 
      hits_arr[i][2] = tmp.substring(15, tmp.length() - 10); 
      tmp = (String) it.next(); 
      hits_arr[i][3] = tmp.substring(18, tmp.length() - 13); 
      it.next(); 
      it.next(); 
      t_str = (String) it.next(); 
      j++; 
     }  
     String[] b_info_arr = new String[9]; 
     int[] space_nums = {13, 10, 13, 11, 11, 12, 5, 10, 13}; 
     for (int i = 0; i < space_nums.length; i++) { 
      tmp = (String) it.next(); 
      b_info_arr[i] = tmp.substring(space_nums[i] + 4, tmp.length() - space_nums[i] - 1); 
     } 
     //Point C 
     return new parsedBattlePage(hits_arr, b_info_arr, false, j); 
    } 

J'ai essayé de remplacer le BufferedReader par défaut avec

BufferedReader br = new BufferedReader(new InputStreamReader(is), 250000); 

Cela n'a pas beaucoup changé. Mon deuxième essai a été de remplacer le code entre A et B par: Iterator it = IOUtils.lineIterator (est, "UTF-8"); Même résultat, sauf que cette fois A-B était 0ms, et B-C était 1000ms, alors chaque appel à it.next() doit avoir consommé un temps significatif (IOUtils vient de la bibliothèque apache-commons-io).

Et voici le coupable - le temps nécessaire pour analyser le flux en chaîne, que ce soit par un itérateur ou BufferedReader dans TOUS les cas était d'environ 1000ms, tandis que le reste du code prenait 0ms (par exemple non pertinent). Cela signifie que l'analyse du flux vers LinkedList, ou l'itération de celui-ci, pour une raison quelconque, a consommé une grande partie de mes ressources système. question était - pourquoi? Est-ce juste la façon dont java est faite ... non ... c'est juste stupide, alors j'ai fait une autre expérience.

Dans ma méthode principale j'ajouté après la getWebPageAsStream():

//Point A 
    ba = new byte[l]; // 'l' comes from wobj.getContentLength above 
    bytesRead = is.read(ba); //'is' is our URLConnection original InputStream 
    offset = bytesRead;   
    while (bytesRead != -1) { 
     bytesRead = is.read(ba, offset - 1, l - offset); 
     offset += bytesRead; 
    } 
    //Point B 
    InputStream is2 = new ByteArrayInputStream(ba); 
    //Now just working with 'is2' - the "copied" stream 

Le InputStream-> byte [] a repris la conversion 1000ms - c'est la façon dont beaucoup ppl ont suggéré de lire un InputStream et stil il est lent. Et devinez quoi - les 2 méthodes de l'analyseur ci-dessus (convertToXML() et convertBattlePagetoXMLWithoutDOM(), quand passé 'is2' au lieu de 'est' a pris, dans tous les 4 cas, sous 50ms pour terminer

J'ai lu une suggestion que le attend de flux pour la connexion à fermer avant débouchage, donc j'essayé d'utiliser HttpComponentsClient 4.0 (http://hc.apache.org/httpcomponents-client/index.html) à la place, mais le InputStream initial a pris aussi longtemps pour analyser par exemple ce code:.

public InputStream getWebPageAsStream2(int battle_id, int page) throws Exception { 
     String url = "http://api.erepublik.com/v1/feeds/battle_logs/" + battle_id + "/" + page; 
     HttpClient httpclient = new DefaultHttpClient(); 
     HttpGet httpget = new HttpGet(url);  
     HttpParams p = new BasicHttpParams(); 
     HttpConnectionParams.setSocketBufferSize(p, 250000); 
     HttpConnectionParams.setStaleCheckingEnabled(p, false); 
     HttpConnectionParams.setConnectionTimeout(p, 5000); 
     httpget.setParams(p);   
     HttpResponse response = httpclient.execute(httpget); 
     HttpEntity entity = response.getEntity(); 
     l = (int) entity.getContentLength(); 
     return entity.getContent(); 
    } 

a pris encore plus longtemps à traiter (50ms plus Les temps d'analyse des flux sont restés les mêmes, ce qui peut évidemment être instancié de manière à ne pas créer HttpClient et ses propriétés à chaque fois (temps réseau plus court), mais le problème de flux ne sera pas affecté par cela. Nous en arrivons donc au problème du centre - pourquoi le URLConnection InputStream (ou HttpClient InputStream) initial est-il si long à traiter, alors que tout flux de même taille et contenu créé localement est plus rapide? Je veux dire, la réponse initiale est déjà quelque part dans la mémoire vive, et je ne vois pas pourquoi il est traité si lentement par rapport à quand un même flux est créé à partir d'un octet []. Considérant que je dois analyser des millions d'entrées et des milliers de pages comme ça, un temps de traitement total de près de 1.5s/page semble WAY WAY trop long.

Des idées?

P.S. S'il vous plaît demander dans plus de code est nécessaire - la seule chose que je fais après l'analyse est de faire un PreparedStatement et mettre les entrées dans JavaDB en paquets de 1000+, et la performance est ok ~ 200ms/1000entries, prb pourrait être optimisé avec plus de cache mais Je n'ai pas regardé ça beaucoup.

Répondre

1

Cela prend plus de temps car il lit depuis le serveur distant. Votre méthode executeConnection() crée simplement le flux, elle ne lit pas la réponse complète du serveur. Cela se fait une fois que vous commencez à lire dans le flux.

+1

alors pourquoi existe-t-il une méthode appelée conn.connect()? afin que je puisse faire: java.net.URL url = new java.net.URL (url_string); java.net.URLConnection uconn = url.openConnection(); conn = (java.net.HttpURLConnection) uconn; conn.connect(); // ne devrait pas cette déclaration charger la page entière déjà sur la machine locale ?? conn.getInputStream(); // wouldnt cela juste vous donner le flux vers les données déjà chargées? et si votre réponse est vraie, est-il possible de charger la page entière avant d'appeler inputstream? et puis est = conn.get – Nikita

+1

Non, connect() doit créer une connexion et read() doit être lu. – EJP