Windows 앱 SDK를 사용하여 시작 작업을 줄이고, 첫 번째 프레임을 단순화하고, 창이 대화형인 후 중요하지 않은 기능을 로드하여 빠르게 시작하는 WinUI 앱을 만듭니다.
앱 시작 성능 모범 사례
부분적으로 사용자는 앱이 시작하는 데 걸리는 시간을 기준으로 앱이 빠르거나 느린지 여부를 인식합니다. 이 항목의 목적을 위해 앱의 시작 시간은 사용자가 앱을 시작하고 사용자가 의미 있는 방식으로 앱과 상호 작용할 수 있을 때 종료되는 경우에 시작됩니다. 이 문서에서는 WinUI 앱에서 더 나은 시작 성능을 얻는 방법에 대한 제안을 제공합니다.
앱의 시작 시간 측정
실제로 시작 시간을 측정하기 전에 앱을 몇 번 시작해야 합니다. 이렇게 하면 측정에 대한 기준이 제공되고 가능한 한 짧은 시작 시간을 측정할 수 있습니다.
최종 사용자가 경험하게 될 내용을 나타내는 측정값을 사용합니다. 측정 릴리스는 대표적인 하드웨어를 기반으로 하며, 콜드 및 웜 시작 모두를 검토하고, 프로세스가 존재할 때까지의 시간만을 측정하는 것이 아니라 첫 번째 인터랙티브 프레임까지의 시간에 집중합니다.
가능한 한 오래 작업 연기
앱의 시작 시간을 개선하려면 사용자가 앱과 상호 작용을 시작할 수 있도록 반드시 수행해야 하는 작업만 수행합니다. 이는 추가 어셈블리 로드를 지연할 수 있는 경우에 특히 유용합니다. 공용 언어 런타임은 어셈블리를 처음 사용할 때 로드합니다. 로드되는 어셈블리 수를 최소화할 수 있는 경우 앱의 시작 시간과 메모리 사용량을 향상시킬 수 있습니다.
장기 실행 작업을 독립적으로 수행
앱이 완전히 작동하지 않는 부분이 있더라도 앱은 대화형일 수 있습니다. 예를 들어 앱에서 검색하는 데 시간이 걸리는 데이터를 표시하는 경우 데이터를 비동기적으로 검색하여 해당 코드를 앱의 시작 코드와 독립적으로 실행하도록 할 수 있습니다. 데이터를 사용할 수 있는 경우 앱의 사용자 인터페이스를 데이터로 채웁니다.
데이터를 검색하는 대부분의 API는 비동기이므로 어쨌든 데이터를 비동기적으로 검색할 수 있습니다. 자세한 내용은 async 및 await를 사용한 비동기 프로그래밍을 참조하세요. 비동기 API를 사용하지 않는 작업을 수행하는 경우 사용자가 앱과 상호 작용하는 것을 차단하지 않도록 클래스를 사용하여 Task 장기 실행 작업을 수행할 수 있습니다. 이렇게 하면 데이터가 로드되는 동안 앱의 응답성이 유지됩니다.
앱이 UI의 일부를 로드하는 데 특히 오랜 시간이 걸리는 경우 사용자가 앱이 여전히 처리 중임을 알 수 있도록 "최신 데이터 가져오기"와 같은 메시지를 해당 영역에 표시하는 것이 좋습니다.
시작 시간 최소화
가장 간단한 앱을 제외한 모든 앱은 리소스를 로드하고, XAML을 구문 분석하고, 데이터 구조를 설정하고, 시작하는 동안 논리를 실행하는 데 상당한 시간이 필요합니다. WinUI 앱의 경우 프로세스 시작, 창 만들기, 기본 페이지 만들기, 첫 번째 프레임의 레이아웃/렌더링 등 4단계에서 시작에 대해 생각하는 데 도움이 됩니다.
시작 기간은 사용자가 앱을 시작하는 시점과 앱이 작동하는 시점 사이의 시간입니다. 이는 앱에 대한 사용자의 첫 인상이기 때문에 중요한 시기입니다. 사용자는 시스템 및 앱에서 즉각적이고 지속적인 피드백을 기대합니다. 앱이 빠르게 시작되지 않을 때 시스템과 앱이 손상되거나 제대로 설계되지 않은 것으로 인식됩니다.
시작 단계 소개
시작에는 다양한 이동 조각이 포함되며, 모든 조각은 최상의 사용자 환경을 위해 조정되어야 합니다. 다음 단계는 앱을 시작하는 사용자와 표시되는 애플리케이션 콘텐츠 간에 발생합니다.
- 프로세스가 시작되고 템플릿에서 생성된 시작 코드가 호출
Main됩니다. -
Application개체가 만들어집니다.-
InitializeComponent를 호출하면 앱 생성자가App.xaml을(를) 구문 분석하고 개체를 생성합니다.
-
-
Application.OnLaunched이 발생합니다.
- 앱 코드는 주 창을 만들고, 초기 콘텐츠를 할당하고, 호출
Activate합니다. - 기본 페이지 생성자는 XAML 페이지를 구문 분석하고 개체를 만들도록 하는 호출
InitializeComponent을 호출합니다.
- 앱 코드는 주 창을 만들고, 초기 콘텐츠를 할당하고, 호출
- XAML 프레임워크는 측정값 및 정렬을 포함하여 레이아웃 패스를 실행합니다.
-
ApplyTemplate는 각 컨트롤에 대해 컨트롤 템플릿 콘텐츠를 만들도록 합니다. 이는 일반적으로 시작하는 동안 레이아웃 시간의 대부분입니다.
-
- 렌더링은 창 내용에 대한 시각적 개체를 만듭니다.
- 첫 번째 프레임이 표시되고 시작 후 작업이 비동기적으로 계속됩니다.
시작 경로에서 더 적은 작업을 수행합니다.
첫 번째 프레임에 필요하지 않은 모든 것을 시작 코드 경로에서 제거하세요.
- 첫 번째 프레임 동안 필요하지 않은 컨트롤을 포함하는 사용자 DLL이 있는 경우 지연 로드를 고려하세요.
- 클라우드의 데이터에 따라 UI의 일부가 있는 경우 해당 UI를 분할합니다. 먼저 클라우드 데이터에 종속되지 않는 UI를 가져온 다음 클라우드 종속 UI를 비동기적으로 표시합니다. 또한 애플리케이션이 오프라인으로 작동하거나 느린 네트워크 연결의 영향을 받지 않도록 데이터를 로컬로 캐싱하는 것도 고려해야 합니다.
- UI가 데이터를 기다리는 경우 진행률 UI를 표시합니다.
- 구성 파일의 구문 분석이 많은 앱 디자인 또는 코드에서 동적으로 생성되는 UI를 주의해야 합니다.
요소 수 줄이기
XAML 앱의 시작 성능은 시작 중에 만드는 요소 수와 직접 상관 관계가 있습니다. 만드는 요소가 적을수록 앱을 시작하는 데 걸리는 시간이 줄어듭니다. 대략적인 벤치마크로 각 요소를 만들려면 1ms를 사용합니다.
- 항목 컨트롤에 사용되는 템플릿은 여러 번 반복되므로 가장 큰 영향을 미칠 수 있습니다. ListView 및 GridView UI 최적화를 참조하세요.
- UserControls 및 컨트롤 템플릿이 확장되므로 이러한 템플릿도 고려해야 합니다.
- 화면에 표시되지 않는 XAML을 만드는 경우 시작 중에 이러한 XAML 조각을 만들어야 하는지 여부를 정당화해야 합니다.
Visual Studio Live Visual Tree 창에는 트리의 각 노드에 대한 자식 요소 수가 표시됩니다.
지연을 사용합니다. 요소를 축소하거나 불투명도를 0으로 설정해도 요소가 만들어지지는 않습니다.
x:Load 또는 x:DeferLoadStrategy을 사용하여 UI의 로드를 지연한 뒤, 필요한 경우 로드할 수 있습니다. 이는 필요할 때 또는 지연된 논리 집합의 일부로 로드할 수 있도록 시작 중에 표시되지 않는 UI 처리를 지연하는 좋은 방법입니다. 로드를 트리거하려면 요소에 대한 호출 FindName 만 필요합니다. 예제 및 자세한 내용은 x:Load 특성 및 x:DeferLoadStrategy 특성을 참조하세요.
가상화. UI에 목록 또는 반복기 콘텐츠가 있는 경우 UI 가상화를 사용하는 것이 좋습니다. 목록 UI가 가상화되지 않은 경우 모든 요소를 미리 만드는 데 드는 비용을 지불하면 시작 속도가 느려질 수 있습니다. ListView 및 GridView UI 최적화를 참조하세요.
애플리케이션 성능은 원시 성능에 관한 것이 아닙니다. 그것은 또한 인식에 관한 것입니다. 시각적 측면이 먼저 발생하도록 작업 순서를 변경하면 일반적으로 사용자가 애플리케이션이 더 빠른 것처럼 느껴집니다. 사용자는 콘텐츠가 화면에 표시될 때 로드된 애플리케이션을 고려합니다. 애플리케이션은 일반적으로 시작 중에 여러 작업을 수행해야 하며, UI를 가져오는 데 모든 작업이 필요한 것은 아니므로 이러한 부분을 UI보다 낮게 지연하거나 우선 순위를 지정해야 합니다.
이 문서에서는 애니메이션 및 비디오 용어에서 가져온 첫 번째 프레임에 대해 설명하며 최종 사용자가 콘텐츠를 볼 때까지 걸리는 시간을 측정합니다.
스타트업 인식 개선
간단한 온라인 게임의 예제를 사용하여 시작의 각 단계와 다양한 기술을 식별하여 프로세스 전체에서 사용자에게 피드백을 제공해 보겠습니다.
첫 번째 단계에서 프로세스가 시작되고 앱이 해당 창을 만듭니다. 이 시기 동안 사용자는 앱의 자체 콘텐츠를 아직 보지 못했습니다. 목표는 화면에 간단한 창을 빠르게 가져오는 것입니다.
두 번째 단계는 게임에 중요한 구조를 만들고 초기화하는 것을 포함합니다. 앱이 시작 시 사용할 수 있는 데이터를 사용하여 초기 UI를 빠르게 만들 수 있는 경우 이 단계는 간단하며 UI를 즉시 표시할 수 있습니다. 그렇지 않으면 앱이 초기화되는 동안 경량 로드 페이지를 표시합니다.
로딩 페이지의 모양은 사용자에게 달려 있습니다. 진행률 표시줄 또는 진행률 링을 표시하는 것만큼 간단할 수 있습니다. 핵심은 앱이 완전히 응답하기 전에 작업을 수행하고 있음을 나타낸다는 것입니다. 게임의 경우 초기 화면에서 일부 이미지와 소리를 디스크에서 메모리로 로드해야 합니다. 이러한 작업에는 시간이 걸리므로 앱은 게임의 테마와 관련된 간단한 애니메이션이 있는 로드 페이지를 표시하여 사용자에게 정보를 제공합니다.
세 번째 단계는 게임이 로딩 페이지를 대체하는 대화형 UI를 만들기 위한 최소한의 정보 집합이 있는 후에 시작됩니다. 이 시점에서 온라인 게임에 사용할 수 있는 유일한 정보는 앱이 디스크에서 로드한 콘텐츠일 수 있습니다. 이 게임은 대화형 UI를 만들기에 충분한 콘텐츠와 함께 제공 될 수 있지만 온라인 게임이기 때문에 인터넷에 연결하고 몇 가지 추가 정보를 다운로드 할 때까지 완전히 작동하지 않습니다. 필요한 모든 정보가 필요할 때까지 사용자는 UI와 상호 작용할 수 있지만 웹에서 추가 데이터가 필요한 기능은 콘텐츠가 여전히 로드되고 있다는 피드백을 제공해야 합니다. 앱이 완전히 작동하는 데 시간이 걸릴 수 있으므로 가능한 한 빨리 기능을 사용할 수 있도록 하는 것이 중요합니다.
이제 온라인 게임에서 시작의 세 단계를 확인했으므로 실제 코드에 연결해 보겠습니다.
1단계 및 2단계
앱의 생성자만 사용하여 앱에 중요한 데이터 구조를 초기화합니다.
OnLaunched 앱에서 피드백을 즉시 표시할 수 있도록 첫 번째 창을 빠르게 만들고, 경량 콘텐츠를 할당하고, 창을 활성화하는 데 계속 집중하세요.
public partial class App : Application
{
public static Window MainWindow { get; private set; } = null!;
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
MainWindow = new MainWindow();
MainWindow.Content = new LoadingPage();
MainWindow.Activate();
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
// Asynchronously restore state and load the minimum data needed
// to create the first interactive UI.
await LoadInitialDataAsync();
MainWindow.Content = new GameHomePage();
}
private static Task LoadInitialDataAsync()
{
// Download data to populate the initial UI.
return Task.CompletedTask;
}
}
OnLaunched의 주요 작업 중 하나는 UI를 만들고, Window.Content에 할당한 다음, Window.Activate를 호출하는 것입니다. 둘 이상의 활성화 흐름이 필요한 경우 동일한 원칙을 유지합니다. 경량 콘텐츠를 빠르게 표시하고 중요한 시작 경로에서 비용이 많이 드는 작업을 이동합니다.
시작하는 동안 로드 페이지를 표시하는 앱은 백그라운드에서 기본 UI를 만드는 작업을 시작할 수 있습니다. 해당 요소를 만든 후 FrameworkElement.Loaded 이벤트가 발생합니다. 이벤트 처리기에서 현재 로드 화면인 창의 콘텐츠를 새로 만든 홈페이지로 바꿀 수 있습니다.
초기화 기간이 연장된 앱은 로드 페이지를 표시하는 것이 중요합니다. 시작 프로세스에 대한 피드백을 제공하는 것 외에도 사용자가 앱이 진행 중임을 알 수 있도록 창을 빠르게 활성화해야 합니다.
partial class GameHomePage : Page
{
public GameHomePage()
{
InitializeComponent();
// Add a handler to be called when the home page has been loaded.
Loaded += GameHomePageLoaded;
// Load the minimal amount of image and sound data from disk necessary
// to create the home page.
}
private void GameHomePageLoaded(object sender, RoutedEventArgs e)
{
// Set the content of the main window to the home page now that it's
// ready to be displayed.
App.MainWindow.Content = this;
}
}
3단계
앱이 UI를 표시했다고 해서 UI를 완전히 사용할 준비가 되었음을 의미하지는 않습니다. 게임의 경우 인터넷의 데이터가 필요한 기능에 대한 자리 표시자와 함께 UI가 표시됩니다. 이 시점에서 게임은 앱이 완벽하게 작동하도록 하는 데 필요한 추가 데이터를 다운로드하고 데이터를 획득할 때 점진적으로 기능을 사용하도록 설정합니다.
경우에 따라 시작에 필요한 콘텐츠의 대부분을 앱으로 패키지할 수 있습니다. 이러한 간단한 게임의 경우입니다. 이렇게 하면 시작 프로세스가 매우 간단합니다. 그러나 뉴스 독자 및 사진 시청자와 같은 많은 프로그램은 작동하려면 웹에서 정보를 가져와야 합니다. 이 데이터는 클 수 있으며 다운로드하는 데 상당한 시간이 걸릴 수 있습니다. 시작 중에 앱이 이 데이터를 가져오는 방법은 인식된 성능에 큰 영향을 미칠 수 있습니다.
앱이 시작의 첫 번째 또는 두 번째 단계에서 기능에 필요한 전체 데이터 집합을 다운로드하려고 하면 로드 페이지를 너무 오래 표시할 수 있습니다. 이렇게 하면 앱이 중단된 것처럼 보입니다. 앱은 2단계에서 자리 표시자 요소가 있는 대화형 UI를 표시하는 데 필요한 최소한의 데이터를 다운로드한 다음, 3단계에서 자리 표시자 요소를 대체하는 데이터를 점진적으로 로드하는 것이 좋습니다. 데이터 처리에 대한 자세한 내용은 ListView 및 GridView 최적화를 참조하세요.
앱이 시작의 각 단계에 정확히 어떻게 반응하는지는 전적으로 사용자에게 달려 있지만 간단한 초기 UI를 사용하고 화면을 로드하며 점진적인 데이터 로드를 통해 사용자에게 최대한 많은 피드백을 제공하면 앱이 더 빠르게 느껴집니다.
시작 경로에서 관리되는 어셈블리 최소화
재사용 가능한 코드는 종종 프로젝트에 포함된 모듈(DLL) 형식으로 제공됩니다. 이러한 모듈을 로드하려면 디스크에 액세스해야 하며 비용이 더해질 수 있습니다. 이는 콜드 시작에 가장 큰 영향을 주지만 따뜻한 시작에도 영향을 줄 수 있습니다. .NET 앱에서 CLR은 요청 시 어셈블리를 로드하여 가능한 한 많은 비용을 지연하려고 합니다. 즉, CLR은 실행된 메서드가 모듈을 참조할 때까지 모듈을 로드하지 않습니다. 따라서 CLR이 불필요한 모듈을 로드하지 않도록 시작 코드에서 앱을 시작하는 데 필요한 어셈블리만 참조합니다. 시작 경로에 불필요한 참조가 있는 사용되지 않는 코드 경로가 있는 경우 이러한 코드 경로를 다른 메서드로 이동하여 불필요한 로드를 방지합니다.
모듈 로드를 줄이는 또 다른 방법은 앱 모듈을 결합하는 것입니다. 하나의 큰 어셈블리를 로드하는 데는 일반적으로 두 개의 작은 어셈블리를 로드하는 것보다 시간이 적게 걸립니다. 항상 가능한 것은 아니며, 모듈이 개발자 생산성 또는 코드 재사용성에 중대한 차이를 만들지 않는 경우에만 모듈을 결합해야 합니다. PerfView 또는 WPA(Windows Performance Analyzer)와 같은 도구를 사용하여 시작할 때 로드되는 모듈을 확인할 수 있습니다.
스마트 웹 요청 만들기
XAML, 이미지 및 앱에 중요한 기타 파일을 포함하여 콘텐츠를 로컬로 패키징하여 앱의 로드 시간을 크게 향상시킬 수 있습니다. 디스크 작업은 네트워크 작업보다 빠릅니다. 초기화 시 앱에 특정 파일이 필요한 경우 원격 서버에서 검색하는 대신 디스크에서 로드하여 전체 시작 시간을 줄일 수 있습니다.
저널 및 캐시 페이지를 효율적으로 관리하려면
Frame 컨트롤은 탐색 기능을 제공합니다. 페이지 탐색(Navigate 메서드), 탐색 저널링(BackStack 및 ForwardStack 속성, GoForward 및 GoBack 메서드), 페이지 캐싱(Page.NavigationCacheMode), 그리고 serialization 지원(GetNavigationState 메서드)을 제공합니다.
Frame에서 주의해야 할 성능은 주로 저널링 및 페이지 캐싱과 관련이 있습니다.
프레임 저널링. 페이지에서 Frame.Navigate를 탐색하면 현재 페이지에 대한 PageStackEntry가 Frame.BackStack 컬렉션에 추가됩니다.
PageStackEntry는 상대적으로 작지만 BackStack 컬렉션의 크기에 기본으로 제공되는 제한은 없습니다. 잠재적으로 사용자는 루프에서 탐색하고 이 컬렉션을 무기한 확장할 수 있습니다.
메서드 PageStackEntry 에 전달된 Frame.Navigate 매개 변수도 포함됩니다. 메서드가 작동하기 위해 매개변수를 int, string, 또는 Frame.GetNavigationState와 같은 직렬화 가능한 기본 형식으로 사용할 것을 권장합니다. 그러나 이 매개 변수는 잠재적으로 더 많은 양의 작업 집합 또는 기타 리소스를 차지하는 개체를 참조하여 각 항목 BackStack 의 비용이 훨씬 더 많이 들 수 있습니다. 예를 들어 잠재적으로 매개 변수로 사용할 StorageFile 수 있으며 BackStack 결과적으로 무한 개수의 파일을 열어 둘 수 있습니다.
따라서 탐색 매개 변수를 작게 유지하고 크기를 제한하는 BackStack것이 좋습니다. C BackStack #의 표준 컬렉션이므로 항목을 제거하여 트리밍할 수 있습니다.
페이지 캐싱. 기본적으로 메서드를 사용하여 페이지 Frame.Navigate 로 이동하면 페이지의 새 인스턴스가 인스턴스화됩니다. 마찬가지로 이전 페이지로 Frame.GoBack다시 이동하면 이전 페이지의 새 인스턴스가 할당됩니다.
Frame 또한 이러한 인스턴스화를 방지할 수 있는 선택적 페이지 캐시를 제공합니다. 페이지를 캐시에 넣려면 속성을 사용합니다 Page.NavigationCacheMode . 해당 모드를 Required로 설정하면 페이지가 강제로 캐시되고, Enabled로 설정하면 페이지가 캐시될 수 있도록 허용합니다. 기본적으로 캐시 크기는 10페이지이지만 속성으로 재정의 Frame.CacheSize 할 수 있습니다. 모든 Required 페이지가 캐시되고 필요한 페이지보다 CacheSize 적은 경우 페이지 Enabled 도 캐시할 수 있습니다.
페이지 캐싱은 인스턴스화를 방지하여 탐색 성능을 향상시킴으로써 전체적인 성능을 개선할 수 있습니다. 페이지 캐싱은 오버 캐싱으로 인해 성능이 저하되어 작업 집합에 영향을 줄 수 있습니다.
따라서 애플리케이션에 맞게 페이지 캐싱을 사용하는 것이 좋습니다. 예를 들어 항목 목록을 Frame표시하는 앱이 있다고 가정하고 항목을 선택하면 해당 항목의 세부 정보 페이지로 프레임을 이동합니다. 목록 페이지는 캐시로 설정해야 합니다. 모든 항목에 대해 세부 정보 페이지가 동일한 경우 캐시해야 할 수도 있습니다. 그러나 세부 정보 페이지가 이질적인 경우 캐싱을 해제하는 것이 더 좋을 수 있습니다.
Windows developer