Найти в Дзене

Что делает код медленным, основные ошибки и моменты

Оглавление

Народ, всем привет. Производительность не всегда является главным критерием качества кода, но медленный код может стоить дорого, и в деньгах, и во времени, даже в ресурсах и пользовательском терпении. Зачастую даже простая программа может «тормозить» из-за неочевидных ошибок, плохих алгоритмов или неудачного подхода к решению задачи. А давайте сегодня разберём, что именно делает код медленным, и приведём реальные примеры, которые помогут это понять. конечно, мы остановимся на каких-то общих проблемах, с которыми сталкиваются начинающие программисты.

1. Плохая алгоритмическая сложность

Скажем, алгоритм то может работать правильно, но крайне неэффективно на больших объёмах данных. Самый классический пример — сортировка, о чем мы, кстати, недавно обсуждали в отдельной статье.

Пузырьковая сортировка — O(n^2)
def bubble_sort(arr):
for i in range(len(arr)):
for j in range(len(arr)-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]

На массиве из 10 элементов всё будет отлично. А вот на 10 000 — уже беда. Быстрая сортировка или встроенная функция sort() справится в разы быстрее. Поэтому это стоит учитывать, и заменять алгоритмы с O(n^2) на O(n log n), где это возможно.

-2

2. Избыточные циклы и повторные вычисления

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

def count_common(a, b):
count = 0
for item in a:
if item in b: # O(n) операция внутри цикла
count += 1
return count

Если b — большой список, проверка item in b будет выполняться очень долго. Лучше преобразовать b в множество:

def count_common(a, b):
b_set = set(b) # O(n), но один раз
return sum(1 for item in a if item in b_set)

3. Создание лишних объектов

Например, постоянное создание новых переменных и объектов в цикле.

result = []
for i in range(100000):
temp = [i] * 10 # создаётся список каждый раз
result.append(temp)

Если список temp повторяется или может быть заранее предопределён, лучше его не пересоздавать.

-3
Хотите знать больше? Читайте нас в нашем Telegram – там еще больше интересного: новинки гаджетов, технологии, AI, фишки программистов, примеры дизайна и маркетинга.

4. Работа с медленными структурами данных

Да, выбор неправильной структуры данных тоже может сыграть свою ключевую роль. Например, использование списка для хранения и поиска уникальных значений. Операция val not in unique — это O(n), а скажем использованием set даст O(1):

unique = []
for val in values:
if val not in unique:
unique.append(val)
но лучше:
unique = set()
for val in values:
unique.add(val)

5. Частые обращения к диску или сети

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

for line_id in range(1000):
with open('data.txt') as f:
lines = f.readlines()
line = lines[line_id]
Лучше один раз считать файл:
with open('data.txt') as f:
lines = f.readlines()
for line_id in range(1000):
line = lines[line_id]
-4

6. Нерациональное использование памяти

Конечно, это особенно критично в языках вроде C++ или JavaScript в браузере, когда ваша прграмма «позволяет» хранение избыточных данных или утечки памяти.

let bigArray = [];
function loadData() {
for (let i = 0; i < 1000000; i++) {
bigArray.push({ index: i, value: i * 2 });
}
}

Если bigArray не очищается, оно занимает всё больше памяти, что приводит к замедлению и утечкам. Самое простое решение, это использовать слабые ссылки, сборку мусора, или вручную управлять памятью (в низкоуровневых языках).

7. Отсутствие профилирования

Когда разработчик пытается оптимизировать свой код «вслепую», это просто трата времени, которое вообще не влияет на скорость. Настоящие «узкие места» можно найти только через профилирование (benchmarking, time profiling, memory profiling).

import cProfile
def my_func():
# функция с подозрением на медленную работу
...
cProfile.run('my_func()')

8. Слишком «умный» код

И да, избыточно обобщённый, гибкий или «магический» (сразу вспенивается мем про «magic») код тоже может быть очень медленным. скажем, вот пример, когда умно, но медленно…

def dynamic_dispatch(obj, method_name):
return getattr(obj, method_name)()

Выглядит круто, но getattr — дорогая операция по сравнению с обычным вызовом метода. Используй такие приёмы только при необходимости.

-5

9. Использование неэффективных библиотек

Некоторые сторонние библиотеки реализованы неоптимально или не используют нативные ускорения (например, C-расширения). В том же Python медленнее работать с «чистыми» циклами, чем использовать numpy:

Медленно:

result = [x ** 2 for x in range(1000000)]

Быстро:

import numpy as np
arr = np.arange(1000000)
result = arr ** 2

10. Неправильная асинхронность

Ну и последнее, больше для веб-разработки или при работе с API, часто можно ускорить работу с помощью асинхронного подхода.

Медленно: последовательные запросы

for url in urls:
data = requests.get(url).json()
python
Copy
Edit

Быстро: параллельные запросы

import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as resp:
return await resp.json()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
-6

Кстати, у нас есть и другой канал, FIT FOR FUN, про фитнес, бодибилдинг, правильное питание, похудение и ЗОЖ в целом. Кому интересно, ждем вас в гости!