Хорошо, потратив два дня, пытаясь разобраться в проблеме, и прочитав о 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 может быть оптимизирован с большим количеством кеша, но Я не особо разбирался в этом.