Spec-Zone.ru › Swift

Отладка проблем производительности

Обзор

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

Вот некоторые основные методы и инструменты для отладки проблем производительности в Swift:

  1. Измерение производительности: Инструменты Xcode и perf Linux предоставляют инструменты профилирования для отслеживания производительности вашего приложения и выявления областей, которые потребляют чрезмерные ресурсы ЦП, памяти или энергии. Например, профилирование и графики вызовов показывают потребление ЦП, а графики памяти — потребление памяти. Важно отметить, что каждая платформа по-разному управляет измерением производительности вашего приложения.

    • Для macOS см. Начало работы с инструментами.
    • Для Linux см. perf: профилирование Linux с помощью счетчиков производительности.
  2. Профилирование использования памяти: Используйте Отладчик графика памяти Xcode для выявления и устранения проблем, связанных с памятью.

  3. Выполнение бенчмарков и измерение улучшений: Продолжайте итерации и оптимизацию до достижения желаемой производительности.

Совет: Мы рекомендуем компилировать ваш Swift-код в режиме release, чтобы обеспечить оптимальную производительность. Разница в производительности между сборками отладки и релизной значительна. Вы можете сделать это, выполнив команду swift build -c release перед настройкой кода для сбора данных.

Инструменты

Отладка проблем производительности иногда может быть сложным и итеративным процессом. Он требует сочетания методов, инструментов и анализа. Мы собрали некоторые инструменты и методы, которые помогут вам эффективно идентифицировать и устранять узкие места, такие как:

  • Графики вызовов (Flame graphs)
  • Библиотеки malloc

Графики вызовов

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

Графики вызовов в Xcode

Хотя в Xcode нет встроенного инструмента, специально предназначенного для создания графиков вызовов, подобных Linux perf, вы можете использовать сторонние инструменты для генерации графиков вызовов для некоторых приложений, разработанных с использованием Xcode.

Один из часто используемых инструментов для создания графиков вызовов — это Инструменты, которые являются частью Xcode. Вы можете использовать инструмент «Профилировщик времени» в Инструментах для захвата стеков и преобразования захваченных данных в график вызовов с помощью инструментов, таких как flamegraph.pl. Запуск приложения с инструментами, используя Профилировщик времени, а затем преобразование собранных данных в график вызовов, может дать вам представление о профиле производительности вашего приложения.

Графики вызовов в Linux

Графики вызовов можно создавать на большинстве платформ, включая Swift на Linux. В этом разделе мы сосредоточимся на Linux.

Для обсуждения приведем пример программы с графиком вызовов на Linux, которая использует структуру данных TerribleArray, что приводит к неэффективным добавлениям O(n) вместо ожидаемой амортизированной временной сложности O(1) для Array. Это может вызвать проблемы с производительностью и повлиять на общую эффективность программы.

/* a terrible data structure which has a subset of the operations that Swift's
 * array does:
 *  - retrieving elements by index
 *     --> user's reasonable performance expectation: O(1)   (like Swift's Array)
 *     --> implementation's actual performance:       O(n)
 *  - adding elements
 *     --> user's reasonable performance expectation: amortised O(1)   (like Swift's Array)
 *     --> implementation's actual performance:       O(n)
 *
 * ie. the problem I'm trying to demo here is that this is an implementation
 * where the user would expect (amortised) constant time access but in reality
 * is linear time.
 */
struct TerribleArray<T: Comparable> {
    /* this is a terrible idea: storing the index inside of the array (so we can
     * waste some performance later ;)
     */
    private var storage: Array<(Int, T)> = Array()

    /* oh my */
    private func maximumIndex() -> Int {
        return (self.storage.map { $0.0 }.max()) ?? -1
    }

    /* expectation: amortised O(1) but implementation is O(n) */
    public mutating func append(_ value: T) {
        let maxIdx = self.maximumIndex()
        self.storage.append((maxIdx + 1, value))
        assert(self.storage.count == maxIdx + 2)
    }

    /* expectation: O(1) but implementation is O(n) */
    public subscript(index: Int) -> T? {
        get {
            return self.storage.filter({ $0.0 == index }).first?.1
        }
    }
}

protocol FavouriteNumbers {
    func addFavouriteNumber(_ number: Int)
    func isFavouriteNumber(_ number: Int) -> Bool
}

public class MyFavouriteNumbers: FavouriteNumbers {
    private var storage: TerribleArray<Int>
    public init() {
        self.storage = TerribleArray<Int>()
    }

    /* - user's expectation: O(n)
     * - reality O(n^2) because of TerribleArray */
    public func isFavouriteNumber(_ number: Int) -> Bool {
        var idx = 0
        var found = false
        while true {
            if let storageNum = self.storage[idx] {
                if number == storageNum {
                    found = true
                    break
                }
            } else {
                break
            }
            idx += 1
        }
        return found
    }

    /* - user's expectation: amortised O(1)
     * - reality O(n) because of TerribleArray */
    public func addFavouriteNumber(_ number: Int) {
        self.storage.append(number)
        precondition(self.isFavouriteNumber(number))
    }
}

let x: FavouriteNumbers = MyFavouriteNumbers()

for f in 0..<2_000 {
    x.addFavouriteNumber(f)
}

Генерация графика вызовов

Для генерации графиков вызовов в Swift на Linux вы можете использовать различные инструменты, такие как perf в сочетании с FlameGraph скриптами для сбора данных об использовании ЦП и стековых следах. Затем их можно визуализировать с помощью инструментов графиков вызовов, чтобы получить представление об характеристиках производительности приложения следующим образом:

  1. Установите и настройте perf для Linux, чтобы собирать данные о производительности.
  2. Скомпилируйте код с помощью swift build -c release в двоичный файл под названием ./slow, выполнив эти шаги:

    a. Откройте свой терминал и перейдите в каталог, содержащий ваш Swift-код, обычно в корневой каталог вашего Swift-пакета.

    b. Выполните следующую команду для компиляции кода в релизном режиме, оптимизируя сборку для производительности:

     swift build -c release
    

    После успешного завершения процесса сборки вы можете найти скомпилированный двоичный файл в каталоге .build/release/ в каталоге вашего Swift-пакета.

    c. Скопируйте скомпилированный двоичный файл в текущий каталог и переименуйте его в slow с помощью следующей команды:

     cp .build/release/YourExecutableName ./slow
    

    Замените YourExecutableName на фактическое имя вашего скомпилированного двоичного файла.

  3. Скопируйте репозиторий в каталог ~/FlameGraph с помощью этой команды:
     git clone https://github.com/brendangregg/FlameGraph
    
  4. Выполните эту команду для записи стековых кадров со скоростью дискретизации 99 Гц:
     sudo perf record -F 99 --call-graph dwarf -- ./slow
    

В качестве альтернативы, для подключения к существующему процессу используйте: sudo perf record -F 99 --call-graph dwarf -p PID_OF_SLOW

  1. Экспортируйте запись в out.perf, выполнив эту команду:
     sudo perf script > out.perf
    
  2. Агрегируйте записанные стеки и разобрать символы с помощью этой команды:
     ~/FlameGraph/stackcollapse-perf.pl out.perf | swift demangle > out.folded
    
  3. Экспортируйте результат в файл SVG, чтобы визуально представить функции и их относительное использование ЦП с помощью следующей команды:
     ~/FlameGraph/flamegraph.pl out.folded > out.svg # Produce
    

Полученный файл Flamegraph должен выглядеть примерно так:

Flame graph

Мы видим на графике вызовов, что isFavouriteNumber потребляет большую часть времени выполнения, вызываемый из addFavouriteNumber. Это указывает, на что следует обратить внимание при поиске улучшений.

Примечание: Если вы используете Set<Int> для хранения FavouriteNumber, побочный продукт должен указать, является ли число FavouriteNumber за константное время (O(1)).

Библиотеки malloc

В Swift выделение и освобождение памяти в основном управляются механизмом автоматической подсчета ссылок (ARC). В некоторых случаях вам может потребоваться взаимодействие с C или другими языками с использованием библиотек malloc, или если вам нужен более точный контроль над управлением памятью. Например, для нагрузок, оказывающих существенное давление на подсистему выделения памяти, вы можете использовать пользовательскую библиотеку malloc. Хотя изменения в коде не нужны, для запуска вашего сервера необходимо ввести ее с помощью переменной среды.

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

Ниже приведены некоторые специализированные библиотеки выделения памяти, разработанные для решения проблем с производительностью, особенно в многопоточных средах:

  • TCMalloc оптимизирован для скорости и масштабируемости в средах Google.
  • Jemalloc акцентирует внимание на уменьшении фрагментации и эффективности для более широкого круга приложений.

Существуют и другие malloc реализации, которые обычно можно включить с помощью LD_PRELOAD:

> LD_PRELOAD=/usr/bin/libjemalloc.so  myprogram

Выбор между этими библиотеками зависит от конкретных потребностей в производительности и характеристик приложения или системы.

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

The Swift Programming Language, Copyright © 2014-2025 Apple Inc.
Swift and the Swift logo are trademarks of Apple Inc.

Documentation for Swift 6.0.3


https://www.swift.org/documentation/server/guides/performance.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API