Термин «Web Scale» недавно был раскручен, и люди также делают свои системы более «широкими доменом», расширяя архитектуру приложения. Но что такое полный домен? Или как обеспечить весь домен?
Самая популярная нагрузка на масштабирование - самый популярный домен. Например, системы, которые поддерживают доступ для одного пользователя, также могут поддерживать 10, 100 или даже 1 миллион пользовательских доступа. В идеале наша система должна оставаться как можно более хранением. Даже если состояние должно существовать, оно может быть преобразовано и передано на различных терминалах обработки сети. Когда нагрузка становится узким местом, может быть никакой задержки. Таким образом, для единого запроса приемлемо взять от 50 до 100 миллисекунд. Это называется масштабирование.
Расширение работает совершенно по -разному в полной оптимизации доменов. Например, алгоритм, который обеспечивает успешно обработанную одну данных, также может успешно обработать 10, 100 или даже 1 миллион данных. Сложность события (большие символы O) является лучшим описанием независимо от того, является ли этот метрический тип возможным или нет. Задержка - это убийца масштабирования производительности. Вы постараетесь обрабатывать все операции на одной машине. Это называется масштабирование.
Если пирог может упасть с неба (конечно, это невозможно), мы можем объединить горизонтальное расширение и вертикальное расширение. Тем не менее, сегодня мы только собираемся представить следующие простые способы повышения эффективности.
Как Forkjoinpool в Java 7, так и параллельные потоки данных в Java 8 (ParallelStream) помогают для параллельной обработки. Это особенно очевидно при развертывании программ Java на многоядерных процессорах, поскольку все процессоры могут получить доступ к одной и той же памяти.
Следовательно, фундаментальная выгода от этой параллельной обработки состоит в том, чтобы практически полностью устранить задержку по сравнению с масштабированием на разных машинах в разных сетях.
Но не путайте эффекты параллельной обработки! Пожалуйста, имейте в виду следующие два момента:
Сокращение сложности алгоритма, несомненно, является наиболее эффективным способом повышения производительности. Например, для метода экземпляра HashMap () сложность события o (1) или пространственная сложность o (1) являются самыми быстрыми. Но эта ситуация часто невозможна, не говоря уже о том, чтобы легко ее достичь.
Если вы не можете уменьшить сложность алгоритма, вы также можете улучшить производительность, найдя ключевые моменты в алгоритме и улучшив методы. Предположим, у нас есть следующая диаграмма алгоритма:
Общая временная сложность этого алгоритма составляет O (N3), и если она рассчитана в отдельном порядке доступа, также можно сделать вывод, что сложность O (n x O x P). Но в любом случае, мы найдем несколько странных сценариев, когда анализируем этот код:
Без ссылки на производственные данные мы можем легко сделать выводы о том, что мы должны оптимизировать «высокопоставленные операции». Но оптимизации, которые мы сделали, не оказали никакого влияния на доставленные продукты.
Оптимизированное золотое правило - не что иное, как следующее:
Это все для теории. Предполагая, что мы обнаружили, что проблема возникает в правой ветви, вполне вероятно, что простая обработка в продукте теряет ответ из -за большого количества времени (при условии, что значения n, O и P очень велики), обратите внимание, что временная сложность левой ветви, упомянутой в статье, составляет O (N3). Сделанные здесь усилия не могут быть расширены, но могут сэкономить время для пользователей и задержать сложные улучшения производительности до позже.
Вот 10 советов по улучшению производительности Java:
1. Используйте StringBuilder
Стингбюлдер должен использоваться по умолчанию в нашем коде Java, а оператора + следует избегать. Может быть, у вас будут разные мнения о синтаксисе сахара StringBuilder, например:
Строка x = "a" + args.length + "b";
Будет составлен как:
0 new java.lang.stringbuilder [16] 3 dup4 ldc <string "a"> [18] 6 invokespecial java.lang.stringbuilder (java.lang.string) [20] 9 aload_0 [args] 10 arraylength vokevirtual java.langbuilder. java.lang.stringbuilder [23] 14 Ldc <String "B"> [27] 16 Invokevirtual Java.Lang.StringBuilder.Append (Java.Lang.String): java.lang.stringbuilder [29] 19 invokevirtual java.lang.stringbuilder.tring [32] 22 Store_1 [x]
Но что именно произошло? Вам нужно использовать следующие разделы для улучшения строки дальше?
Строка x = "a" + args.length + "b"; if (args.length == 1) x = x + args [0];
Теперь мы использовали второй StringBuilder, который не потребляет дополнительную память в куче, но оказывает давление на GC.
StringBuilder x = new StringBuilder ("a"); x.Append (args.length); x.append ("b"); if (args.length == 1); x.append (args [0]);В приведенном выше примере, если вы полагаетесь на компилятор Java, чтобы неявно генерировать экземпляр, скомпилированный эффект не имеет ничего общего с использованием того, используется ли экземпляр StringBuilder. Пожалуйста, помните: в филиале Nope время, проведенное на каждом цикле ЦП, тратится на GC или распределяя пространство по умолчанию для StringBuilder, и мы тратим время N x O X P.
Вообще говоря, использование StringBuilder лучше, чем использование оператора +. Если возможно, выберите StringBuilder, если вам нужно передать ссылки по нескольким методам, потому что строка потребляет дополнительные ресурсы. JOOQ использует этот метод при генерации сложных операторов SQL. Только один StringBuilder используется в течение всего прохождения SQL Abstract Syntax Tree.
Что еще более трагично, так это то, что если вы все еще используете StringBuffer, то используйте StringBuilder вместо StringBuffer. В конце концов, на самом деле не так много случаев, когда строки должны быть синхронизированы.
Регулярные выражения дают людям впечатление, что они быстрые и легкие. Но использование регулярных выражений в филиале NOPE было бы худшим решением. Если вам нужно использовать регулярные выражения в коде вычислительного интенсивного, по крайней мере, кэшируйте шаблон, чтобы избежать многократного составления шаблона.
Статический конечный шаблон Heavy_regex = pattern.compile ("((((x)*y)*z)*");Если используется только простое регулярное выражение, подобное следующему:
String [] parts = ipaddress.split ("//.");Лучше всего использовать обычную операцию на основе массива или индекса. Например, следующий код с плохой читаемости фактически играет ту же роль.
int length = ipaddress.length (); int offset = 0; int part = 0; for (int i = 0; i <length; i ++) {if (i == длина - 1 || ipaddress.charat (i+1) == '.Приведенный выше код также показывает, что преждевременная оптимизация бессмысленна. Несмотря на то, что по сравнению с методом Split () этот код менее поддерживается.
Задача: Могут ли умные друзья придумать более быстрые алгоритмы?
Регулярные выражения очень полезны, но они также должны заплатить цену при использовании. Особенно, когда вы находитесь в глубине нет, вам следует избегать использования регулярных выражений любой ценой. Также будьте осторожны с различными методами строки JDK, которые используют регулярные выражения, такие как string.replaceall () или string.split (). Вы можете использовать более популярную библиотеку разработки, такую как Apache Commons Lang, для выполнения строковых операций.
Это предложение относится не к общим случаям, а только к сценариям в глубине нет. Несмотря на это, вы должны что -то понять. Метод написания цикла Format Java 5 настолько удобен, что мы можем забыть метод внутреннего цикла, например:
для (строка значения: строки) {// сделать что -нибудь полезное здесь}Всякий раз, когда код вступает в этот цикл, если переменная строк является итерационной, код автоматически создаст экземпляр итератора. Если вы используете ArrayList, виртуальная машина автоматически распределяет 3 памяти целочисленного типа на объект в куче.
Частный класс ITR реализует итератор <e> {int cursor; int lastret = -1; int weddcount = modcount; // ...Вы также можете использовать следующий метод эквивалентного цикла для замены вышеупомянутого цикла, просто «тратить» кусок пластической хирургии на стеке, что довольно рентабельно.
int size = strings.size (); for (int i = 0; i <size; i ++) {строка значения: strings.get (i); // Сделай что -нибудь полезное здесь}Если значение строки в цикле не сильно меняется, вы также можете использовать массивы для реализации цикла.
для (строка значения: stringArray) {// сделать что -нибудь полезное здесь}С точки зрения простого чтения и письма, или с точки зрения дизайна API, итераторов, итерационных интерфейсов и петлей Foreach, очень полезны. Но при стоимости, когда они их используют, на куче создается дополнительный объект для каждого ребенка. Если цикл должен быть выполнен много раз, пожалуйста, будьте осторожны, чтобы не генерировать бессмысленные экземпляры. Лучше всего использовать метод базового цикла указателя для замены вышеупомянутого итератора, итерабируемого интерфейса и цикла Foreach.
Некоторые мнения, которые возражают против вышеизложенного (особенно замены итераторов с указателями) обсуждаются на Reddit для деталей.
Некоторые методы дороги. В качестве примера не упоминалось о том, что мы не упомянули соответствующие методы листьев, но это можно найти. Предположим, что нашему драйверу JDBC необходимо преодолеть все трудности, чтобы вычислять возвращаемое значение метода Resultset.wasnull (). Фреймворк SQL, которую мы реализуем сами, может выглядеть так:
if (type == integer.class) {result = (t) watnull (rs, integer.valueof (rs.getint (index)));} // а затем ... статический окончательный <t> t был null (resultset rs, t value) throws sqlexception {return rs.wasnull ()? null: value;}В приведенной выше логике метод Resultset.wasnull () называется каждый раз, когда значение int получается из набора результатов, но метод getInt () определяется как:
Возвращение типа: переменное значение; Если результат запроса SQL NULL, верните 0.
Таким образом, простой и эффективный способ улучшить его, заключается в следующем:
static final <t Extends number> t aSnull (ResultSet rs, t value) throws sqlexception {return (value == null || (value.intvalue () == 0 && rs.wasnull ()))? null: value;}Это ветерок.
Кэш -метод вызовы для замены методов высоких накладных расходов на листовых узлах или избежать вызова методов высокой накладной расходы, если позволяет соглашение о методе.
Вышеуказанное представляет использование большого количества дженериков в примере из JOOQ, что приводит к использованию классов байтовых, коротких, Int и длинных обертков. Но, по крайней мере, дженерики не должны быть ограничением кода, пока они не будут специализироваться в проектах Java 10 или Valhalla. Потому что это может быть заменено следующим методом:
// Хранится в целом кучи I = 817598;
... если ты пишешь таким образом:
// хранить в стеке int i = 817598;
Все может ухудшиться при использовании массивов:
// три объекта были сгенерированы в целочисленной куче [] i = {1337, 424242};... если ты пишешь таким образом:
// только один объект генерируется на куче int [] i = {1337, 424242};Когда мы находимся в глубине нет, мы должны стараться изо всех сил, чтобы избежать использования классов упаковки. Недостатком этого является то, что он оказывает большое давление на GC. GC будет очень занят, чтобы очистить объекты, сгенерированные классом упаковки.
Таким образом, эффективным методом оптимизации является использование основных типов данных, массивов фиксированной длины и использования ряда разделенных переменных для определения позиции объекта в массиве.
TROVE4J, который следует за протоколом LGPL, является библиотекой классов Java Collection, которая обеспечивает нам лучшую реализацию производительности, чем формирующий массив int [].
Следующее исключение для этого правила: потому что логических и байтовых типов недостаточно, чтобы позволить JDK предоставлять для него метод кеша. Мы можем написать таким образом:
Логический a1 = true; // ... Синтаксис сахар для: логический a2 = boolean.valueof (true); byte b1 = (байт) 123; // ... Синтаксис сахар для: byte b2 = byte.valueof ((byte) 123);
Другие основные типы целых чисел также имеют аналогичные ситуации, такие как Char, Short, Int и Long.
Не автоматически - эти целочисленные примитивные типы при вызове конструкторов или вызовой метод thetype.valueof ().
Также не вызовите конструкторы на классах обертки, если вы не хотите получить экземпляр, который не создается в куче. Преимущество этого заключается в том, чтобы дать вам огромную апрельскую шутку для ваших коллег.
Конечно, если вы хотите испытать библиотеку функций вне HEAP, хотя это может быть смешано с большим количеством стратегических решений, а не с наиболее оптимистичным локальным решением. Для интересной статьи о не HEAP Storage, написанном Питером Лоури и Беном Коттоном, нажмите: OpenJDK и Hashmap-пусть ветераны безопасно освоит (не хранение без HEAP!) Новые методы.
В настоящее время функциональные языки программирования, такие как Scala, поощряют рекурсию. Потому что рекурсия обычно означает рекурсуцию хвоста, которая может быть разложена на индивидуально оптимизированные люди. Было бы лучше, если бы язык программирования, который вы используете, сможет поддержать его. Но даже в этом случае будьте осторожны, чтобы небольшая корректировка алгоритма превратила хвостовую рекурсию в обычную рекурсию.
Надеемся, что компилятор сможет обнаружить это автоматически, иначе мы бы потратили впустую много кадров стека на вещи, которые можно сделать с помощью нескольких локальных переменных.
В этом разделе нечего сказать, за исключением того, что итерация как можно больше в ветви нет вместо рекурсии.
Когда мы хотим итерации по карте, сохраненной в парах ключей, мы должны найти вескую причину для следующего кода:
for (k key: map.keyset ()) {v value: map.get (key);}Не говоря уже о следующем методе написания:
for (entry <k, v> intry: map.enterSet ()) {k key = intry.getKey (); v value = intry.getValue ();}Когда мы используем Nope Branch, мы должны использовать карту с осторожностью. Потому что многие операции доступа, которые, по -видимому, имеют временную сложность O (1), фактически состоят из ряда операций. И сам доступ не бесплатен. По крайней мере, если вам нужно использовать карту, вам необходимо использовать метод intrintSet () для итерации! Таким образом, мы хотим только получить доступ к экземпляру Map.Entry.
Когда необходимо итерацию над картой в форме пар клавишных значений, обязательно используйте метод intrySet ().
8. Используйте перечисление или Enummap
В некоторых случаях, например, при использовании карты конфигурации мы можем заранее знать значение ключа, сохраненное на карте. Если это значение ключа очень мало, мы должны рассмотреть возможность использования перечисления или enummap вместо использования хэшса или хэшмапа, который мы обычно используем. Следующий код дает четкое объяснение:
Частный переходной объект [] vals; public v put (k key, v value) {// ... int index = key.ordinal (); vals [index] = masknull (value); // ...}Ключевая реализация предыдущего кода заключается в том, что мы используем массивы вместо хэш -таблиц. Особенно при введении новых значений в карту все, что вам нужно сделать, это получить постоянный номер последовательности, сгенерированный компилятором для каждого типа перечисления. Если есть глобальная конфигурация карты (например, только один экземпляр), Enummap достигнет более выдающейся производительности, чем HashMap под давлением увеличения скорости доступа. Причина в том, что Enummap использует на несколько битов меньше памяти кучи, чем Hashmap, и HashMap вызывает метод HashCode () и метод Equals () на каждом значении ключа.
Enum и enummap - близкие друзья. Когда мы используем значения ключей, аналогичные Enum-подобным структурам, мы должны рассмотреть возможность объявления этих значений ключей в качестве типов перечисления и использования их в качестве клавиш Enummap.
В случае, если EnummAP не может быть использован, по крайней мере, методы HashCode () и Equals () должны быть оптимизированы. Хороший метод HashCode () необходим, потому что он предотвращает ненужные вызовы методу высокопоставленного ().
В структуре наследования каждого класса необходимы простые объекты, которые легко приемлемы. Давайте посмотрим, как реализован Jooq Org.jooq.table?
Самый простой и самый быстрый метод реализации HashCode () заключается в следующем:
// Abstracttable Основная реализация общей таблицы: @OverridePublic int hashcode () {// [#1938] по сравнению со стандартными QueryParts, это более эффективная реализация HashCode (). Return name.hashcode ();}Имя - имя таблицы. Нам даже не нужно рассматривать схему или другие атрибуты таблицы, потому что имена таблиц обычно уникальны в базе данных. И имя переменной - это строка, которая сама уже кэшировала значение hashcode ().
Комментарии в этом коде очень важны, потому что Abstracttable унаследован от AbstractQuerypart является основной реализацией любого абстрактного синтаксического дерева. Обычные абстрактные синтаксические элементы дерева не имеют никаких атрибутов, поэтому у них не может быть никакой фантазии об оптимизации реализации метода HashCode (). Метод перезапись HashCode () выглядит следующим образом:
// AbstractQuerypart Общая абстрактная базовая реализация дерева синтаксиса: @OverridePublic int hashcode () {// Это рабочая реализация по умолчанию. // Конкретный подкласс реализации должен переопределить этот метод для повышения производительности. return create (). renderinlined (this) .hashcode ();}Другими словами, весь рабочий процесс рендеринга SQL запускается для расчета хэш -кода для нормального абстрактного синтаксического дерева.
Метод equals () еще более интересен:
// Основная реализация AbstractTable General Table: @OverridePublic Boolean Equals (Object that) {if (this == that) {return true;} // [#2144], прежде чем вызовать метод высокопоставленного Abstractquerypart.equals (), // вы можете рано узнать, не являются ли объекты равными. if (этот экземпляр AbstractTable) {if (stringUtils.equals (name, (((AbstractBite <?>) that) .name)))) {return super.equals (that);} return false;} вернуть false;}Во -первых, не используйте метод equals () слишком рано (не только в нет), если:
Примечание. Если мы используем экземпляр слишком рано, чтобы проверять совместимые типы, следующие условия фактически включают аргумент == null. Я уже объяснил это в моем предыдущем блоге, пожалуйста, обратитесь к 10 изысканным лучшим практикам кодирования Java.
После того, как наше сравнение вышеупомянутых ситуаций закончилось, мы должны быть в состоянии сделать некоторые выводы. Например, метод jooq table.equals () используется для сравнения, являются ли две таблицы одинаковыми. Независимо от конкретного типа реализации, они должны иметь одинаковое имя поля. Например, следующие два элемента не могут быть одинаковыми:
Если мы сможем легко определить, равен ли входящий параметр самому экземпляру (это), мы можем отказаться от операции, если результат является ложным. Если результат возврата верен, мы можем дополнительно судить супер реализацию родительского класса. В случае, когда большинство сравниваемых объектов не равны, мы можем закончить метод как можно раньше, чтобы сохранить время выполнения ЦП.
Некоторые объекты имеют более высокое сходство, чем другие.
В JOOQ большинство экземпляров таблиц генерируются генератором кода JOOQ, и метод Equals () этих экземпляров был глубоко оптимизирован. Десятки других типов таблиц (полученные таблицы), табличные функции, массивные таблицы, соединенные таблицы, поворотные таблицы, общие выражения таблицы и т. Д. Поддерживают базовую реализацию метода Equals ().
Наконец, есть другая ситуация, которая может быть применена ко всем языкам, а не только на Java. Кроме того, NOPE PRENCAL, которую мы ранее изучали, также будет полезна для понимания от O (N3) до O (N LOG N).
К сожалению, многие программисты используют простые локальные алгоритмы для рассмотрения проблемы. Они привыкли к решению проблем шаг за шагом. Это «да/или» форма императива. Этот стиль программирования легко смоделировать «большую картину» при преобразовании из чистого императивного программирования в объектно-ориентированное программирование в функциональное программирование, но в этих стилях не хватает того, что существует только в SQL и R:
Декларативное программирование.
В SQL мы можем объявить эффект, необходимый для базы данных, не рассматривая влияние алгоритма. База данных может принять лучший алгоритм в соответствии с типом данных, такими как ограничения, ключи, индексы и т. Д.
Теоретически, у нас сначала были основные идеи после SQL и реляционных расчетов. На практике поставщики SQL в течение последних нескольких десятилетий внедрили эффективные оптимизаторы накладных расходов (оптимизаторы, основанные на затратах). Затем в версии 2010 года мы наконец обнаружили весь потенциал SQL.
Но нам не нужно реализовать SQL, используя метод SET. Все языки и библиотеки поддерживают наборы, коллекции, сумки и списки. Основным преимуществом использования набора является то, что он может сделать наш код кратким и ясным. Например, следующий метод написания:
SomeSet пересекает некоторые пари
Вместо
// Предыдущий метод написания Java 8 SET RESUAL = NEW HASHSET (); Для (объект -кандидат: someSet) if (womeotherset.contains (кандидат)) result.add (кандидат); // Даже если Java 8 используется, это не очень полезно.someset.stream (). Filter (neamotherset :: Содержит) .collect (collectors.toset ());
У некоторых людей могут быть разные мнения о функциональном программировании и Java 8, которые могут помочь нам написать более простые и более простые алгоритмы. Но эта точка зрения не обязательно верна. Мы можем преобразовать императивную петлю Java 7 в коллекцию потоков Java 8, но мы все еще используем тот же алгоритм. Но выражения в стиле SQL разные:
SomeSet пересекает некоторые париПриведенный выше код может иметь 1000 различных реализаций на разных двигателях. Мы изучаем сегодня, так это автоматически преобразовать два набора в перечисление перед вызовом операции пересечения. Даже мы можем выполнять параллельные операции пересечения, не вызывая метод базового потока. Pparallel ().
В этой статье мы обсуждаем оптимизации о ветвлении. Например, глубоко в алгоритмы высокой комплексности. Как разработчики JOOQ, мы рады оптимизировать поколение SQL.
JOOQ находится на «нижней части пищевой цепи», потому что это последний API, вызванный нашей компьютерной программой при выходе из JVM и въезда в СУБД. Расположенный в нижней части пищевой цепи означает, что любая линия занимает время n x x p, когда она выполняется в JOOQ, поэтому я хочу оптимизировать ее как можно скорее.
Наша бизнес -логика может быть не такой сложной, как нет. Однако базовая структура может быть очень сложной (локальная структура SQL, локальные библиотеки и т. Д.). Поэтому мы должны следовать указанным сегодня принципам, использовать управление миссией Java или другие инструменты, чтобы проверить, есть ли какая -либо область, которая нуждается в оптимизации.
Оригинальная ссылка: jaxenter Перевод: ImportNew.com - Всегда на дороге Перевод Ссылка: http://www.importnew.com/16181.html