Замедление при чтении из входного потока urlconnection (даже с байтом [] и буферами) - PullRequest
2 голосов
/ 17 мая 2010

Хорошо, потратив два дня, пытаясь разобраться в проблеме, и прочитав о dizillion статьях, я, наконец, решил подойти и попросить совета (мой первый раз здесь).

Теперь к вопросу - я пишу программу, которая будет анализировать данные API из игры, а именно журналы сражений. В базе данных будет много записей (более 20 миллионов), поэтому скорость разбора для каждой страницы журнала сражений имеет большое значение.

Анализируемые страницы выглядят так: http://api.erepublik.com/v1/feeds/battle_logs/10000/0. (см. исходный код, если используется Chrome, он не отображает страницу справа). Он имеет 1000 записей попаданий, за которыми следует небольшая информация о битве (на последней странице будет очевидно <1000). В среднем страница содержит 175000 символов, кодировка UTF-8, формат xml (v 1.0). Программа будет работать локально на хорошем ПК, память практически не ограничена (так что создание байта [250000] вполне нормально). </p>

Формат никогда не меняется, что очень удобно.

Теперь я начал как обычно:

//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

Вот тут и началась забавная часть. Я запустил некоторое профилирование (используя System.currentTimeMillis ()), чтобы выяснить, что занимает много времени, а что нет. Вызов этого метода занимает всего 200 мс на 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
}

200мс вполне ожидаемо от сетевой операции, и я в порядке. НО, когда я анализирую inputStream любым способом (читаем его в строку / используем java XML parser / читаем его в другой ByteArrayStream), этот процесс занимает более 1000 мс!

например, этот код занимает 1000 мс, если я передаю полученный поток ('is') выше из getContentStream () непосредственно этому методу:

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

этот код тоже занимает около 920 мс, если передается начальный InputStream 'is' (не считывает сам код - он просто извлекает нужные мне данные путем прямого подсчета символов, что можно сделать благодаря жесткому API формат канала):

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

Я попытался заменить BufferedReader по умолчанию на

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

Это не сильно изменилось. Моя вторая попытка состояла в том, чтобы заменить код между A и B на: Iterator it = IOUtils.lineIterator (есть, "UTF-8");

Тот же результат, за исключением того, что на этот раз AB было 0 мс, а BC было 1000 мс, поэтому каждый вызов it.next () должен был занимать некоторое значительное время (IOUtils из библиотеки apache-commons-io).

А вот и виновник - время, необходимое для синтаксического анализа потока в строку, будь то с помощью итератора или BufferedReader, во ВСЕХ случаях составляло около 1000 мс, в то время как остаток кода занимал 0 мс (например, не имеет значения). Это означает, что анализ потока в LinkedList или его перебор по некоторым причинам потреблял много системных ресурсов. вопрос был - почему? Это просто способ сделать Java ... нет ... это просто глупо, поэтому я сделал еще один эксперимент.

В моем основном методе, который я добавил после 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

Преобразование InputStream-> byte [] снова заняло 1000 мс - так многие люди предложили прочитать InputStream, и все же это медленно. И угадайте, что - 2 приведенных выше метода синтаксического анализа (convertToXML () и convertBattlePagetoXMLWithoutDOM (), когда передано «is2» вместо «is»), во всех 4 случаях выполнялись менее 50 мс.

Я прочитал предложение о том, что поток ожидает закрытия соединения перед разблокировкой, поэтому я попытался использовать HttpComponentsClient 4.0 (http://hc.apache.org/httpcomponents-client/index.html) вместо этого, но первоначальный InputStream занимал столько же времени для анализа. Например, этот код:

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

обработка заняла еще больше времени (на 50 мсек больше для сети), и время анализа потока осталось прежним. Очевидно, что он может быть создан для того, чтобы не создавать HttpClient и свойства каждый раз (быстрее сетевого времени), но это не затронет проблему потока.

Итак, мы подошли к центральной проблеме - почему первоначальный URLConnection InputStream (или HttpClient InputStream) обрабатывается так долго, в то время как любой поток такого же размера и содержимого, созданный локально, на несколько порядков быстрее? Я имею в виду, что первоначальный ответ уже находится где-то в ОЗУ, и я не вижу причин, почему он обрабатывается так медленно, по сравнению с тем, когда тот же поток создается из байта [].

Учитывая, что мне нужно проанализировать миллион записей и тысячи подобных страниц, общее время обработки почти 1,5 с / страница кажется СЛИШКОМ ПУТИ слишком длинным.

Есть идеи?

P.S. Пожалуйста, спросите, требуется ли еще какой-либо код - единственное, что я делаю после анализа, это создаю PreparedStatement и помещаю записи в JavaDB в пакетах по 1000+, и производительность в порядке ~ 200ms / 1000entries, prb может быть оптимизирован с большим количеством кеша, но Я не особо разбирался в этом.

1 Ответ

1 голос
/ 17 мая 2010

Это занимает больше времени, потому что это чтение с удаленного сервера. Ваш метод executeConnection () просто создает поток, но фактически не читает весь ответ с сервера. Это делается, как только вы начинаете читать из потока.

...