Spec-Zone.ru › Swift

Отладка утечек памяти и использования

Обзор

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

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

Инструменты и методы

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

Основные методы устранения неполадок включают:

  • Использование инструментов профилирования.
  • Просмотр кода и выявление возможных утечек.
  • Включение функций отладки выделения памяти.

1. Использование инструментов профилирования, предоставляемых соответствующими операционными системами и средами разработки, для выявления и анализа использования памяти.

Для macOS: Отладчик графа памяти и это видео по обнаружению и диагностике проблем с памятью будут полезны. Также можно использовать инструмент Xcode Instruments для различных инструментов профилирования, включая инструмент «Выделения» для отслеживания выделения и освобождения памяти в коде Swift.

Для Linux: можно использовать такие инструменты, как Valgrind или Heaptrack, для профилирования приложения, как показано в примерах ниже. Хотя эти инструменты в основном используются для кода C/C++, они также могут работать с Swift.

2. Просмотр кода и выявление возможных утечек, чтобы проверить ваш код на наличие потенциальных областей, где могут возникнуть утечки памяти. Общие источники утечек включают удерживаемые ссылки или несбалансированные циклы retain-release, которые редко применяются к Swift, поскольку он выполняет автоматическое управление ссылками (ARC).

Примечание: утечки памяти могут возникать в Swift, если существуют значительные циклы ссылок между объектами, которые включают замыкания, или если объекты хранят ссылки на внешние ресурсы, которые не освобождаются должным образом. Однако вероятность таких проблем существенно снижается благодаря способности автоматического управления памятью добавлять и удалять ссылки, делая такие источники утечек, как удерживаемые ссылки и несбалансированные циклы retain-release, менее распространенными в коде Swift.

3. Включение функций отладки выделения памяти позволяет получить дополнительную информацию об объектах и их выделении памяти.

На macOS: можно включить Zombie Objects с помощью Xcode или использовать MallocStackLogging для обнаружения объектов, которые были избыточно освобождены или к которым был избыточный доступ.

Для включения Zombie Objects:

  1. Откройте свой проект Xcode.
  2. Перейдите в меню Изменить схему, нажав на раскрывающееся меню схемы в строке меню.
  3. В окне редактора схемы выберите вкладку Запуск.
  4. Выберите вкладку Диагностика.
  5. В разделе Управление памятью установите флажок Включить Zombie Objects.

На Linux: Swift имеет встроенную поддержку LeakSanitizer, которую можно включить с помощью флага компилятора -sanitize=leak.

Устранение неполадок

Этот раздел призван предоставить вам полезные методы устранения неполадок на стороне сервера для отладки утечек и использования с помощью Valgrind, LeakSanitizer и Heaptrack.

Следующая примерная программа содержит утечку памяти. Мы используем её только в качестве примера, чтобы проиллюстрировать различные методы устранения неполадок, упомянутые ниже.

public class MemoryLeaker {
   var closure: () -> Void = { () }
   
   public init() {}
   
   public func doNothing() {}
   
   public func doSomethingThatLeaks() {
      self.closure = {
         // This will leak as it'll create a permanent reference cycle:
         //
         //     self -> self.closure -> self
         self.doNothing()
      }
   }
}
@inline(never) // just to be sure to get this in a stack trace
func myFunctionDoingTheAllocation() {
   let thing = MemoryLeaker()
   thing.doSomethingThatLeaks()
}

myFunctionDoingTheAllocation()

Отладка утечек с помощью Valgrind

Valgrind — это открытая платформа для отладки и профилирования приложений на Linux. Она предоставляет несколько инструментов, в том числе Memcheck, который может обнаруживать утечки памяти, некорректный доступ к памяти и другие ошибки памяти. Хотя Valgrind в основном ориентирован на приложения C/C++, он также может использоваться с Swift на Linux.

Чтобы отладить утечки памяти для Swift на Linux с помощью Valgrind, установите его на вашей системе.

  1. Установите Swift на вашей системе Linux. Вы можете скачать и установить Swift с официального сайта.
  2. Установите Valgrind на вашей системе Linux, используя ваш менеджер пакетов. Например, если вы используете Ubuntu, вы можете выполнить следующую команду:
    sudo apt-get install valgrind
    
  3. После установки Valgrind выполните следующую команду:
    valgrind --leak-check=full swift run
    

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

==1== Memcheck, a memory error detector
==1== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==1== Command: ./test
==1==
==1==
==1== HEAP SUMMARY:
==1==     in use at exit: 824 bytes in 4 blocks
==1==   total heap usage: 5 allocs, 1 frees, 73,528 bytes allocated
==1==
==1== 32 bytes in 1 blocks are definitely lost in loss record 1 of 4
==1==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1==    by 0x52076B1: swift_slowAlloc (in /usr/lib/swift/linux/libswiftCore.so)
==1==    by 0x5207721: swift_allocObject (in /usr/lib/swift/linux/libswiftCore.so)
==1==    by 0x108E58: $s4test12MemoryLeakerCACycfC (in /tmp/test)
==1==    by 0x10900E: $s4test28myFunctionDoingTheAllocationyyF (in /tmp/test)
==1==    by 0x108CA3: main (in /tmp/test)
==1==
==1== LEAK SUMMARY:
==1==    definitely lost: 32 bytes in 1 blocks
==1==    indirectly lost: 0 bytes in 0 blocks
==1==      possibly lost: 0 bytes in 0 blocks
==1==    still reachable: 792 bytes in 3 blocks
==1==         suppressed: 0 bytes in 0 blocks
==1== Reachable blocks (those to which a pointer was found) are not shown.
==1== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==1==
==1== For counts of detected and suppressed errors, rerun with: -v
==1== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Следующий блок трассировки (из примера выше) указывает на утечку памяти.

==1== 32 bytes in 1 blocks are definitely lost in loss record 1 of 4
==1==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1==    by 0x52076B1: swift_slowAlloc (in /usr/lib/swift/linux/libswiftCore.so)
==1==    by 0x5207721: swift_allocObject (in /usr/lib/swift/linux/libswiftCore.so)
==1==    by 0x108E58: $s4test12MemoryLeakerCACycfC (in /tmp/test)
==1==    by 0x10900E: $s4test28myFunctionDoingTheAllocationyyF (in /tmp/test)
==1==    by 0x108CA3: main (in /tmp/test)

Однако, так как Swift использует именование функций и символов, стек вызовов может быть не очевидным для понимания.

Для разбора Swift-символов в стеке вызовов выполните команду swift demangle:

swift demangle <mangled_symbol>

Замените <mangled_symbol> на имя замаскированного символа, показанное в стеке вызовов. Например:

swift demangle $s4test12MemoryLeakerCACycfC

Примечание: swift demangle — это утилита командной строки Swift и должна быть доступна, если у вас установлена среда Swift.

Утилита разберёт символ и отобразит удобочитаемый вариант, как показано ниже:

==1== 32 bytes in 1 blocks are definitely lost in loss record 1 of 4
==1==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1==    by 0x52076B1: swift_slowAlloc (in /usr/lib/swift/linux/libswiftCore.so)
==1==    by 0x5207721: swift_allocObject (in /usr/lib/swift/linux/libswiftCore.so)
==1==    by 0x108E58: test.MemoryLeaker.__allocating_init() -> test.MemoryLeaker (in /tmp/test)
==1==    by 0x10900E: test.myFunctionDoingTheAllocation() -> () (in /tmp/test)
==1==    by 0x108CA3: main (in /tmp/test)

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

test.myFunctionDoingTheAllocation вызова test.MemoryLeaker.__allocating_init()

Ограничения
  • Команда valgrind не понимает битовую упаковку, используемую во многих типах данных Swift, таких как String, или когда enums создаются со связанными значениями. Вследствие этого, команда valgrind иногда сообщает об ошибках памяти или утечках, которые на самом деле не существуют, и происходят ложноотрицательные результаты, когда она не обнаруживает реальные проблемы.
  • Команда valgrind делает ваше приложение значительно медленнее (возможно, в 100 раз), что может помешать воспроизведению проблемы и анализу производительности.
  • Valgrind в основном поддерживается на Linux. Его поддержка на других платформах, таких как macOS или iOS, может быть ограничена или отсутствовать.

Отладка утечек с помощью LeakSanitizer

LeakSanitizer — это детектор утечек памяти, интегрированный в AddressSanitizer. Чтобы отладить утечки памяти с помощью LeakSanitizer с включенным Address Sanitizer в Swift, вам необходимо установить соответствующую переменную среды, скомпилировать свой Swift-пакет с необходимыми параметрами и затем запустить ваше приложение.

Вот шаги:

  1. Откройте сеанс терминала и перейдите в каталог своего Swift-пакета.
  2. Установите переменную среды ASAN_OPTIONS для включения AddressSanitizer и настройки его поведения. Вы можете сделать это, выполнив команду:
    export ASAN_OPTIONS=detect_leaks=1
    
  3. Запустите swift build с дополнительным параметром для включения Address Sanitizer:
    swift build --sanitize=address
    

Процесс сборки скомпилирует ваш код с включённым AddressSanitizer, который автоматически ищет утечки блоков памяти. Если во время сборки будут обнаружены утечки памяти, он выведет информацию (аналогично Valgrind), как показано в примере ниже:

=================================================================
==478==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 32 byte(s) in 1 object(s) allocated from:
    #0 0x55f72c21ac8d  (/tmp/test+0x95c8d)
    #1 0x7f7e44e686b1  (/usr/lib/swift/linux/libswiftCore.so+0x3cb6b1)
    #2 0x55f72c24b2ce  (/tmp/test+0xc62ce)
    #3 0x55f72c24a4c3  (/tmp/test+0xc54c3)
    #4 0x7f7e43aecb96  (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

SUMMARY: AddressSanitizer: 32 byte(s) leaked in 1 allocation(s).

В настоящее время вывод не предоставляет удобочитаемого представления имен функций, потому что LeakSanitizer не символично отображает трассировки стека на Linux. Однако вы можете выполнить символизацию, используя llvm-symbolizer или addr2line, если у вас установлен binutils.

Чтобы установить binutils для Swift на сервере под Linux, выполните следующие действия:

Шаг 1: Подключитесь к серверу Swift через SSH с помощью терминала.

Шаг 2: Обновите списки пакетов, выполнив следующую команду:

sudo apt update

Шаг 3: Установите binutils, выполнив следующую команду:

sudo apt install binutils

Это установит binutils и связанные с ним инструменты для работы с бинарными файлами, объектными файлами и библиотеками, что может быть полезно при разработке и отладке приложений Swift на Linux.

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

# /tmp/test+0xc62ce
addr2line -e /tmp/test -a 0xc62ce -ipf | swift demangle

В этом примере утечка выделения происходит из:

0x00000000000c62ce: test.myFunctionDoingTheAllocation() -> () at crtstuff.c:?
Ограничения
  • LeakSanitizer может быть не так эффективен в обнаружении и отчёте обо всех типах утечек памяти в коде Swift по сравнению с языками, такими как C или C++.
  • Ложноположительные результаты возникают, когда LeakSanitizer сообщает об утечке памяти, которая на самом деле отсутствует.
  • LeakSanitizer в первую очередь поддерживается на macOS и Linux. Хотя использование LeakSanitizer на iOS или других платформах, поддерживающих Swift, возможно, могут существовать ограничения или проблемы, специфичные для платформы.
  • Включение Address Sanitizer и LeakSanitizer в ваш проект Swift может повлиять на производительность. Рекомендуется использовать LeakSanitizer для целевого анализа и отладки, а не для непрерывного запуска в производственной среде.

Отладка временного использования памяти с помощью Heaptrack

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

Графический анализатор интерфейса heaptrack_gui доступен помимо доступа через командную строку. Анализатор позволяет сравнивать два разных запуска вашего приложения, чтобы устранить несоответствия в поведении malloc между feature branch и main.

Используя другой пример, вот краткое руководство по использованию Ubuntu для анализа временного использования.

Шаг 1: Установите heaptrack, выполнив эту команду:

sudo apt-get install heaptrack

Шаг 2: Запустите бинарный файл дважды, используя heaptrack. Первый запуск предоставляет базу для main.

heaptrack .build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
heaptrack output will be written to "/tmp/.nio_alloc_counter_tests_GRusAy/heaptrack.test_1000_autoReadGetAndSet.84341.gz"
starting application, this might take some time...
...
heaptrack stats:
    allocations:              319347
    leaked allocations:       107
    temporary allocations:    68
Heaptrack finished! Now run the following to investigate the data:

  heaptrack --analyze "/tmp/.nio_alloc_counter_tests_GRusAy/heaptrack.test_1000_autoReadGetAndSet.84341.gz"

Шаг 3: Затем запустите его второй раз для feature branch, изменив ветку и перекомпилировав.

heaptrack .build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
heaptrack output will be written to "/tmp/.nio_alloc_counter_tests_GRusAy/heaptrack.test_1000_autoReadGetAndSet.84372.gz"
starting application, this might take some time...
...
heaptrack stats:
    allocations:              673989
    leaked allocations:       117
    temporary allocations:    341011
Heaptrack finished! Now run the following to investigate the data:

  heaptrack --analyze "/tmp/.nio_alloc_counter_tests_GRusAy/heaptrack.test_1000_autoReadGetAndSet.84372.gz"
ubuntu@ip-172-31-25-161 /t/.nio_alloc_counter_tests_GRusAy>

Вывод показывает 673989 выделений в версии feature branch и 319347 в main, что указывает на регрессию.

Шаг 4: Выполните следующую команду, чтобы проанализировать вывод как разницу между этими запусками, используя heaptrack_print и передать его через swift demangle для удобочитаемости:

heaptrack_print -T -d heaptrack.test_1000_autoReadGetAndSet.84341.gz heaptrack.test_1000_autoReadGetAndSet.84372.gz | swift demangle

Примечание: -T выводит временные выделения, предоставляя временные выделения, а не утечки. Если утечки обнаружены, удалите -T.

Прокрутите вниз, чтобы увидеть временные выделения (вывод может быть длинным):

MOST TEMPORARY ALLOCATIONS
307740 temporary allocations of 290324 allocations in total (106.00%) from
swift_slowAlloc
  in /home/ubuntu/bin/usr/lib/swift/linux/libswiftCore.so
43623 temporary allocations of 44553 allocations in total (97.91%) from:
    swift_allocObject
      in /home/ubuntu/bin/usr/lib/swift/linux/libswiftCore.so
    NIO.ServerBootstrap.(bind0 in _C131C0126670CF68D8B594DDFAE0CE57)(makeServerChannel: (NIO.SelectableEventLoop, NIO.EventLoopGroup) throws -> NIO.ServerSocketChannel, _: (NIO.EventLoop, NIO.ServerSocketChannel) -> NIO.EventLoopFuture<()>) -> NIO.EventLoopFuture<NIO.Channel>
      at /home/ubuntu/swiftnio/swift-nio/Sources/NIO/Bootstrap.swift:295
      in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
    merged NIO.ServerBootstrap.bind(host: Swift.String, port: Swift.Int) -> NIO.EventLoopFuture<NIO.Channel>
      in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
    NIO.ServerBootstrap.bind(host: Swift.String, port: Swift.Int) -> NIO.EventLoopFuture<NIO.Channel>
      in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
    Test_test_1000_autoReadGetAndSet.run(identifier: Swift.String) -> ()
      at /tmp/.nio_alloc_counter_tests_GRusAy/Sources/Test_test_1000_autoReadGetAndSet/file.swift:24
      in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
    main
      at Sources/bootstrap_test_1000_autoReadGetAndSet/main.c:18
      in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
22208 temporary allocations of 22276 allocations in total (99.69%) from:
    swift_allocObject
      in /home/ubuntu/bin/usr/lib/swift/linux/libswiftCore.so
    generic specialization <Swift.UnsafeBufferPointer<Swift.Int8>> of Swift._copyCollectionToContiguousArray<A where A: Swift.Collection>(A) -> Swift.ContiguousArray<A.Element>
      in /home/ubuntu/bin/usr/lib/swift/linux/libswiftCore.so
    Swift.String.utf8CString.getter : Swift.ContiguousArray<Swift.Int8>
      in /home/ubuntu/bin/usr/lib/swift/linux/libswiftCore.so
    NIO.URing.getEnvironmentVar(Swift.String) -> Swift.String?
      at /home/ubuntu/swiftnio/swift-nio/Sources/NIO/LinuxURing.swift:291
      in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
    NIO.URing._debugPrint(@autoclosure () -> Swift.String) -> ()
      at /home/ubuntu/swiftnio/swift-nio/Sources/NIO/LinuxURing.swift:297
...
22196 temporary allocations of 22276 allocations in total (99.64%) from:

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

NIO.URing.getEnvironmentVar(Swift.String) -> Swift.String?
  at /home/ubuntu/swiftnio/swift-nio/Sources/NIO/LinuxURing.swift:291
  in /tmp/.nio_alloc_counter_tests_GRusAy/.build/x86_64-unknown-linux-gnu/release/test_1000_autoReadGetAndSet
NIO.URing._debugPrint(@autoclosure () -> Swift.String) -> ()

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

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

Ограничения
  • Важно отметить, что Heaptrack был в первую очередь разработан для приложений C и C++, поэтому его поддержка приложений Swift ограничена.
  • Хотя Heaptrack может предоставлять информацию о выделении и освобождении памяти в приложении Swift, он может не захватывать некоторые механизмы управления памятью, специфичные для Swift, такие как встроенный профилировщик инструментов 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/memory-leaks-and-usage.html

Spec-Zone.ru

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