← Назад к вопросам

Как оптимизируешь медленную работу метода

2.0 Middle🔥 121 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как оптимизируешь медленную работу метода

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

Шаг 1: Профилирование и измерение

Сначала найду узкие места с помощью инструментов.

JMH (Java Microbenchmark Harness)

public class MethodBenchmark {
    private List<Integer> numbers;
    
    @Setup
    public void setUp() {
        numbers = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            numbers.add(i);
        }
    }
    
    @Benchmark
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public long slowMethod() {
        long sum = 0;
        for (int num : numbers) {
            if (isPrime(num)) {
                sum += num;
            }
        }
        return sum;
    }
    
    private boolean isPrime(int n) {
        for (int i = 2; i < n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }
}

Позволит увидеть реальное время выполнения и GC пауз.

JProfiler / YourKit

  • CPU профилирование - кто сжирает CPU
  • Memory профилирование - утечки памяти
  • Call tree - видеть цепочку вызовов
  • Hot spots - самые медленные методы

Шаг 2: Анализ узких мест

Сложный алгоритм

Проблема: O(n²) или хуже

// ❌ Медленно: O(n²)
public boolean containsDuplicate(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        for (int j = i + 1; j < nums.length; j++) {
            if (nums[i] == nums[j]) return true;
        }
    }
    return false;
}

// ✅ Быстро: O(n)
public boolean containsDuplicate(int[] nums) {
    Set<Integer> seen = new HashSet<>();
    for (int num : nums) {
        if (!seen.add(num)) return true;
    }
    return false;
}

Излишние вычисления

// ❌ Пересчитываем length каждый раз
for (int i = 0; i < list.size(); i++) {
    process(list.get(i));
}

// ✅ Кэшируем размер
int size = list.size();
for (int i = 0; i < size; i++) {
    process(list.get(i));
}

Работа с коллекциями

// ❌ ArrayList с частыми удалениями - O(n)
List<String> list = new ArrayList<>();
for (String item : items) {
    if (shouldRemove(item)) {
        list.remove(item);  // O(n)!
    }
}

// ✅ Собираем в новый список - O(n)
List<String> filtered = new ArrayList<>();
for (String item : items) {
    if (!shouldRemove(item)) {
        filtered.add(item);
    }
}

Шаг 3: Практические техники оптимизации

1. Кэширование результатов

@Service
public class ExpensiveComputationService {
    
    private final Cache<String, Result> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .maximumSize(1000)
        .build();
    
    public Result computeSlowMethod(String input) throws Exception {
        return cache.get(input, () -> actualComputation(input));
    }
    
    private Result actualComputation(String input) {
        // Дорогое вычисление
        Thread.sleep(5000);
        return new Result(input);
    }
}

2. Memoization для рекурсивных функций

// ❌ Медленно: O(2^n)
public long fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// ✅ С memoization: O(n)
public long fibonacci(int n, Map<Integer, Long> memo) {
    if (memo.containsKey(n)) {
        return memo.get(n);
    }
    long result = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
    memo.put(n, result);
    return result;
}

3. Параллелизм (Streams, Fork/Join)

// ❌ Последовательно
List<Integer> numbers = range(1, 1000000).boxed().collect(toList());
long sum = 0;
for (Integer num : numbers) {
    sum += expensiveComputation(num);
}

// ✅ Параллельно
long sum = IntStream.range(1, 1000000)
    .parallel()
    .mapToLong(this::expensiveComputation)
    .sum();

Но осторожно - parallelStream не всегда быстрее:

// Параллелизм имеет смысл только для больших данных
if (items.size() > 10000) {
    return items.parallelStream()...;
} else {
    return items.stream()...;
}

4. Оптимизация работы с БД

// ❌ N+1 проблема
List<User> users = userRepository.findAll();
for (User user : users) {
    List<Order> orders = orderRepository.findByUserId(user.getId());
    // 1 + N запросов
}

// ✅ Eager loading
List<User> users = userRepository.findAllWithOrders();

// SQL:
// SELECT u.*, o.* FROM users u
// LEFT JOIN orders o ON u.id = o.user_id

// В JPA:
@Entity
public class User {
    @OneToMany(fetch = FetchType.EAGER)
    private List<Order> orders;
}

5. Batch обработка

// ❌ Медленно: 1000 отдельных запросов
for (Item item : items) {
    itemRepository.save(item);
}

// ✅ Batch: 1 запрос
itemRepository.saveAll(items);

// Или batch в SQL:
String sql = "INSERT INTO items (name, price) VALUES (?, ?), (?, ?), ...";
// Batch size обычно 10-50

6. Индексы в БД

-- ❌ Медленно: full table scan
SELECT * FROM users WHERE email = 'test@example.com';

-- ✅ Быстро: с индексом
CREATE INDEX idx_users_email ON users(email);

7. Объединение циклов

// ❌ Три прохода
List<String> upper = items.stream().map(String::toUpperCase).collect(toList());
List<String> filtered = upper.stream().filter(s -> s.length() > 5).collect(toList());
long count = filtered.stream().count();

// ✅ Один проход
long count = items.stream()
    .map(String::toUpperCase)
    .filter(s -> s.length() > 5)
    .count();

8. Уменьшение объем памяти

// ❌ Загружаем всё в память
List<String> allLines = Files.readAllLines(file);
for (String line : allLines) {
    process(line);
}

// ✅ Потоковая обработка
try (Stream<String> lines = Files.lines(file)) {
    lines.forEach(this::process);
}

Шаг 4: Специфические оптимизации для Java

String операции

// ❌ Создаёт 1000 String объектов
String result = "";
for (String part : parts) {
    result += part;  // new String каждый раз!
}

// ✅ StringBuilder
StringBuilder sb = new StringBuilder();
for (String part : parts) {
    sb.append(part);
}
String result = sb.toString();

Collections.unmodifiableList вместо копирования

// ❌ Копирование
return new ArrayList<>(originalList);

// ✅ Если не нужно менять
return Collections.unmodifiableList(originalList);

Ленивые вычисления

// ❌ Compute всегда
public Result compute(Data data) {
    return new Result(expensiveComputation(data));
}

// ✅ Ленивое вычисление
public Result compute(Data data) {
    return new Result(() -> expensiveComputation(data));
}

Мой практический пример

Была медленная операция обработки большого CSV файла (1M строк):

Было: 8 минут

  • Full table scan в БД
  • Последовательная обработка
  • Создание промежуточных List'ов

Сделал:

  1. Добавил индексы на 3 часто используемых колонки (-2 минуты)
  2. Batch insert вместо одного update на строку (-3 минуты)
  3. parallelStream для обработки (-2 минуты)
  4. Убрал N+1 запросы к БД с помощью JOIN'ов (-1 минута)

Итого: 8 мин → 30 сек (16x быстрее)

Чеклист оптимизации

  1. Профилирование - найди узкие места
  2. Выбери правильный алгоритм - O(n) вместо O(n²)
  3. Кэширование - избегай повторных вычислений
  4. Параллелизм - для большых наборов данных
  5. Оптимизация БД - индексы, batch, joins
  6. Мониторинг - убедись, что улучшения работают

Не оптимизируй без измерений!

Как оптимизируешь медленную работу метода | PrepBro