Вопрос или проблема
Я столкнулся с странным багом, из-за которого мое приложение зависает при вызове makeComputePipelineState
с “сложной” металлической функцией.
Под сложной я подразумевал функцию с немного большей логикой, большинство функций работают нормально, но у меня есть 2 металлические функции с этой проблемой. Чем старее устройство, тем дольше оно зависает. На iPad 6-го поколения оно зависает до краха, а иногда и на iPad 7-го. Все более новые модели зависают всего на короткое время или вообще не зависают.
Кроме того, это происходит только в первый раз; если функция удачно проходит зависание, не будучи убитой iOS, последующие вызовы работают прекрасно.
Это происходит на всех версиях iOS.
Вот стек вызовов для зависания:
CrashReporter Key: --
Модель устройства: iPad7,5
Процесс: MyApp
Идентификатор: com.MyApp
Версия: 2.42.4
Роль: Передний план
Версия ОС: iOS 16.1.1
Зависание приложения: Приложение было завершено во время неответа
0 libsystem_kernel.dylib +0x1190 _mach_msg2_trap
1 libsystem_kernel.dylib +0x12a04 _mach_msg2_internal
2 libsystem_kernel.dylib +0x12c40 _mach_msg_overwrite
3 libsystem_kernel.dylib +0x1684 _mach_msg
4 libdispatch.dylib +0x1b0ec __dispatch_mach_send_and_wait_for_reply
5 libdispatch.dylib +0x1b47c _dispatch_mach_send_with_result_and_wait_for_reply$VARIANT$mp
6 libxpc.dylib +0xe018 _xpc_connection_send_message_with_reply_sync
7 Metal +0x260d8 XPCCompilerConnection::BuildRequestInternal(MTLCompilerRequest*, char const*, NSObject objcproto16OS_dispatch_data*, int, bool, void ( block_pointer)(unsigned int, void const*, unsigned long, char const*))
8 Metal +0x2df54 ____ZN21XPCCompilerConnection12BuildRequestEP18MTLCompilerRequestPKcPU27objcproto16OS_dispatch_data8NSObjectibU13block_pointerFvjPKvmS3_E_block_invoke
9 libdispatch.dylib +0x647c4 __dispatch_client_callout
10 libdispatch.dylib +0x11a74 __dispatch_lane_barrier_sync_invoke_and_complete
11 Metal +0x2d2b8 XPCCompilerConnection::BuildRequest(MTLCompilerRequest*, char const*, NSObject objcproto16OS_dispatch_data*, int, bool, void ( block_pointer)(unsigned int, void const*, unsigned long, char const*))
12 Metal +0x32f90 MTLCompilerConnectionManagerPrivate::buildRequest(unsigned int, MTLCompilerRequest*, bool, void ( block_pointer)(MTLCompilerError, NSObject objcproto16OS_dispatch_data*, char const*))
13 Metal +0x37f8 -[MTLCompiler compileFunctionRequestInternal:frameworkLinking:linkDataSize:reflectionOnly:completionHandler:]
14 AGXMetalA10 +0x920c 0x2030b120c (0x2030b1038 + 468)
15 AGXMetalA10 +0x7e94 0x2030afe94 (0x2030afb50 + 836)
16 AGXMetalA10 +0x43878 0x2030eb878 (0x2030eb4cc + 940)
17 AGXMetalA10 +0x3b1f8 0x2030e31f8 (0x2030e31c0 + 56)
18 Metal +0x28f7c -[_MTLDevice newComputePipelineStateWithFunction:error:]
19 MyApp вызывает makeComputePipelineState
Ответ или решение
Почему makeComputePipelineState
вызывает зависание приложения на старых устройствах
Ситуация, с которой вы столкнулись, действительно может вызвать серьезные проблемы при разработке и тестировании вашего приложения. Давайте разберем основные аспекты, которые могут быть ответственны за зависание при вызове функции makeComputePipelineState
с "сложными" Metal-функциями, особенно на старых устройствах, таких как iPad 6-го и 7-го поколений.
Причины зависания:
-
Эффективность компиляции Metal-шейдеров: Когда вы вызываете
makeComputePipelineState
с более сложными шейдерами, процесс их компиляции может требовать больше ресурсов. На старых устройствах, таких как iPad 6-го поколения с архитектурой A10, время компиляции может прерваться из-за ограниченных мощностей процессора. Компилятор Metal пытается оптимизировать и скомпилировать код, что может занять больше времени, чем это возможно на современном оборудовании. -
Перегрузка XPC-процессов: Стек вызовов, который вы предоставили, указывает на использование XPC для передачи сообщений между вашим приложением и компонентами Metal. На старых устройствах может происходить значительная задержка, когда XPC не может обработать запрос на компиляцию, что приводит к зависанию приложения, пока не истечет тайм-аут. Это может быть значительно выражено при первой компиляции функции, так как все оптимизации и загрузки производятся в этот момент.
-
Отсутствие кэширования: Если ваше приложение впервые пытается скомпилировать функцию, которую оно не использовало ранее, Metal должен выполнить все работы по созданию состояния конвейера. На новых устройствах могут быть механизмы кэширования промежуточных результатов компиляции, отсутствующие на старых.
Как предотвратить зависание:
-
Оптимизация шейдеров: Убедитесь, что ваши Metal-функции максимально оптимизированы. Упрощение кода шейдера может заметно снизить время компиляции. Возможно, вы сможете разделить сложные операции на несколько более простых.
-
Периодическая компиляция: Попробуйте вызвать
makeComputePipelineState
в фоновом режиме или провести компиляцию шейдеров заранее при запуске приложения, а не во время выполнения основного потока логики приложения. Это может помочь избежать зависания интерфейса пользователя. -
Логирование и мониторинг производительности: Добавьте механизм логирования, который будет фиксировать время компиляции шейдеров на различных устройствах. Это даст вам возможность понять, какие функции вызывают наибольшие задержки и как это связано с типом оборудования.
-
Минимизация использования сложных функций на старых устройствах: Если возможно, предоставьте альтернативные, менее ресурсоемкие функции для старых устройств, чтобы обеспечить плавную работу приложения для всех пользователей.
Заключение
Зависание приложения при первом вызове makeComputePipelineState
на старых устройствах может быть вызвано несколькими факторами, включая сложности компиляции Metal, ограничения XPC, а также отсутствие кэширования. Оптимизация шейдеров, предварительная компиляция и реализация фоновых потоков могут значительно повысить производительность и предотвратить зависания. Эти меры помогут не только улучшить взаимодействие с пользователем, но и повысить стабильность вашего приложения на большем количестве устройств.