Share via


ファイル アクセスの最適化

ファイル システムに効率的にアクセスすることで、ディスクの待機時間とメモリ/CPU サイクルによるパフォーマンスの問題を回避するユニバーサル Windows プラットフォーム (UWP) アプリを作成します。

ファイルの大規模なコレクションにアクセスして、Name、FileType、Path のような一般的なプロパティ以外のプロパティ値にアクセスする場合は、QueryOptions を作成して、SetPropertyPrefetch を呼び出してアクセスします。 SetPropertyPrefetch メソッドによって、イメージ コレクションなど、項目のコレクションをファイル システムから取得して表示するアプリのパフォーマンスを大幅に向上させることができます。 次の一連の例では、複数のファイルにアクセスする方法をいくつか紹介します。

最初の例では、Windows.Storage.StorageFolder.GetFilesAsync を使用して一連のファイルの名前情報を取得します。 この例のように Name プロパティだけにアクセスすることで、この手法は高いパフォーマンスを実現しています。

StorageFolder library = Windows.Storage.KnownFolders.PicturesLibrary;
IReadOnlyList<StorageFile> files = await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate);

for (int i = 0; i < files.Count; i++)
{
    // do something with the name of each file
    string fileName = files[i].Name;
}
Dim library As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
Dim files As IReadOnlyList(Of StorageFile) =
    Await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)

For i As Integer = 0 To files.Count - 1
    ' do something with the name of each file
    Dim fileName As String = files(i).Name
Next i

2 番目の例では、Windows.Storage.StorageFolder.GetFilesAsync を使用して、各ファイルのイメージ プロパティを取得します。 この手法のパフォーマンスは高くありません。

StorageFolder library = Windows.Storage.KnownFolders.PicturesLibrary;
IReadOnlyList<StorageFile> files = await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate);
for (int i = 0; i < files.Count; i++)
{
    ImageProperties imgProps = await files[i].Properties.GetImagePropertiesAsync();

    // do something with the date the image was taken
    DateTimeOffset date = imgProps.DateTaken;
}
Dim library As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
Dim files As IReadOnlyList(Of StorageFile) = Await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)
For i As Integer = 0 To files.Count - 1
    Dim imgProps As ImageProperties =
        Await files(i).Properties.GetImagePropertiesAsync()

    ' do something with the date the image was taken
    Dim dateTaken As DateTimeOffset = imgProps.DateTaken
Next i

3 番目の例では、QueryOptions を使用して、一連のファイルの情報を取得します。 この手法のパフォーマンスは前の例よりもかなり高くなります。

// Set QueryOptions to prefetch our specific properties
var queryOptions = new Windows.Storage.Search.QueryOptions(CommonFileQuery.OrderByDate, null);
queryOptions.SetThumbnailPrefetch(ThumbnailMode.PicturesView, 100,
        ThumbnailOptions.ReturnOnlyIfCached);
queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties, 
       new string[] {"System.Size"});

StorageFileQueryResult queryResults = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions);
IReadOnlyList<StorageFile> files = await queryResults.GetFilesAsync();

foreach (var file in files)
{
    ImageProperties imageProperties = await file.Properties.GetImagePropertiesAsync();

    // Do something with the date the image was taken.
    DateTimeOffset dateTaken = imageProperties.DateTaken;

    // Performance gains increase with the number of properties that are accessed.
    IDictionary<String, object> propertyResults =
        await file.Properties.RetrievePropertiesAsync(
              new string[] {"System.Size" });

    // Get/Set extra properties here
    var systemSize = propertyResults["System.Size"];
}
' Set QueryOptions to prefetch our specific properties
Dim queryOptions = New Windows.Storage.Search.QueryOptions(CommonFileQuery.OrderByDate, Nothing)
queryOptions.SetThumbnailPrefetch(ThumbnailMode.PicturesView,
            100, Windows.Storage.FileProperties.ThumbnailOptions.ReturnOnlyIfCached)
queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties,
                                 New String() {"System.Size"})

Dim queryResults As StorageFileQueryResult = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions)
Dim files As IReadOnlyList(Of StorageFile) = Await queryResults.GetFilesAsync()


For Each file In files
    Dim imageProperties As ImageProperties = Await file.Properties.GetImagePropertiesAsync()

    ' Do something with the date the image was taken.
    Dim dateTaken As DateTimeOffset = imageProperties.DateTaken

    ' Performance gains increase with the number of properties that are accessed.
    Dim propertyResults As IDictionary(Of String, Object) =
        Await file.Properties.RetrievePropertiesAsync(New String() {"System.Size"})

    ' Get/Set extra properties here
    Dim systemSize = propertyResults("System.Size")

Next file

Windows.Storage.ApplicationData.Current.LocalFolder などの Windows.Storage オブジェクトで複数の操作を実行する場合は、対象のストレージ ソースを参照するローカル変数を作成すると、アクセスのたびに中間オブジェクトを再作成する必要がなくなります。

C# と Visual Basic におけるストリームのパフォーマンス

UWP ストリームと .NET ストリームの間のバッファリング

UWP ストリーム (Windows.Storage.Streams.IInputStreamIOutputStream など) から .NET ストリーム (System.IO.Stream) への変換が必要になるシナリオは多数存在します。 たとえば、UWP アプリを作成しており、ストリームを処理する既存の .NET コードを UWP のファイル システムで利用する場合に、これが有用です。 UWP アプリ用の .NET API には、これを可能にする .NET と UWP 間のストリーム型変換を行う拡張メソッドがあります。 詳細については、「WindowsRuntimeStreamExtensions」を参照してください。

UWP ストリームを .NET ストリームに変換する場合、実質的には、基になる UWP ストリーム用のアダプターを作成することになります。 場合によっては、UWP ストリームのメソッド呼び出しに伴うコストが実行時に発生します。 これは、特に小規模な読み取り/書き込み操作を高頻度で何度も実行するシナリオにおいて、アプリの速度に影響を及ぼす可能性があります。

アプリを高速化するために、UWP ストリーム アダプターにはデータ バッファーが含まれています。 次のコード サンプルでは、既定のバッファー サイズの UWP ストリーム アダプターを使用した、小規模な連続する読み取りを行っています。

StorageFile file = await Windows.Storage.ApplicationData.Current
    .LocalFolder.GetFileAsync("example.txt");
Windows.Storage.Streams.IInputStream windowsRuntimeStream = 
    await file.OpenReadAsync();

byte[] destinationArray = new byte[8];

// Create an adapter with the default buffer size.
using (var managedStream = windowsRuntimeStream.AsStreamForRead())
{

    // Read 8 bytes into destinationArray.
    // A larger block is actually read from the underlying 
    // windowsRuntimeStream and buffered within the adapter.
    await managedStream.ReadAsync(destinationArray, 0, 8);

    // Read 8 more bytes into destinationArray.
    // This call may complete much faster than the first call
    // because the data is buffered and no call to the 
    // underlying windowsRuntimeStream needs to be made.
    await managedStream.ReadAsync(destinationArray, 0, 8);
}
Dim file As StorageFile = Await Windows.Storage.ApplicationData.Current -
.LocalFolder.GetFileAsync("example.txt")
Dim windowsRuntimeStream As Windows.Storage.Streams.IInputStream =
    Await file.OpenReadAsync()

Dim destinationArray() As Byte = New Byte(8) {}

' Create an adapter with the default buffer size.
Dim managedStream As Stream = windowsRuntimeStream.AsStreamForRead()
Using (managedStream)

    ' Read 8 bytes into destinationArray.
    ' A larger block is actually read from the underlying 
    ' windowsRuntimeStream and buffered within the adapter.
    Await managedStream.ReadAsync(destinationArray, 0, 8)

    ' Read 8 more bytes into destinationArray.
    ' This call may complete much faster than the first call
    ' because the data is buffered and no call to the 
    ' underlying windowsRuntimeStream needs to be made.
    Await managedStream.ReadAsync(destinationArray, 0, 8)

End Using

ここに示した既定のバッファリング動作は、UWP ストリームを .NET ストリームに変換するほとんどのシナリオに対応します。 しかし、パフォーマンスを向上させるために、一部のシナリオではバッファリングの動作を調整した方がよい場合もあります。

大きなデータ セットを操作する

大きなデータ セットの読み取りまたは書き込みを行う場合、AsStreamForReadAsStreamForWriteAsStream の各拡張メソッドに大きいバッファー サイズを指定することで、読み取りまたは書き込みのスループットを向上させることができることがあります。 これによって、ストリーム アダプターに割り当てられる内部バッファーのサイズが大きくなります。 たとえば、大きなファイルから取得したストリームを XML パーサーに渡すと、パーサーが、ストリームから多数の小規模な順次読み取りを行うことがあります。 バッファーを大きくすると、基になる UWP ストリームに対する呼び出しの回数を減らし、パフォーマンスを向上させることができます。

約 80 KB を超えるバッファー サイズを設定するときは注意が必要です。ガベージ コレクターのヒープが断片化する可能性があるためです (「ガベージ コレクションのパフォーマンスの向上」を参照)。 次のコード例では、81,920 バイトのバッファーを持つマネージド ストリーム アダプターを作成しています。

// Create a stream adapter with an 80 KB buffer.
Stream managedStream = nativeStream.AsStreamForRead(bufferSize: 81920);
' Create a stream adapter with an 80 KB buffer.
Dim managedStream As Stream = nativeStream.AsStreamForRead(bufferSize:=81920)

Stream.CopyTo メソッドと CopyToAsync メソッドでも、ストリーム間のコピー用にローカル バッファーが割り当てられます。 AsStreamForRead 拡張メソッドと同様に、大規模なストリームのコピーでは、既定のバッファー サイズをオーバーライドすることによってパフォーマンスを向上させることができる場合があります。 次のコード例では、CopyToAsync 呼び出しの既定のバッファー サイズを変更しています。

MemoryStream destination = new MemoryStream();
// copies the buffer into memory using the default copy buffer
await managedStream.CopyToAsync(destination);

// Copy the buffer into memory using a 1 MB copy buffer.
await managedStream.CopyToAsync(destination, bufferSize: 1024 * 1024);
Dim destination As MemoryStream = New MemoryStream()
' copies the buffer into memory using the default copy buffer
Await managedStream.CopyToAsync(destination)

' Copy the buffer into memory using a 1 MB copy buffer.
Await managedStream.CopyToAsync(destination, bufferSize:=1024 * 1024)

この例では、先ほど推奨した 80 KB より大きい、1 MB のバッファー サイズを使用しています。 このように大きなバッファーを使用すると、非常に大きいデータ セット (数百メガバイトなど) のコピー操作のスループットを向上させることができます。 ただし、このバッファーは大きなオブジェクト ヒープ上に割り当てられるため、ガベージ コレクションのパフォーマンスが低下する可能性があります。 大きなバッファー サイズの使用は、アプリのパフォーマンスに顕著な向上が見られる場合に限定してください。

多数のストリームを同時に処理する場合は、バッファーのメモリ オーバーヘッドを削減 (または排除) することをお勧めします。 ストリーム アダプターに対して、より小さいバッファーを指定するか、bufferSize パラメーターを 0 に設定してバッファリングを完全にオフにすることもできます。 大規模な読み取りと書き込みをマネージド ストリームに対して行う場合、バッファリングしなくても十分なスループット パフォーマンスを確保できます。

待機時間の影響を受けやすい操作の実行

読み取りと書き込みの待機時間を短くする必要があり、基になる UWP ストリームから大きなブロック単位で読み取らない場合にも、バッファリングを行わないことをお勧めします。 たとえば、ネットワーク通信にストリームを使用する場合、読み取りと書き込みの待機時間を短くする必要があることがあります。

チャット アプリでは、ネットワーク インターフェイス経由でストリームを使用し、メッセージをやり取りすることがあります。 この場合、バッファーが満杯になるまで待たずに、準備ができたメッセージをすぐに送信したいと考えます。 AsStreamForReadAsStreamForWriteAsStream の各拡張メソッドを呼び出す際にバッファー サイズを 0 に設定した場合、そのアダプターでは、バッファーが割り当てられず、すべての呼び出しについて、基になる UWP ストリームが直接操作されます。