플랫폼 간 성능Cross-Platform Performance

낮은 애플리케이션 성능은 여러가지 방법으로 나타납니다.Poor application performance presents itself in many ways. 이 경우에 애플리케이션이 응답하지 않는 것처럼 보이고, 스크롤 속도가 느려지고, 배터리 수명이 줄어들 수 있습니다.It can make an application seem unresponsive, can cause slow scrolling, and can reduce battery life. 그러나 성능을 최적화하려면 효율적인 코드를 구현하는 것 이상이 필요합니다.However, optimizing performance involves more than just implementing efficient code. 애플리케이션 성능에 대한 사용자 환경도 고려해야 합니다.The user's experience of application performance must also be considered. 예를 들어 사용자가 다른 활동을 수행하지 못하도록 차단하지 않고 작업을 실행하면 사용자 환경을 향상시키는 데 도움이 될 수 있습니다.For example, ensuring that operations execute without blocking the user from performing other activities can help to improve the user's experience.

프로파일러 사용Use the Profiler

애플리케이션을 개발하는 경우 프로파일링된 후에만 코드를 최적화하려고 해야 합니다.When developing an application, it's important to only attempt to optimize code once it has been profiled. 프로파일링은 코드 최적화가 성능 문제를 줄이는 데 가장 큰 영향을 주는지를 확인하는 기술입니다.Profiling is a technique for determining where code optimizations will have the greatest effect in reducing performance problems. 프로파일러는 애플리케이션의 메모리 사용을 추적하고, 애플리케이션에서 메서드의 실행 시간을 기록합니다.The profiler tracks the application's memory usage, and records the running time of methods in the application. 이 데이터를 통해 최적화할 시점을 검색할 수 있도록 애플리케이션의 실행 경로 및 코드의 실행 비용을 탐색할 수 있습니다.This data helps to navigate through the execution paths of the application, and the execution cost of the code, so that the best opportunities for optimization can be discovered.

Xamarin Profiler는 애플리케이션에서 성능 관련 문제를 측정하고, 평가하고, 찾을 수 있습니다.The Xamarin Profiler will measure, evaluate, and help find performance-related issues in an application. Mac용 Visual Studio 또는 Visual Studio 내에서 Xamarin.iOS 및 Xamarin.Android 애플리케이션을 프로파일링하는 데 사용할 수 있습니다.It can be used to profile Xamarin.iOS and Xamarin.Android applications from inside Visual Studio for Mac or Visual Studio. Xamarin Profiler에 대한 자세한 내용은 Xamarin Profiler 소개를 참조하세요.For more information about the Xamarin Profiler, see Introduction to the Xamarin Profiler.

앱을 프로파일링할 때 다음과 같은 모범 사례를 사용하는 것이 좋습니다.The following best practices are recommended when profiling an app:

  • 시뮬레이터가 애플리케이션 성능을 왜곡할 수 있으므로 시뮬레이터에서 애플리케이션을 프로파일링하지 않도록 방지합니다.Avoid profiling an application in a simulator, as the simulator may distort the application performance.
  • 하나의 디바이스에서 성능 측정을 수행하더라도 다른 디바이스의 성능 특성을 표시하지 않기 때문에 이상적으로 다양한 디바이스에서 프로파일링을 수행할 수 있어야 합니다.Ideally, profiling should be performed on a variety of devices, as taking performance measurements on one device won't always show the performance characteristics of other devices. 그러나 프로파일링은 최소한 가장 낮은 사양이 예상되는 디바이스에서 수행되어야 합니다.However, at a minimum, profiling should be performed on a device that has the lowest anticipated specification.
  • 다른 애플리케이션이 아닌 프로파일링된 애플리케이션의 전체 영향을 측정할 수 있도록 다른 모든 애플리케이션을 닫습니다.Close all other applications to ensure that the full impact of the application being profiled is being measured, rather than the other applications.

IDisposable 리소스 릴리스Release IDisposable Resources

IDisposable 인터페이스는 리소스를 릴리스하는 메커니즘을 제공합니다.The IDisposable interface provides a mechanism for releasing resources. 명시적으로 리소스를 릴리스하기 위해 구현해야 하는 Dispose 메서드를 제공합니다.It provides a Dispose method that should be implemented to explicitly release resources. IDisposable은 소멸자가 아니며 다음과 같은 경우에만 구현되어야 합니다.IDisposable is not a destructor, and should only be implemented in the following circumstances:

  • 클래스가 관리되지 않는 리소스를 소유하는 경우When the class owns unmanaged resources. 릴리스해야 하는 일반적인 관리되지 않는 리소스에는 파일, 스트림 및 네트워크 연결이 포함됩니다.Typical unmanaged resources that require releasing include files, streams, and network connections.
  • 클래스가 관리 IDisposable 리소스를 소유하는 경우When the class owns managed IDisposable resources.

인스턴스가 더 이상 필요하지 않은 경우 형식 소비자는 IDisposable.Dispose 구현을 호출하여 리소스를 해제할 수 있습니다.Type consumers can then call the IDisposable.Dispose implementation to free resources when the instance is no longer required. 이를 위한 두 가지 방법이 있습니다.There are two approaches for achieving this:

  • using 문에서 IDisposable 개체를 래핑합니다.By wrapping the IDisposable object in a using statement.
  • try/finally 블록에서 IDisposable.Dispose에 대한 호출을 래핑합니다.By wrapping the call to IDisposable.Dispose in a try/finally block.

using 문에서 IDisposable 개체 래핑Wrapping the IDisposable Object in a using Statement

다음 코드 예제에서는 using 문에서 IDisposable 개체를 래핑하는 방법을 보여줍니다.The following code example shows how to wrap an IDisposable object in a using statement:

public void ReadText (string filename)
{
  ...
  string text;
  using (StreamReader reader = new StreamReader (filename)) {
    text = reader.ReadToEnd ();
  }
  ...
}

StreamReader 클래스는 IDisposable을 구현하고, using 문은 범위를 벗어나기 전에 StreamReader 개체에서 StreamReader.Dispose 메서드를 호출하는 편리한 구문을 제공합니다.The StreamReader class implements IDisposable, and the using statement provides a convenient syntax that calls the StreamReader.Dispose method on the StreamReader object prior to it going out of scope. using 블록 내에서 StreamReader 개체는 읽기 전용이며 다시 할당할 수 없습니다.Within the using block, the StreamReader object is read-only and cannot be reassigned. 또한 using 문을 사용하면 컴파일러가 try/finally 블록에서 IL(중간 언어)을 구현하므로 예외가 발생하는 경우에도 Dispose 메서드를 호출할 수 있습니다.The using statement also ensures that the Dispose method is called even if an exception occurs, as the compiler implements the intermediate language (IL) for a try/finally block.

Try/Finally 블록에서 IDisposable.Dispose에 대한 호출 래핑Wrapping the Call to IDisposable.Dispose in a Try/Finally Block

다음 코드 예제에서는 try/finally 블록에서 IDisposable.Dispose에 대한 호출을 래핑하는 방법을 보여줍니다.The following code example shows how to wrap the call to IDisposable.Dispose in a try/finally block:

public void ReadText (string filename)
{
  ...
  string text;
  StreamReader reader = null;
  try {
    reader = new StreamReader (filename);
    text = reader.ReadToEnd ();
  } finally {
    if (reader != null) {
      reader.Dispose ();
    }
  }
  ...
}

StreamReader 클래스는 IDisposable을 구현하고, finally 블록은 StreamReader.Dispose 메서드를 호출하여 리소스를 릴리스합니다.The StreamReader class implements IDisposable, and the finally block calls the StreamReader.Dispose method to release the resource.

자세한 내용은 IDisposable 인터페이스를 참조하세요.For more information, see IDisposable Interface.

이벤트 구독 취소Unsubscribe from Events

메모리 누수를 방지하려면 구독자 개체를 삭제하기 전에 이벤트 구독을 취소해야 합니다.To prevent memory leaks, events should be unsubscribed from before the subscriber object is disposed of. 이벤트 구독을 취소할 때까지 게시 개체에서 이벤트의 대리자에는 구독자의 이벤트 처리기를 캡슐화하는 대리자에 대한 참조가 있습니다.Until the event is unsubscribed from, the delegate for the event in the publishing object has a reference to the delegate that encapsulates the subscriber's event handler. 게시 개체에 해당 참조가 있다면 가비지 수집은 구독자 개체 메모리를 회수하지 않습니다.As long as the publishing object holds this reference, garbage collection will not reclaim the subscriber object memory.

다음 코드 예제에서는 이벤트의 구독을 취소하는 방법을 보여줍니다.The following code example shows how to unsubscribe from an event:

public class Publisher
{
  public event EventHandler MyEvent;

  public void OnMyEventFires ()
  {
    if (MyEvent != null) {
      MyEvent (this, EventArgs.Empty);
    }
  }
}

public class Subscriber : IDisposable
{
  readonly Publisher publisher;

  public Subscriber (Publisher publish)
  {
    publisher = publish;
    publisher.MyEvent += OnMyEventFires;
  }

  void OnMyEventFires (object sender, EventArgs e)
  {
    Debug.WriteLine ("The publisher notified the subscriber of an event");
  }

  public void Dispose ()
  {
    publisher.MyEvent -= OnMyEventFires;
  }
}

Subscriber 클래스가 해당 Dispose 메서드의 이벤트 구독을 해제합니다.The Subscriber class unsubscribes from the event in its Dispose method.

람다 식에서 개체를 참조하고 활성 상태로 유지할 수 있으므로 참조 주기는 이벤트 처리기 및 람다 구문을 사용하는 경우에 발생할 수 있습니다.Reference cycles can also occur when using event handlers and lambda syntax, as lambda expressions can reference and keep objects alive. 따라서 무명 메서드에 대한 참조는 필드에 저장되고 다음 코드 예제와 같이 이벤트의 구독을 취소하는 데 사용될 수 있습니다.Therefore, a reference to the anonymous method can be stored in a field and used to unsubscribe from the event, as shown in the following code example:

public class Subscriber : IDisposable
{
  readonly Publisher publisher;
  EventHandler handler;

  public Subscriber (Publisher publish)
  {
    publisher = publish;
    handler = (sender, e) => {
      Debug.WriteLine ("The publisher notified the subscriber of an event");
    };
    publisher.MyEvent += handler;
  }

  public void Dispose ()
  {
    publisher.MyEvent -= handler;
  }
}

handler 필드는 무명 메서드에 대한 참조를 유지 관리하고 이벤트를 구독하고 구독을 취소하는 데 사용됩니다.The handler field maintains the reference to the anonymous method, and is used for event subscription and unsubscribe.

약한 참조를 사용하여 유한 개체 방지Use Weak References to Prevent Immortal Objects

참고

iOS 개발자는 iOS에서 순환 참조를 방지하는 방법에 대한 설명서를 검토하여 해당 앱이 메모리를 효율적으로 사용하도록 해야 합니다.iOS developers should review the documentation on avoiding circular references in iOS to ensure their apps use memory efficiently.

개체를 만드는 비용 연기Delay the Cost of Creating Objects

초기화 지연은 개체를 처음 사용할 때까지 생성을 지연하는 데 사용될 수 있습니다.Lazy initialization can be used to defer the creation of an object until it's first used. 이 방법을 통해 기본적으로 성능을 향상시키고, 계산을 방지하고, 메모리 요구 사항을 줄입니다.This technique is primarily used to improve performance, avoid computation, and reduce memory requirements.

다음과 같은 두 가지 시나리오에서 만드는 데 비용이 많이 드는 개체에 초기화 지연을 사용하는 것이 좋습니다.Consider using lazy initialization for objects that are expensive to create in this two scenarios:

  • 애플리케이션이 개체를 사용하지 않을 수 있습니다.The application might not use the object.
  • 다른 비용이 많이 드는 작업은 개체를 만들기 전에 완료되어야 합니다.Other expensive operations must complete before the object is created.

다음 코드 예제에서 설명한 대로 Lazy<T> 클래스는 초기화 지연 형식을 정의하는 데 사용됩니다.The Lazy<T> class is used to define a lazy-initialized type, as demonstrated in the following code example:

void ProcessData(bool dataRequired = false)
{
  Lazy<double> data = new Lazy<double>(() =>
  {
    return ParallelEnumerable.Range(0, 1000)
                 .Select(d => Compute(d))
                 .Aggregate((x, y) => x + y);
  });

  if (dataRequired)
  {
    if (data.Value > 90)
    {
      ...
    }
  }
}

double Compute(double x)
{
  ...
}

처음으로 Lazy<T>.Value 속성에 액세스할 때 초기화 지연이 발생합니다.Lazy initialization occurs the first time the Lazy<T>.Value property is accessed. 처음 액세스할 때 래핑된 형식이 생성되고 반환되며 나중에 액세스하는 데 사용하기 위해 저장됩니다.The wrapped type is created and returned on first access, and stored for any future access.

초기화 지연에 대한 자세한 내용은 초기화 지연을 참조하세요.For more information about lazy initialization, see Lazy Initialization.

비동기 작업 구현Implement Asynchronous Operations

.NET은 많은 API의 비동기 버전을 제공합니다..NET provides asynchronous versions of many of its APIs. 동기 API와 달리 비동기 API는 활성 실행 스레드가 상당한 시간 동안 스레드 호출을 차단하지 않도록 합니다.Unlike synchronous APIs, the asynchronous APIs ensure that the active execution thread never blocks the calling thread for a significant amount of time. 따라서 UI 스레드에서 API를 호출할 때 가능하면 비동기 API를 사용합니다.Therefore, when calling an API from the UI thread, use the asynchronous API if it's available. 그러면 UI 스레드의 차단이 해제됩니다. 따라서 애플리케이션에서 사용자의 환경을 개선할 수 있습니다.This will keep the UI thread unblocked, which will help to improve the user's experience with the application.

또한 장기 실행 작업이 UI 스레드를 차단하지 않도록 백그라운드 스레드에서 실행되어야 합니다.In addition, long running operations should be executed on a background thread, to avoid blocking the UI thread. .NET에서는 asyncawait 키워드를 제공하여 백그라운드 스레드에서 장기 실행 작업을 실행하고 완료 시 결과에 액세스하는 비동기 코드를 작성할 수 있습니다..NET provides the async and await keywords that enable the writing of asynchronous code that executes long running operations on a background thread, and accesses the results on completion. 그러나 await 키워드를 사용하여 장기 실행 작업을 비동기적으로 실행할 수 있는 반면 작업이 백그라운드 스레드에서 실행된다고 보장할 수 없습니다.However, while long running operations can be executed asynchronously with the await keyword, this does not guarantee that the operation will run on a background thread. 대신, 다음 코드 예제에서 보여준 대로 장기 실행 작업을 Task.Run에 전달하여 수행할 수 있습니다.Instead, this can be accomplished by passing the long running operation to Task.Run, as shown in the following code example:

public class FaceDetection
{
  ...
  async void RecognizeFaceButtonClick(object sender, EventArgs e)
  {
    await Task.Run(() => RecognizeFace ());
    ...
  }

  async Task RecognizeFace()
  {
    ...
  }
}

계속하기 전에 RecognizeFace 메서드가 완료될 때까지 RecognizeFaceButtonClick 메서드가 수신 대기하여 백그라운드 스레드에서 RecognizeFace 메서드가 실행됩니다.The RecognizeFace method executes on a background thread, with the RecognizeFaceButtonClick method waiting until the RecognizeFace method completes before continuing.

장기 실행 작업은 취소도 지원해야 합니다.Long running operations should also support cancellation. 예를 들어 사용자가 애플리케이션 내에서 탐색하는 경우 장기 실행 작업을 계속할 필요가 없을 수 있습니다.For example, continuing a long running operation may become unnecessary if the user navigates within the application. 취소를 구현하는 패턴은 다음과 같습니다.The pattern for implementing cancellation is as follows:

  • CancellationTokenSource 인스턴스를 만듭니다.Create a CancellationTokenSource instance. 이 인스턴스는 취소 알림을 관리하고 보냅니다.This instance will manage and send cancellation notifications.
  • 취소할 수 있어야 하는 각 작업에 CancellationTokenSource.Token 속성 값을 전달합니다.Pass the CancellationTokenSource.Token property value to each task that should be cancelable.
  • 각 작업이 취소에 응답하는 메커니즘을 제공합니다.Provide a mechanism for each task to respond to cancellation.
  • CancellationTokenSource.Cancel 메서드를 호출하여 취소 알림을 제공합니다.Call the CancellationTokenSource.Cancel method to provide cancellation notification.

중요

CancellationTokenSource 클래스가 IDisposable 인터페이스를 구현하므로 CancellationTokenSource 인스턴스가 완료되면 CancellationTokenSource.Dispose 메서드는 한 번만 호출되어야 합니다.The CancellationTokenSource class implements the IDisposable interface, and so the CancellationTokenSource.Dispose method should be invoked once the CancellationTokenSource instance is finished with.

자세한 내용은 동기 지원 개요를 참조하세요.For more information, see Async Support Overview.

SGen 가비지 수집기 사용Use the SGen Garbage Collector

C#과 같은 관리 언어는 가비지 수집을 사용하여 더 이상 사용되지 않는 개체에 할당된 메모리를 회수합니다.Managed languages such as C# use garbage collection to reclaim memory that is allocated to objects that are no longer in use. Xamarin 플랫폼에서 사용하는 두 개의 가비지 수집기는 다음과 같습니다.The two garbage collectors used by the Xamarin platform are:

  • SGen – 세대 가비지 수집기이며 Xamarin 플랫폼에서 기본 가비지 수집기입니다.SGen – This is a generational garbage collector and is the default garbage collector on the Xamarin platform.
  • Boehm – 보수적인 비세대 가비지 수집기입니다.Boehm – This is a conservative, non-generational garbage collector. 클래식 API를 사용하는 Xamarin.iOS 애플리케이션에 사용되는 기본 가비지 수집기입니다.It is the default garbage collector used for Xamarin.iOS applications that use the Classic API.

SGen에서는 세 가지 힙 중 하나를 활용하여 개체에 공간을 할당합니다.SGen utilizes one of three heaps to allocate space for objects:

  • Nursery – 새로운 작은 개체가 할당되는 위치입니다.The Nursery – This is where new small objects are allocated. Nursery에 공간이 없는 경우 보조 가비지 수집이 발생합니다.When the nursery runs out of space, a minor garbage collection will occur. 모든 라이브 개체가 주요 힙으로 이동됩니다.Any live objects will be moved to the major heap.
  • 주요 힙 – 장기 실행 개체가 유지되는 위치입니다.Major Heap – This is where long running objects are kept. 주요 힙에 충분한 메모리가 없는 경우 주요 가비지 수집이 발생합니다.If there is not enough memory in the major heap, then a major garbage collection will occur. 주요 가비지 수집이 충분한 메모리를 확보하지 못하는 경우 SGen은 시스템에 추가 메모리를 요청합니다.If a major garbage collection fails to free up enough memory then SGen will ask the system for more memory.
  • LOB(Large Object) 공간 – 8000바이트를 초과해야 하는 개체가 유지되는 위치입니다.Large Object Space – This is where objects that require more than 8000 bytes are kept. LOB(Large Object)는 nursery에서 시작되지 않지만 대신 이 힙에 할당됩니다.Large objects will not start out in the nursery, but instead will be allocated in this heap.

SGen의 장점 중 하나는 보조 가비지 수집을 수행하는 데 걸리는 시간이 마지막 보조 가비지 수집 이후 생성된 새로운 라이브 개체의 수에 비례한다는 점입니다.One of the advantages of SGen is that the time it takes to perform a minor garbage collection is proportional to the number of new live objects that were created since the last minor garbage collection. 그러면 이러한 보조 가비지 수집에 걸리는 시간이 주요 가비지 수집보다 적기 때문에 애플리케이션의 성능에 대한 가비지 수집의 영향이 감소합니다.This will reduce the impact of garbage collection on the performance of an application, as these minor garbage collections will take less time than a major garbage collection. 주요 가비지 수집은 계속 발생하지만 빈도가 감소합니다.Major garbage collections will still occur, but less frequently.

가비지 수집기에 대한 압력 감소Reducing Pressure on the Garbage Collector

SGen이 가비지 수집을 시작하면 메모리를 회수하는 동안 애플리케이션의 스레드를 중지합니다.When SGen starts a garbage collection, it will stop the application’s threads while it reclaims memory. 메모리를 회수하는 동안 애플리케이션은 UI가 잠깐 일시 중지되거나 끊길 수도 있습니다.While memory is being reclaimed, the application may experience a brief pause or stutter in the UI. 이 일시 중지의 허용 정도는 다음과 같은 두 가지 요인에 따라 달라집니다.How perceptible this pause is depends on two factors:

  1. 빈도 - 가비지 수집이 발생하는 빈도입니다.Frequency – How often garbage collection occurs. 컬렉션 간에 더 많은 메모리를 할당하면 가비지 수집의 빈도가 증가합니다.The frequency of garbage collections will increase as more memory is allocated between collections.
  2. 기간 – 각 개별 가비지 수집에 걸리는 시간입니다.Duration – How long each individual garbage collection will take. 수집되는 라이브 개체의 수와 거의 비례합니다.This is roughly proportional to the number of live objects that are being collected.

즉, 전체적으로 많은 개체가 할당되지만 활성 상태로 유지되지 않는 경우 여러 개의 짧은 가비지 수집이 생성됩니다.Collectively this means that if many objects are allocated but do not stay alive, there will be many short garbage collections. 반면에 새 개체가 느리게 할당되고 개체가 활성 상태인 경우 가비지 수집 횟수는 줄고 시간은 길어집니다.Conversely, if new objects are allocated slowly and the objects stay alive, there will be fewer but longer garbage collections.

가비지 수집기에 대한 압력을 줄이려면 다음과 같은 지침을 따르세요.To reduce pressure on the garbage collector, follow these guidelines:

  • 개체 풀을 사용하여 빽빽한 루프에서 가비지 수집을 방지합니다.Avoid garbage collection in tight loops by using object pools. 이 항목은 특히 게임에 관련되며 나중에 해당 개체를 만드는 데 필요합니다.This is particularly relevant for games, which need to create the majority of their objects in advance.
  • 스트림, 네트워크 연결, 큰 블록의 메모리 및 파일 등의 리소스가 더 이상 필요 없는 경우 명시적으로 릴리스합니다.Explicitly release resources such as streams, network connections, large blocks of memory, and files once they are no longer required. 자세한 내용은 IDisposable 리소스 릴리스를 참조하세요.For more information, see Release IDisposable Resources.
  • 개체를 수집할 수 있도록 이벤트 처리기가 더 이상 필요 없는 경우 다시 등록합니다.De-register event handlers once they are no longer required, to make objects collectable. 자세한 내용은 이벤트 구독 취소를 참조하세요.For more information, see Unsubscribe from Events.

애플리케이션의 크기 축소Reduce the Size of the Application

애플리케이션의 실행 파일 크기를 제공한 위치를 이해하려면 각 플랫폼에서 컴파일 프로세스를 이해해야 합니다.It's important to understand the compilation process on each platform, to understand where an application's executable size comes from:

  • iOS 애플리케이션은 ARM 어셈블리 언어로 AOT(Ahead Of Time) 컴파일됩니다.iOS applications are ahead-of-time (AOT) compiled to ARM assembly language. 적절한 링커 옵션을 사용하는 경우에만 사용하지 않는 클래스를 제거한 .NET Framework가 포함됩니다.The .NET framework is included, with unused classes being stripped out only if the appropriate linker option is enabled.
  • Android 애플리케이션은 IL(중간 언어)로 컴파일되고 MonoVM 및 JIT(just-in-time) 컴파일로 패키지됩니다.Android applications are compiled to intermediate language (IL) and packaged with MonoVM and just-in-time (JIT) compilation. 적절한 링커 옵션을 사용하는 경우에만 사용하지 않는 프레임워크 클래스를 제거합니다.Unused framework classes are stripped out only if the appropriate linker option is enabled.
  • Windows Phone 애플리케이션은 IL로 컴파일되고 기본 제공 런타임에 의해 실행됩니다.Windows Phone applications are compiled to IL and executed by the built-in runtime.

또한 애플리케이션이 제네릭을 광범위하게 사용하는 경우 기본적으로 컴파일된 버전의 제네릭 가능성이 포함되기 때문에 최종 실행 파일 크기가 더욱 증가합니다.In addition, if an application makes extensive use of generics then the final executable size will further increase since it will contain natively compiled versions of the generic possibilities.

애플리케이션의 크기를 줄이기 위해 Xamarin 플랫폼에는 빌드 도구의 일부로 링커가 포함됩니다.To help reduce the size of applications, the Xamarin platform includes a linker as part of the build tools. 기본적으로 링커는 사용되지 않고 애플리케이션의 프로젝트 옵션에서 사용할 수 있어야 합니다.By default the linker is disabled, and must be enabled in the project options for the application. 빌드 시 애플리케이션에서 실제로 사용하는 형식과 멤버를 확인하기 위해 애플리케이션의 정적 분석을 수행합니다.At build time, it will perform static analysis of the application to determine which types, and members, are actually used by the application. 그런 다음, 애플리케이션에서 사용하지 않는 형식과 메서드를 제거합니다.It will then remove any unused types and methods from the application.

다음과 같은 스크린샷에서는 Xamarin.iOS 프로젝트의 Mac용 Visual Studio에 있는 링커 옵션을 보여줍니다.The following screenshot shows the linker options in Visual Studio for Mac for a Xamarin.iOS project:

다음과 같은 스크린샷에서는 Xamarin.Android 프로젝트의 Mac용 Visual Studio에 있는 링커 옵션을 보여줍니다.The following screenshot shows the linker options in Visual Studio for Mac for a Xamarin.Android project:

링커는 다음과 같은 세 가지 설정을 제공하여 해당 동작을 제어합니다.The linker provides three different settings to control its behavior:

  • 링크하지 않음 – 사용되지 않는 형식과 메서드가 링커에 의해 제거됩니다.Don’t Link – No unused types and methods will be removed by the linker. 성능상의 이유로 디버그 빌드의 기본 설정입니다.For performance reasons, this is the default setting for debug builds.
  • 프레임워크 SDK/SDK 어셈블리만 링크 – 이 설정은 Xamarin에서 제공되는 해당 어셈블리의 크기를 감소시킵니다.Link Framework SDKs/SDK Assemblies Only – This setting will only reduce the size of those assemblies that are shipped by Xamarin. 사용자 코드가 영향을 받지 않습니다.User code will be unaffected.
  • 모든 어셈블리 링크 – SDK 어셈블리와 사용자 코드를 대상으로 하는 보다 적극적인 최적화입니다.Link All Assemblies – This is a more aggressive optimization that will target the SDK assemblies and user code. 바인딩의 경우 사용되지 않는 지원 필드를 제거하고, 각 인스턴스(또는 바인딩된 개체)를 가볍게 해서 더 적은 메모리를 사용합니다.For bindings this will remove unused backing fields and make each instance (or bound objects) lighter, consuming less memory.

모든 어셈블리 링크는 예상하지 못한 방법으로 애플리케이션을 중단할 수 있으므로 주의해서 사용해야 합니다.The Link All Assemblies should be used with caution as it may break the application in unexpected ways. 링커에 의해 수행되는 정적 분석은 필요한 일부 코드를 잘못 식별할 수 있습니다. 그 결과 너무 많은 코드를 컴파일된 애플리케이션에서 제거하게 됩니다.The static analysis that is performed by the linker may not correctly identify all of the code that is required, resulting in too much code being removed from the compiled application. 이 경우에 애플리케이션이 충돌할 때 런타임 시에만 스스로를 매니페스트합니다.This situation will manifest itself only at runtime when the application crashes. 이로 인해 링커 동작을 변경한 후에 애플리케이션을 철저히 테스트해야 합니다.Because of this it is important to thoroughly test an application after changing the linker behavior.

테스트로 인해 링커가 클래스 또는 메서드에서 잘못 제거되었음이 드러난 경우 정적으로 참조되지 않지만 다음과 같은 특성 중 하나를 사용하여 애플리케이션에 필요한 형식이나 메서드를 표시할 수 있습니다.If testing does reveal that the linker has incorrectly removed a class or method it is possible to mark types or methods that are not statically referenced but are required by the application by using one of the following attributes:

  • Xamarin.iOS.Foundation.PreserveAttribute - 이 특성은 Xamarin.iOS 프로젝트에 사용됩니다.Xamarin.iOS.Foundation.PreserveAttribute – This attribute is for Xamarin.iOS projects.
  • Android.Runtime.PreserveAttribute - 이 특성은 Xamarin.Android 프로젝트에 사용됩니다.Android.Runtime.PreserveAttribute – This attribute is for Xamarin.Android projects.

예를 들어 동적으로 시작된 형식의 기본 생성자를 유지해야 할 수 있습니다.For instance, it may be necessary to preserve the default constructors of types that are dynamically instantiated. 또한 XML 직렬화를 사용하려면 형식의 속성을 유지해야 할 수 있습니다.Also, the use of XML serialization may require that the properties of types are preserved.

자세한 내용은 iOS용 링커Android용 링커를 참조하세요.For more information, see Linker for iOS and Linker for Android.

추가 크기 감소 방법Additional Size Reduction Techniques

모바일 디바이스에 전원을 공급하는 다양한 CPU 아키텍처가 있습니다.There are a wide variety of CPU architectures that power mobile devices. 따라서 Xamarin.iOS 및 Xamarin.Android는 각 CPU 아키텍처에 대한 컴파일된 버전의 애플리케이션을 포함하는 FAT 이진 파일을 생성합니다.Therefore, Xamarin.iOS and Xamarin.Android produce fat binaries that contain a compiled version of the application for each CPU architecture. 그러면 CPU 아키텍처에 관계 없이 모바일 애플리케이션을 디바이스에서 실행할 수 있습니다.This ensures that a mobile application can run on a device regardless of the CPU architecture.

다음 단계를 사용하여 애플리케이션 실행 파일의 크기를 더 줄일 수 있습니다.The following steps can be used to further reduce the application executable size:

  • 릴리스 빌드가 생성되어야 합니다.Ensure that a Release build is produced.
  • FAT 이진 파일이 생성되지 않도록 방지하기 위해 애플리케이션을 빌드할 아키텍처의 수를 감소시킵니다.Reduce the number of architectures that the application is built for, to avoid a FAT binary being produced.
  • 보다 최적화된 실행 파일을 생성하기 위해 LLVM 컴파일러를 사용해야 합니다.Ensure that the LLVM compiler is being used, to generate a more optimized executable.
  • 애플리케이션의 관리 코드 크기를 줄입니다.Reduce the application's managed code size. 각 어셈블리에서 링커를 사용하여 이 작업을 수행할 수 있습니다(iOS 프로젝트의 경우 모두 링크 및 Android 프로젝트의 경우 모든 어셈블리 링크).This can be accomplished by enabling the linker on every assembly (Link All for iOS projects, and Link all assemblies for Android projects).

Android 앱은 각 ABI의 별도 APK로 분할될 수도 있습니다("아키텍처").Android apps can also be split into a separate APK for each ABI ("architecture"). 이 블로그 게시물에서 자세히 알아보기: Android 앱 크기를 줄이는 방법.Learn more in this blog post: How To Keep Your Android App Size Down.

이미지 리소스 최적화Optimize Image Resources

이미지는 애플리케이션에서 사용하는 가장 비싼 리소스 중 일부이며, 고해상도로 캡처되는 경우가 많습니다.Images are some of the most expensive resources that applications use, and are often captured at high resolutions. 여기에서 활발하게 세부적인 전체 이미지를 만드는 동안 해당 이미지를 표시하는 애플리케이션에는 일반적으로 이미지를 디코딩할 더 많은 CPU 사용량 및 디코딩된 이미지를 저장할 더 많은 메모리가 필요합니다.While this creates vibrant images full of detail, applications that display such images typically require more CPU usage to decode the image and more memory to store the decoded image. 표시하기 위해 더 작은 크기로 축소하는 경우 메모리에서 고해상도 이미지를 디코딩할 필요가 없습니다.It is wasteful to decode a high resolution image in memory when it will be scaled down to a smaller size for display. 대신, 예측된 디스플레이 크기에 가까운 저장된 이미지의 여러 해상도 버전을 만들어서 CPU 사용량 및 메모리 사용 공간을 줄입니다.Instead, reduce the CPU usage and memory footprint by creating multiple resolution versions of stored images that are close to the predicted display sizes. 예를 들어 목록 보기에 표시되는 이미지는 전체 화면에 표시되는 이미지보다 해상도가 더 낮을 가능성이 높습니다.For example, an image displayed in a list view should most likely be a lower resolution than an image displayed at full-screen. 또한 메모리 영향을 최소화하여 효율적으로 표시하도록 축소된 버전의 고해상도 이미지를 로드할 수 있습니다.In addition, scaled down versions of high resolution images can be loaded to efficiently display them with minimal memory impact. 자세한 내용은 효율적으로 큰 비트맵 로드를 참조하세요.For more information, see Load Large Bitmaps Efficiently.

이미지 해상도와 관계 없이 이미지 리소스를 표시하면 앱의 메모리 사용 공간이 크게 증가할 수 있습니다.Regardless of the image resolution, displaying image resources can greatly increase the app's memory footprint. 따라서 필요한 경우에만 만들어야 하며, 애플리케이션에 더 이상 필요하지 않을 경우 즉시 해제되어야 합니다.Therefore, they should only be created when required and should be released as soon as the application no longer requires them.

애플리케이션 활성화 기간 축소Reduce the Application Activation Period

모든 애플리케이션에는 정품 인증 기간이 있습니다. 애플리케이션이 시작할 때와 애플리케이션을 사용할 준비가 될 때 사이의 시간입니다.All applications have an activation period, which is the time between when the application is started and when the application is ready to use. 이 정품 인증 기간은 사용자에게 애플리케이션의 첫 번째 인상을 제공합니다. 따라서 애플리케이션이 좋은 첫 인상을 얻기 위해 정품 인증 기간 및 사용자 인식을 감소시켜야 합니다.This activation period provides users with their first impression of the application, and so it's important to reduce the activation period and the user's perception of it, in order for them to gain a favorable first impression of the application.

애플리케이션이 초기 UI를 표시하기 전에 애플리케이션이 시작되고 있음을 사용자에게 나타내는 시작 화면을 제공해야 합니다.Before an application displays its initial UI, it should provide a splash screen to indicate to the user that the application is starting. 애플리케이션이 초기 UI를 신속하게 표시하지 않는 경우 애플리케이션이 중지되지 않았음을 확인하기 위해 시작 화면을 사용하여 사용자에게 정품 인증 기간 동안 진행률을 알려야 합니다.If the application can't quickly display its initial UI, the splash screen should be used to inform the user of progress through the activation period, to offer reassurance that the application hasn't hung. 이 작업은 진행률 표시줄 또는 비슷한 컨트롤에 있을 수 있습니다.This reassurance could be a progress bar, or similar control.

정품 인증 기간 동안 애플리케이션은 정품 인증 논리를 실행합니다. 여기에는 리소스의 로드 및 처리가 포함됩니다.During the activation period, applications execute activation logic, which often includes the loading and processing of resources. 필수 리소스를 원격으로 검색하는 대신 앱 내에서 패키지되도록 하여 정품 인증 기간을 줄일 수 있습니다.The activation period can be reduced by ensuring that required resources are packaged within the app, instead of being retrieved remotely. 예를 들어 어떤 경우에는 정품 인증 기간 동안 로컬로 저장된 자리 표시자 데이터를 로드하는 것이 적절할 수 있습니다.For example, in some circumstances it may be appropriate during the activation period to load locally stored placeholder data. 그런 다음, 초기 UI가 표시되고 사용자가 해당 앱과 상호 작용할 수 있게 되면 원격 원본에서 자리 표시자 데이터를 점진적으로 바꿀 수 있습니다.Then, once the initial UI is displayed, and the user is able to interact with the app, the placeholder data can be progressively replaced from a remote source. 또한 앱의 활성화 논리는 사용자가 애플리케이션을 사용하기 시작하는 데 필요한 작업만을 수행해야 합니다.In addition, the app's activation logic should only perform work that's required to let the user start using the application. 어셈블리가 처음으로 사용될 때 로드되면 추가 어셈블리를 로드하는 작업이 지연되는 경우 도움이 될 수 있습니다.This can help if it delays loading additional assemblies, as assemblies are loaded the first time they are used.

웹 서비스 통신 축소Reduce Web Service Communication

애플리케이션에서 웹 서비스로 연결하면 애플리케이션 성능에 영향을 줄 수 있습니다.Connecting to a web service from an application can have an impact on application performance. 예를 들어 네트워크 대역폭의 사용량이 증가하면 디바이스 배터리의 사용량이 증가합니다.For example, an increased use of network bandwidth will result in an increased usage of the device's battery. 또한 사용자가 대역폭 제한 환경에서 애플리케이션을 사용할 수 있습니다.In addition, users may be using the application in a bandwidth limited environment. 따라서 애플리케이션과 웹 서비스 간에 대역폭 사용률을 제한하는 것이 좋습니다.Therefore, it's sensible to limit the bandwidth utilization between an application and a web service.

애플리케이션의 대역폭 활용을 감소시키는 한 가지 방법은 네트워크를 통해 전송하기 전에 데이터를 압축하는 것입니다.One approach to reducing an application's bandwidth utilization is to compress data before transferring it over a network. 그러나 압축 프로세스에서 CPU 사용량이 추가되면 배터리 사용량도 증가할 수 있습니다.However, the additional CPU usage from the compression process can also result in an increased battery usage. 따라서 네트워크를 통해 압축된 데이터를 이동할지를 결정하기 전에 이러한 상충 관계를 신중하게 평가해야 합니다.Therefore, this tradeoff should be carefully evaluated before deciding whether to move compressed data over a network.

고려해야 할 또 다른 문제는 애플리케이션과 웹 서비스 간에 전환되는 데이터의 형식입니다.Another issue to consider is the format of the data that moves between an application and a web service. 두 가지 기본 형식은 XML(Extensible Markup Language) 및 JSON(JavaScript Object Notation)입니다.The two primary formats are Extensible Markup Language (XML) and JavaScript Object Notation (JSON). XML은 많은 수의 서식 지정 문자를 포함하기 때문에 비교적 큰 데이터 페이로드를 생성하는 텍스트 기반 데이터 교환 형식입니다.XML is a text-based data-interchange format that produces relatively large data payloads, because it contains a large number of formatting characters. JSON은 간단한 데이터 페이로드를 생성하는 텍스트 기반 데이터 교환 형식입니다. 이로 인해 데이터를 보내고 수신할 때 대역폭 요구 사항이 감소하게 됩니다.JSON is a text-based data-interchange format that produces compact data payloads, which results in reduced bandwidth requirements when sending data and receiving data. 따라서 모바일 애플리케이션에 사용하려는 형식은 주로 JSON입니다.Therefore, JSON is often the preferred format for mobile applications.

애플리케이션과 웹 서비스 간에 데이터를 전송할 때 DTO(데이터 전송 개체)를 사용하는 것이 좋습니다.It's recommended to use data transfer objects (DTOs) when transferring data between an application and a web service. DTO에는 네트워크에서 전송할 데이터 집합이 포함되어 있습니다.A DTO contains a set of data for transferring across the network. DTO를 활용하여 단일 원격 호출에서 더 많은 데이터를 전송할 수 있습니다. 그러면 애플리케이션에서 발생시킨 원격 호출의 수를 줄일 수 있습니다.By utilizing DTOs, more data can be transmitted in a single remote call, which can help to reduce the number of remote calls made by the application. 일반적으로 더 큰 데이터 페이로드를 전송하는 원격 호출은 작은 데이터 페이로드를 전송하는 호출과 비슷한 시간을 사용합니다.Generally, a remote call carrying a larger data payload takes a similar amount of time as a call that only carries a small data payload.

캐시된 데이터를 웹 서비스에서 반복적으로 검색하지 않고 활용하여 웹 서비스에서 검색된 데이터를 로컬로 캐시해야 합니다.Data retrieved from the web service should be cached locally, with the cached data being utilized rather than repeatedly retrieved from the web service. 그러나 이 방법을 채택하면 적절한 캐싱 전략을 구현하여 로컬 캐시에 있는 데이터가 웹 서비스에서 변경되는 경우 업데이트해야 합니다.However, when adopting this approach a suitable caching strategy should also be implemented to update data in the local cache if it changes in the web service.

요약Summary

이 문서에서는 Xamarin 플랫폼을 사용하여 빌드된 애플리케이션의 성능을 높이는 기술에 대해 설명했습니다.This article described and discussed techniques for increasing the performance of applications built using the Xamarin platform. 이러한 기술은 전체적으로 CPU에서 수행하는 작업의 양과 애플리케이션에서 소비하는 메모리의 양을 크게 줄일 수 있습니다.Collectively these techniques can greatly reduce the amount of work being performed by a CPU, and the amount of memory consumed by an application.