Мы сделали все правильно, теперь пришло время делать вещи быстрее. Мы должны помнить предупреждение Дональда Кнута : «Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация — корень всего зла».
По словам Джонатана Хедли, он использует YourKit Java Profiler для измерения использования памяти и определения горячей точки производительности. Использование статистических результатов такого рода инструментов имеет решающее значение для успеха оптимизаций, так как вы не будете тратить время на то, чтобы просто задуматься и сделать бесполезные настройки, которые не улучшат производительность, а также сделают ваш код излишне сложным и сложным в обслуживании. Джонатан также говорил об этом в «Колофоне» .
Мы перечислим некоторые советы и приемы, используемые в Jsoup, они случайным образом упорядочены в настоящее время, будут реорганизованы в будущем.

1. Заполнение для отступа
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
// memoised padding up to 21, from "", " ", " " to " "static final String[] padding = {......};public static String padding(int width) { if (width < 0) throw new IllegalArgumentException("width must be > 0"); if (width < padding.length) return padding[width]; char[] out = new char[width]; for (int i = 0; i < width; i++) out[i] = ' '; return String.valueOf(out);}protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException { accum.append('\n').append(StringUtil.padding(depth * out.indentAmount()));} |
Довольно умный, верно? Он поддерживает кэш различной длины, который покрывал бы 80% случаев — что, я полагаю, будет основано на опыте и статистике автора.
2. Есть класс или нет?
Element#hasClass был помечен как чувствительный к производительности , например, мы хотим проверить, есть ли у <div class="logged-in env-production intent-mouse"> production классов, разделить класс по пробелам на массив, после чего цикл и поиск будут работать , но в глубоком обходе это было бы неэффективно. Jsoup сначала вводит здесь « Ранний выход» , сравнивая длину с именем целевого класса, чтобы избежать ненужного сканирования и поиска, что также будет полезно. Затем он использует указатель для обнаружения пробелов и выполняет regionMatches — честно говоря, я впервые узнал метод String#regionMatches & # 55907; & # 56357; & # 56837; & # 56837;
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
public boolean hasClass(String className) { final String classAttr = attributes().getIgnoreCase("class"); final int len = classAttr.length(); final int wantLen = className.length(); if (len == 0 || len < wantLen) { return false; } // if both lengths are equal, only need compare the className with the attribute if (len == wantLen) { return className.equalsIgnoreCase(classAttr); } // otherwise, scan for whitespace and compare regions (with no string or arraylist allocations) boolean inClass = false; int start = 0; for (int i = 0; i < len; i++) { if (Character.isWhitespace(classAttr.charAt(i))) { if (inClass) { // white space ends a class name, compare it with the requested one, ignore case if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) { return true; } inClass = false; } } else { if (!inClass) { // we're in a class name : keep the start of the substring inClass = true; start = i; } } } // check the last entry if (inClass && len - start == wantLen) { return classAttr.regionMatches(true, start, className, 0, wantLen); } return false;} |
3. Имя тега в или нет?
Как мы анализировали в предыдущих статьях, HtmlTreeBuilderState проверит правильность гнезд, проверив, находится ли имя тега в определенной коллекции или нет. Мы можем сравнить реализацию до и после 1.7.3, чтобы проверить.
|
01
02
03
04
05
06
07
08
09
10
11
|
// 1.7.2} else if (StringUtil.in(name, "base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title")) { return tb.process(t, InHead);}// 1.7.3static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"};...} else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { return tb.process(t, InHead);} |
Согласно комментарию, написанному автором: «Здесь немного труднее читать, но он вызывает меньше GC, чем динамические переменные. Вносит около 10% разбора загрузки GC. Необходимо убедиться, что они отсортированы, как используется в findSorted ». Просто используя static final константный массив, также сделайте их сортированными так, чтобы двоичный поиск также улучшился с O (n) до O (log (n)), соотношение цены и производительности здесь довольно хорошее.
Однако «НЕОБХОДИМО обновить HtmlTreebuilderStateTest, если добавлено больше массивов», — это не хороший способ синхронизации IMHO, вместо того, чтобы копировать и вставлять, я бы использовал отражение для извлечения этих констант. Вы можете найти мое предложение в запросе на извлечение № 1157: «Упростите модульный тест состояния сортировки состояний — избегайте дублирования кода в HtmlTreeBuilderStateTest.java» .
4. Легкая модель
Вы знаете хитрость Integer.valueOf(i) ? Он поддерживает кэш IntegerCache от -128 до 127 или выше, если настроен ( java.lang.Integer.IntegerCache.high ), в результате == и equals результат будет другим, если значение находится в другом диапазоне (классический Вопрос интервью на Java?). Это пример Flyweight Pattern на самом деле. Что касается Jsoup, применение этого шаблона также сократит время создания объектов и увеличит производительность.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
/** * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks. * <p /> * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list. * That saves both having to create objects as hash keys, and running through the entry list, at the expense of * some more duplicates. */private static String cacheString(final char[] charBuf, final String[] stringCache, final int start, final int count) { // limit (no cache): if (count > maxStringCacheLen) return new String(charBuf, start, count); if (count < 1) return ""; // calculate hash: int hash = 0; int offset = start; for (int i = 0; i < count; i++) { hash = 31 * hash + charBuf[offset++]; } // get from cache final int index = hash & stringCache.length - 1; String cached = stringCache[index]; if (cached == null) { // miss, add cached = new String(charBuf, start, count); stringCache[index] = cached; } else { // hashcode hit, check equality if (rangeEquals(charBuf, start, count, cached)) { // hit return cached; } else { // hashcode conflict cached = new String(charBuf, start, count); stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again } } return cached;} |
Существует также другой сценарий минимизации новых GC StringBuilder, использующих ту же идею.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private static final Stack<StringBuilder> builders = new Stack<>();/** * Maintains cached StringBuilders in a flyweight pattern, to minimize new StringBuilder GCs. The StringBuilder is * prevented from growing too large. * <p> * Care must be taken to release the builder once its work has been completed, with {@see #releaseBuilder}*/public static StringBuilder borrowBuilder() { synchronized (builders) { return builders.empty() ? new StringBuilder(MaxCachedBuilderSize) : builders.pop(); }} |
На самом деле CharacterReader и StringUtil достойны того, чтобы переваривать их все больше и больше, поскольку есть много полезных советов и приемов, которые вас вдохновят.
5. Другие методы улучшения
- Используйте RandomAccessFile для чтения файлов, которые улучшили время чтения файлов в 2 раза. Проверьте # 248 для более подробной информации
- Рефакторинг иерархии узлов. Проверьте # 911 для более подробной информации
- «Улучшения в значительной степени благодаря переупорядочению методов HtmlTreeBuilder на основе анализа различных веб-сайтов» — я перечисляю этот здесь, потому что это очень практично. Более глубокое понимание и наблюдение за тем, как будет выполняться код, также даст вам некоторое представление
- Вызовите
list.toArray(0)а неlist.toArray(list.size())— это использовалось в некоторых проектах с открытым исходным кодом, таких как h2database , поэтому я также предложил это в другом запросе Pull # 1158
6. Неизвестные
Оптимизация никогда не заканчивается. Есть еще много советов и приемов, которые я не обнаружил в это время. Я был бы признателен, если бы вы могли поделиться ими со мной, если вы найдете больше вдохновляющих идей в Jsoup. Вы можете найти мою контактную информацию на левой боковой панели этого веб-сайта или просто отправить электронное письмо по ny83427 at gmail.com .
-Продолжение следует-
|
Опубликовано на Java Code Geeks с разрешения Натанаэля Янга, партнера нашей программы JCG . См. Оригинальную статью здесь: « За кулисами» Секреты Jsoup V: Советы и хитрости по оптимизации Мнения, высказанные участниками Java Code Geeks, являются их собственными. |