Когда реальность намного больше, чем то, что ты видишь

Медленный код замедляет вас? Вы когда-нибудь задумывались, что приводит к медленному коду? Что ж, мы все время от времени сталкиваемся с этой ситуацией, и я попытался выяснить, что может замедлять работу. Вы можете столкнуться с тем, что не совсем понимаете, как это работает внутри ядра. Это одна из причин, почему вы не можете выяснить, почему он работает медленно. Думая о медленном коде, вы забываете, что медленный код — это код для выполнения дополнительной работы. Дополнительная работа над вашим решением — один из фундаментальных принципов оптимизации производительности. Следовательно, глубокое изучение корней вашей архитектуры поможет вам решить проблемы с производительностью и сделает вас лучшим техническим экспертом, который знает, как писать стабильный и быстрый код.

Алгоритмическое мышление — это способ найти решение путем точного определения необходимых шагов — ничего не происходит по волшебству.

Давайте представим, что код, который вы написали, — это верхушка айсберга, а фреймворки, библиотеки, методы SaaS API и другие компоненты, которые вы используете, — это его основная часть. Легко быстро проверить, не ошиблись ли вы, и попытаться улучшить свое решение, но если вы посмотрите на айсберг под водой, то легко понять, что этих шагов недостаточно, чтобы ваше приложение работало быстро и работало должным образом.

Эти вопросы тоже всплывают в вашей голове?

  • Не сложно ли найти проблему, если это не одна строчка кода?
  • Что, если суть проблемы заключается в двух строках кода?
  • Что, если эти две строки кода находятся в двух разных функциях/файлах/проектах?
  • Не сложно ли найти такие проблемы и что бы вы сделали с таким бардаком?

Оптимизация

Оптимизация — это непрерывный процесс. Напишите код, опубликуйте код, и сейчас самое время оглянуться назад и выявить скрытые недостатки. В данном случае перепроектирование — это проблема, которая может испортить любое приложение. Когда мы смотрим на наши собственные результаты, мы часто выявляем области, которые мы можем не только улучшить, но и применить на практике. Я всегда стараюсь следовать лучшим практикам и оставлять резервное время для работы над производительностью приложений на последних итерациях цикла разработки — тот же подход, который я бы рекомендовал вам.

Когда мы достигнем этапа, когда нам нужно найти раздел, связанный с проблемами производительности, мы должны сосредоточиться на следующих шагах:

  1. Найдите, где он медленный, он же обнаруживает узкое место
  2. Поймите, почему это медленно, или почему это узкое место
  3. Подумайте, что с этим делать, ака оптимизировать

Обнаружить

Первым шагом в оптимизации является использование профилирования, инструментов разработки Chrome, консольного времени или, например, в Salesforce Commerce Cloud. Вы также можете использовать Code Profiler, Pipeline Profiler и Quota Statuss для поиска конфиденциальных разделов.

Понять и рассмотреть

Второй шаг — понять, где и что именно тормозит. На последнем шаге потребуются некоторые базовые знания, чтобы устранить обнаруженную загвоздку и применить решение для сломанных участков. Эти базовые знания не являются глубокими; это простой алгоритмический способ мышления, который может ускорить и облегчить нашу жизнь.

Используя алгоритмическую сложность, мы можем систематизировать различные структуры кода, обеспечить соответствующую обратную связь и быстро определить, что неправильно, а что хорошо. Это также позволяет нам увидеть, как производительность меняется с увеличением объема данных, с которыми мы работаем; так как код работает быстро, когда у вас есть десять заказов, но очень медленно, когда у вас есть 1 000 000. Это позволяет нам понять, что нам следует искать в первую очередь, и действия, которые мы должны предпринять, чтобы решить эту проблему.

O(1) — постоянная операция, то есть происходит мгновенно: может быть медленной из-за объема данных, но в то же время хорошо заметной и быстрой по сравнению с другими.

array[0] + array[1];

O(n) — линейный, в целом хитрый: неплох, но не так хорош, как O(1), просто требует внимания, чтобы убедиться, что он принимает простой цикл.

for (const item of array) {
    sum += item;
}

O (n2) — просто плохой и медленный: Цикл в цикле, который в большинстве случаев можно переделать другим способом.

for (let i = 0 , n = array.length; i < n; i++) {
    for (let j = i + 1; j < n; j++) {
        sum += array[i] + array[j];
    }
}

Другой пример выглядит как линейный, поскольку у нас есть один цикл с indexOf, сделанным из O (n2). Поскольку indexOf, как правило, просматривает массив, пока не найдет нужный элемент, то есть зацикливание. Петля в петле в конце.

function foo(array) {
    for (const item of array) {
        array[array.indexOf(item)] = item + 20;
    }
}

Еще один пример: у вас есть логика в двух разных функциях, и вы представляете, что эти функции не находятся в одном и том же файле. Это может быть даже результатом фальшивого рефакторинга, когда мы думаем, что разделили логику и делаем правильные вещи.

function myFoo(array) {
    for (const item of array) {
        myBar(array, item);
    }
}
function myBar(array, item) {
    array[array.indexOf(item)] = item + 30;
}

O(n2) — довольно распространенная и популярная проблема, которую мы можем случайно сделать без предупреждения. Случайно квадратичный — результат такой популярности, и на эту тему есть целый блог.

O(n3) — это просто…

for (let i = 0; n = array.length; i < n; i++) {
    for (let j = i; j < n; j++) {
        for(let k = j + 1; k < n; k++) {
            sum += array[i] + array[j];
        }
    }
}

Такой код всегда медленный, несмотря ни на что, и его медлительность увеличивается экспоненциально. Кроме того, в такой логике нет необходимости, а шансы там, где это действительно необходимо, довольно малы, поэтому их можно переделать.

  1. O(1) — хорошо —сохранить
  2. O(n) — неплохо —проверьте
  3. O(n2) — плохо —рефакторинг
  4. O(n3) — очень плохо —удалить и начать с нуля

Функции Javascript O(n)

Этот список должен помочь вам во время ваших расследований и исследований узких мест, связанных с проблемами производительности. Каждая из этих функций является функцией O(n); следовательно, если он у вас есть в цикле, вы уже заблокировали код, становящийся O (n2).

array.indexOf
array.lastIndexOf
array.splice
array.includes
array.reverse
array.every
array.find
array.some
array.findIndex
array.reduce
array.reduceRight
array.some
Object.values
Object.keys

Вышеупомянутая классификация зависит от количества операций, которые должен выполнить движок, в то время как у нас есть другой тип классификации, который зависит от памяти, которую система должна зарезервировать.

Проблемы с производительностью памяти

Неправильное выделение памяти для операции — обычная ситуация в словесном программировании. Мы часто не думаем о выделении памяти для наших операций, что может привести к выделению слишком большого объема памяти и в конечном итоге к проблемам с производительностью. Следовательно, вы всегда должны сначала думать каждый раз, когда видите, что для какого-то процесса выделяется много памяти, и вы всегда должны спрашивать себя — действительно ли мне нужна такая память?

array.slice
array.concat
array.map
array.filter
str.split

Описанные выше методы используются для выделения памяти в нашем движке. Когда мы анализируем производительность, на первый взгляд не видно, что такие операции могут повлиять на производительность. Но в то же время, если мы сможем взглянуть на сборщик мусора, мы можем признать, что он может принимать на себя до 70% влияния на производительность.

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

ab.slice().concat(bc).map(myFoo).filter(myBar).reduce(qwerty,0);

Что каждый «функциональный тип» выше в основном делает со структурирующим кодом:

  1. выделить память для операции slice
  2. очистить память операции slice
  3. выделить память для операции concat
  4. очистить память операции concat
  5. выделить память для операции map
  6. очистить память операции map
  7. выделить память для операции filter
  8. очистить память операции filter

Четыре выделения и освобождения одновременно

Может быть, вы думаете, что это все еще быстро и не влияет на производительность, но если вы добавите приведенный выше код в какой-нибудь цикл, он сразу же всплывет.

О (журнал п)

Один из самых быстрых типов операций — O(log n). Если, например, у нас есть 1 000 000 объектов, то O(log n) будет равно 20, что гораздо ближе к 1, чем к 1 000 000.

Самый классический пример O(log n) — бинарный поиск.

Следовательно, если мы говорим об алгоритмической оптимизации, мы всегда должны заниматься рефакторингом нашего O(n) до O(log n) и т. д.

Ключевое различие между теорией и практикой заключается в том, как применить это на практике. Классический пример — операция поиска в массиве. Каждый раз, когда мы выполняем поиск в массиве, он выполняется медленно. Первый шаг, который мы должны сделать, это сделать классическую алгоритмическую оптимизацию, — взять данные и реструктурировать их (поместить их в какой-то объект данных, отсортировать их или поместить в другой формат). Основная идея заключается в том, чтобы поместить данные в такой формат, при котором следующие операции позволят нам производить мгновенную обработку данных. Идея состоит в том, чтобы заработать больше в начале, чтобы заработать меньше в конце нескольких действий. В то время как структура данных — это просто способ преобразования данных таким образом, чтобы было легко искать или изменять эту структуру данных.

hash map 
hash set
binary search tree
priority heap
linked list
interval tree
grid
r-tree
quadtree
kd-tree
// p.s. we don't need to learn it, just try to use one of them and see if it’s useful or not, this is the best way to learn something

Вывод

  • Подумайте, как это работает 🤔
  • Не бойтесь глубоко погружаться в чей-то код 🧗
  • Не бойтесь изобретать велосипед 🚴
  • Проверьте открытый исходный код ❤️
  • Всегда упрощайте 🍀
  • Практика оптимизации 🔄

Вдохновение было взято из оригинальной презентации: