XAML 태그를 구문 분석하여 메모리에서 개체를 생성하는 것은 복잡한 UI에 시간이 많이 걸립니다. XAML 태그의 구문 분석 및 로드 시간과 WinUI 앱의 메모리 효율성을 개선하기 위해 수행할 수 있는 몇 가지 작업은 다음과 같습니다.
앱을 시작할 때 로드되는 XAML 태그를 초기 UI에 필요한 항목으로만 제한합니다. 페이지 리소스를 포함하여 초기 페이지에서 태그를 검사하고 즉시 필요하지 않은 추가 요소를 로드하지 않는지 확인합니다. 이러한 요소는 리소스 사전, 처음에 축소된 요소 및 다른 요소 위에 그려진 요소와 같은 다양한 원본에서 가져올 수 있습니다.
효율성을 위해 XAML을 최적화하려면 절충이 필요합니다. 모든 상황에 대한 단일 솔루션이 항상 있는 것은 아닙니다. 여기서는 몇 가지 일반적인 문제를 살펴보고 WinUI 앱에 적합한 장단점을 만드는 데 사용할 수 있는 지침을 제공합니다.
요소 수 최소화
XAML 플랫폼은 많은 수의 요소를 표시할 수 있지만 원하는 시각적 개체를 달성하는 데 필요한 가장 적은 수의 요소를 사용하여 앱을 배치하고 더 빠르게 렌더링할 수 있습니다.
UI 컨트롤을 배치하는 방법에서 선택하는 것은 앱이 시작될 때 생성되는 UI 요소의 수에 영향을 줍니다. 레이아웃 최적화에 대한 자세한 내용은 XAML 레이아웃 최적화를 참조하세요.
각 요소가 각 데이터 항목에 대해 다시 생성되므로 데이터 템플릿에서 요소 수는 매우 중요합니다. 목록 또는 그리드에서 요소 수를 줄이는 방법에 대한 자세한 내용은 WinUI에 대한 Optimize ListView 및 GridView 성능 문서의 항목당 요소 감소를 참조하세요.
여기서는 시작 시 앱이 로드해야 하는 요소의 수를 줄일 수 있는 몇 가지 다른 방법을 살펴봅니다.
항목 만들기 연기
XAML 태그에 즉시 표시되지 않는 요소가 포함된 경우 표시될 때까지 해당 요소의 로드를 연기할 수 있습니다. 예를 들어 탭과 유사한 UI에서 보조 탭과 같이 보이지 않는 콘텐츠 만들기를 지연할 수 있습니다. 또는 기본적으로 그리드 보기에 항목을 표시할 수 있지만 사용자가 대신 목록에서 데이터를 볼 수 있는 옵션을 제공합니다. 필요할 때까지 목록 로드를 지연할 수 있습니다.
Visibility 속성 대신 x:Load 특성을 사용하여 요소가 표시되는 시기를 제어합니다. 요소의 표시 유형이 Collapsed로 설정된 경우 렌더링 패스 중에는 건너뛰지만 여전히 메모리의 개체 인스턴스 비용을 지불합니다. 대신 x:Load를 사용하는 경우 프레임워크는 필요할 때까지 개체 인스턴스를 만들지 않으므로 메모리 비용이 훨씬 낮습니다. 단점은 UI가 로드되지 않을 때 작은 메모리 오버헤드(약 600바이트)를 지불한다는 것입니다.
메모
Windows 앱 SDK에서 x:Load 는 즉시 필요하지 않은 XAML 콘텐츠에 권장되는 지연 로드 패턴입니다.
다음 예제에서는 다른 기술을 사용하여 UI 요소를 숨길 때 요소 수와 메모리 사용의 차이를 보여 줍니다. 동일한 항목을 포함하는 ListView 및 GridView는 페이지의 루트 그리드에 배치됩니다. ListView는 표시되지 않지만 GridView가 표시됩니다. 이러한 각 예제의 XAML은 화면에서 동일한 UI를 생성합니다. 프로파일링 및 성능 도구를 사용하여 앱에서 요소 수 및 메모리 사용을 확인합니다.
옵션 1 - 비효율적
여기서 ListView는 로드되었지만, Width가 0이기 때문에 보이지 않습니다. ListView 및 각 자식 요소는 시각적 트리에서 만들어지고 메모리에 로드됩니다.
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="List1" Width="0">
<ListViewItem>Item 1</ListViewItem>
<ListViewItem>Item 2</ListViewItem>
<ListViewItem>Item 3</ListViewItem>
<ListViewItem>Item 4</ListViewItem>
<ListViewItem>Item 5</ListViewItem>
<ListViewItem>Item 6</ListViewItem>
<ListViewItem>Item 7</ListViewItem>
<ListViewItem>Item 8</ListViewItem>
<ListViewItem>Item 9</ListViewItem>
<ListViewItem>Item 10</ListViewItem>
</ListView>
<GridView x:Name="Grid1">
<GridViewItem>Item 1</GridViewItem>
<GridViewItem>Item 2</GridViewItem>
<GridViewItem>Item 3</GridViewItem>
<GridViewItem>Item 4</GridViewItem>
<GridViewItem>Item 5</GridViewItem>
<GridViewItem>Item 6</GridViewItem>
<GridViewItem>Item 7</GridViewItem>
<GridViewItem>Item 8</GridViewItem>
<GridViewItem>Item 9</GridViewItem>
<GridViewItem>Item 10</GridViewItem>
</GridView>
</Grid>
ListView가 로드된 라이브 시각적 트리입니다. 페이지의 총 요소 수는 89개입니다.
ListView 및 해당 자식은 메모리에 로드됩니다.
옵션 2 - 개선
여기서 ListView의 Visibility는 Collapsed로 설정됩니다 (다른 XAML은 원본과 동일합니다). ListView는 시각적 트리에서 만들어지지만 자식 요소는 만들어지지 않습니다. 그러나 데이터가 여전히 메모리에 로드되므로 메모리 사용은 이전 예제와 동일합니다.
<ListView x:Name="List1" Visibility="Collapsed">
ListView가 축소된 실시간 시각적 트리입니다. 페이지의 총 요소 수는 46개입니다.
ListView 및 해당 자식은 메모리에 로드됩니다.
옵션 3 - 가장 효율적
여기서 ListView에는 x:Load 특성이 False 로 설정됩니다(다른 XAML은 원본과 동일). ListView는 시각적 트리에서 생성되거나 시작할 때 메모리에 로드되지 않습니다.
<ListView x:Name="List1" Visibility="Collapsed" x:Load="False">
ListView가 로드되지 않은 라이브 시각적 트리입니다. 페이지의 총 요소 수는 45개입니다.
ListView 및 해당 자식은 메모리에 로드되지 않습니다.
메모
이러한 예제의 요소 수와 메모리 사용은 매우 작으며 개념을 보여 주는 데만 표시됩니다. 이러한 예제에서는 x:Load를 사용하는 오버헤드가 메모리 절약보다 크므로 앱이 도움이 되지 않습니다. 앱에서 프로파일링 도구를 사용하여 지연된 로드가 도움이 되는지 여부를 결정해야 합니다.
레이아웃 패널 속성 사용
레이아웃 패널에는 Background 속성이 있으므로 색을 지정하기 위해 패널 앞에 사각형 을 배치할 필요가 없습니다.
비효율적인
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid>
<Rectangle Fill="Black"/>
</Grid>
효율적인
<Grid Background="Black"/>
레이아웃 패널에는 기본 제공 테두리 속성도 있으므로 레이아웃 패널 주위에 Border 요소를 배치할 필요가 없습니다. 자세한 정보 및 예제는 XAML 레이아웃 최적화 를 참조하세요.
벡터 기반 요소 대신 이미지 사용
동일한 벡터 기반 요소를 충분한 시간 동안 다시 사용하면 Image 요소를 대신 사용하는 것이 더 효율적입니다. CPU가 각 개별 요소를 별도로 만들어야 하므로 벡터 기반 요소는 더 비쌀 수 있습니다. 이미지 파일은 한 번만 디코딩해야 합니다.
리소스의 최적화 및 리소스 사전
일반적으로 리소스 사전을 사용하여 앱의 여러 위치에서 참조하려는 리소스를 전역 수준에서 저장합니다. 예를 들어 스타일, 브러시, 템플릿 등이 있습니다.
일반적으로 ResourceDictionary 는 요청될 때까지 리소스를 인스턴스화하지 않도록 최적화됩니다. 그러나 리소스가 불필요하게 인스턴스화되지 않도록 피해야 하는 상황이 있습니다.
x:Name을 사용하는 리소스
x:Key 특성을 사용하여 리소스를 참조합니다. x:Name 특성이 있는 리소스는 플랫폼 최적화의 이점을 누릴 수 없습니다. 대신 ResourceDictionary가 만들어지는 즉시 인스턴스화됩니다. x:Name은 앱이 이 리소스에 대한 필드 액세스가 필요하다고 플랫폼에 알리기 때문에 플랫폼이 참조를 보유할 항목을 만들어야 하기 때문에 발생합니다.
UserControl의 ResourceDictionary
UserControl 내부에 정의된 ResourceDictionary는 페널티를 전달합니다. 플랫폼은 UserControl의 모든 인스턴스에 대해 이러한 ResourceDictionary의 복사본을 만듭니다. 많이 사용되는 UserControl이 있는 경우 UserControl에서 ResourceDictionary를 이동하여 페이지 수준에 배치합니다.
Resource 및 ResourceDictionary 범위
페이지가 다른 파일에 정의된 사용자 컨트롤 또는 리소스를 참조하는 경우 프레임워크도 해당 파일을 구문 분석합니다.
여기서 InitialPage.xaml 은 ExampleResourceDictionary.xaml에서 하나의 리소스를 사용하므로 시작 시 전체 ExampleResourceDictionary.xaml 을 구문 분석해야 합니다.
InitialPage.xaml
<Page x:Class="ExampleNamespace.InitialPage" ...>
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ExampleResourceDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<Grid>
<TextBox Foreground="{StaticResource TextBrush}"/>
</Grid>
</Page>
ExampleResourceDictionary.xaml
<ResourceDictionary>
<SolidColorBrush x:Key="TextBrush" Color="#FF3F42CC"/>
<!--This ResourceDictionary contains many other resources that
are used in the app, but are not needed during startup.-->
</ResourceDictionary>
앱 전체의 여러 페이지에서 리소스를 사용하는 경우 App.xaml 에 리소스를 저장하는 것이 좋은 방법이며 중복을 방지합니다. 그러나 App.xaml 은 앱 시작 시 구문 분석되므로 해당 페이지가 초기 페이지가 아닌 한 한 페이지에서만 사용되는 모든 리소스를 페이지의 로컬 리소스에 배치해야 합니다. 이 예제에서는 초기 페이지가 아닌 한 페이지에서만 사용되는 리소스가 포함된 App.xaml 을 보여 줍니다. 이렇게 하면 불필요하게 시작 시간이 늘어나게 됩니다.
App.xaml
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Application ...>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextBrush" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="InitialPageTextBrush" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="SecondPageTextBrush" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="ThirdPageTextBrush" Color="#FF3F42CC"/>
</Application.Resources>
</Application>
InitialPage.xaml
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.InitialPage" ...>
<StackPanel>
<TextBox Foreground="{StaticResource InitialPageTextBrush}"/>
</StackPanel>
</Page>
SecondPage.xaml
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.SecondPage" ...>
<StackPanel>
<Button Content="Submit" Foreground="{StaticResource SecondPageTextBrush}"/>
</StackPanel>
</Page>
이 예제를 보다 효율적으로 만들려면 SecondPageTextBrush로 이동하고 ThirdPageTextBrush로 이동합니다.
InitialPageTextBrush 애플리케이션 리소스는 어떤 경우든 앱 시작 시 구문 분석해야 하므로 App.xaml 에 남아 있을 수 있습니다.
동일하게 보이는 여러 브러시를 하나의 리소스로 통합
XAML 플랫폼은 가능한 한 자주 다시 사용할 수 있도록 일반적으로 사용되는 개체를 캐시하려고 합니다. 그러나 XAML은 태그의 한 조각에 선언된 브러시가 다른 부분에 선언된 브러시와 같은지 여부를 쉽게 알 수 없습니다. 이 예제에서는 SolidColorBrush 를 사용하여 보여 주지만 GradientBrush에서 사례는 더 가능성이 높고 더 중요합니다. 미리 정의된 색을 사용하는 브러시도 확인합니다. 예를 들어 "Orange""#FFFFA500" 동일한 색입니다.
비효율적인
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page ... >
<StackPanel>
<TextBlock>
<TextBlock.Foreground>
<SolidColorBrush Color="#FFFFA500"/>
</TextBlock.Foreground>
</TextBlock>
<Button Content="Submit">
<Button.Foreground>
<SolidColorBrush Color="#FFFFA500"/>
</Button.Foreground>
</Button>
</StackPanel>
</Page>
중복을 해결하려면 브러시를 리소스로 정의합니다. 다른 페이지의 컨트롤에서 동일한 브러시를 사용하는 경우 App.xaml로 이동합니다.
효율적인
<Page ... >
<Page.Resources>
<SolidColorBrush x:Key="BrandBrush" Color="#FFFFA500"/>
</Page.Resources>
<StackPanel>
<TextBlock Foreground="{StaticResource BrandBrush}" />
<Button Content="Submit" Foreground="{StaticResource BrandBrush}" />
</StackPanel>
</Page>
초과 그리기 최소화
덮어쓰기는 동일한 화면 픽셀에 둘 이상의 개체가 그려지는 경우에 발생합니다. 이 지침과 요소 수를 최소화하려는 욕구 사이에는 때때로 절충이 있습니다.
DebugSettings.IsOverdrawHeatMapEnabled를 시각적 진단으로 사용합니다. 장면에서 몰랐던 개체가 그려지는 것을 볼 수 있습니다.
투명 또는 숨겨진 요소
요소가 다른 요소 뒤에 투명하거나 숨겨져 있어 표시되지 않고 레이아웃에 기여하지 않는 경우 삭제합니다. 요소가 초기 시각적 상태에 표시되지 않지만 다른 시각적 상태에 표시되는 경우 x:Load를 사용하여 상태를 제어하거나 요소 자체에서 Visibility를Collapsed 로 설정하고 적절한 상태에서 값을 Visible 로 변경합니다. 이 추론에는 예외가 있습니다. 일반적으로 대부분의 시각적 상태에 있는 속성 값은 요소에서 로컬로 설정하는 것이 가장 좋습니다.
복합 요소
여러 요소를 계층화하여 효과를 만드는 대신 복합 요소를 사용합니다. 이 예제에서 결과는
비효율적인
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Black">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="1" Fill="White" Opacity=".5"/>
</Grid>
효율적인
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Black"/>
<Rectangle Grid.Row="1" Fill="#FF7F7F7F"/>
</Grid>
레이아웃 패널
레이아웃 패널에는 영역에 색을 지정하고 자식 요소를 배치하는 두 가지 용도가 있을 수 있습니다. z 순서에서 뒤에 있는 요소가 이미 영역에 색을 지정하는 경우, 앞에 있는 레이아웃 패널은 해당 영역을 그릴 필요가 없습니다. 대신, 자식 요소를 배치하는 데 집중할 수 있습니다. 다음은 예제입니다.
비효율적인
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<GridView Background="Blue">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="Blue"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
효율적인
<GridView Background="Blue">
<GridView.ItemTemplate>
<DataTemplate>
<Grid/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
그리드를 적중 테스트할 수 있어야 하는 경우 그리드의 Transparent 배경 값을 설정합니다.
테두리
Border 요소를 사용하여 개체 주위에 테두리를 그립니다. 이 예제에서는 Grid 가 TextBox 주변의 임시 테두리로 사용됩니다. 그러나 가운데 셀의 모든 픽셀은 과도하게 그려집니다.
비효율적인
<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Blue" Width="300" Height="45">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="1" Grid.Column="1"></TextBox>
</Grid>
효율적인
<Border BorderBrush="Blue" BorderThickness="5" Width="300" Height="45">
<TextBox/>
</Border>
여백
여백에 유의하세요. 음수 여백이 다른 요소의 렌더링 경계까지 확장되어 오버드로잉이 발생하면 인접한 두 요소가 실수로 겹칠 수 있습니다.
정적 콘텐츠 캐시
오버드로잉의 또 다른 원인은 여러 겹치는 요소로 만든 셰이프입니다. 복합 셰이프가 포함된 UIElement에서 CacheMode를 BitmapCache로 설정하는 경우 플랫폼은 요소를 비트맵에 한 번 렌더링하고 오버드래핑 대신 각 프레임마다 해당 비트맵을 사용합니다.
비효율적인
<Canvas Background="White">
<Ellipse Height="40" Width="40" Fill="Blue"/>
<Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
<Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>
위의 이미지는 결과이며, 여기에 오버드로우된 지역의 지도가 있습니다. 진한 빨간색은 초과 인출의 양이 더 높다는 것을 나타냅니다.
효율적인
<Canvas Background="White" CacheMode="BitmapCache">
<Ellipse Height="40" Width="40" Fill="Blue"/>
<Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
<Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>
CacheMode의 사용을 기록해 둡니다. 하위 셰이프 중 하나라도 애니메이션 효과를 주면 목적이 무색해지게 비트맵 캐시가 모든 프레임마다 다시 생성될 수 있으니 이 기술을 사용하지 마세요.
컴파일된 XAML 출력 사용
Windows 앱 SDK는 XAML을 빌드의 일부로 이진 표현으로 컴파일하여 런타임 시 텍스트 구문 분석 비용을 방지합니다. 또한 컴파일된 형식은 시각적 상태, 리소스 사전 및 스타일과 같은 일반적인 XAML 형식에 대한 로드 및 트리 생성을 최적화합니다.
기본 제공 WinUI 컨트롤 및 사전은 이미 이 파이프라인의 이점을 누릴 수 있습니다. 사용자 고유의 WinUI 앱의 경우 생성된 태그 컴파일 출력을 런타임에 사용할 수 있도록 일반 XAML 컴파일 단계를 사용하도록 설정합니다.
관련된 문서
Windows developer