テスターですが何か?

ホビープログラマ略してHPです

BlobStorageの高速化 並列アップロード

leave a comment »

AzureTableStorageを高速化させるための連載をブログに書き、やっぱりBlobStorageについても触れないとなぁと思いこのエントリーを書きます。BlobStorageへのファイルアップロードを並列化して高速化を狙います。

ファイルアップロードの並列化というと以下の2つのパターンがありますが、今回は後者のケースを検証します。(前者はよくわからないので、今後も検証しないと思います)

  • 動画のようなサイズの大きなファイルを分割して並列アップロード
  • テキスト・画像のような大量の小さなファイルをファイル単位で並列アップロード

 

シナリオ

作成中のこちらのアプリを使用します。20個の画像をBlobStorageへアップロードする処理を行なっています。変更前は直列で20個のファイルを順にアップロードしています。ソースコードはこちらで公開しています。(BijokoyomiMobileAzure_1.2.0.zip)

直列処理ではBlobアップロード部分だけで8秒近くかかっています、もうちょっと高速化したい処理時間ですよね。

 

ソース(変更前)

BijokoyomiImageController.cs(抜粋)


        public string Index(string id)
        {
            string target = GetTargetDate(id);
            
            // 画像を抜く
            var UrlGetter = new BijokoyomiImageUrlGetter();
            var ImageUrlList = UrlGetter.GetImageUrl(target);
          
            var img = new BlobImageUploader();
            BijokoyomiImageModels Model;
            var ImageMetaDataList = new List<BijokoyomiImageModels>();

            int Counter = 0;

            // 画像と画像メタデータを登録
            foreach (var OrgUrl in ImageUrlList)
            {
                var BlobImageUrl = img.CreateBlobStorage(OrgUrl);

                Model = new BijokoyomiImageModels();
                Model.PartitionKey = "Bijokoyomi";
                Model.RowKey = target.Replace("/", string.Empty) + string.Format("{0:00}", Counter);
                Model.Date = target.Replace("/", string.Empty);
                Model.Seq = Counter;
                Model.BlobUrl = BlobImageUrl;
                Model.OriginalUrl = OrgUrl;

                ImageMetaDataList.Add(Model);

                Counter++;
            }

            return "OK";
        }

 

BlobImageUploader.cs

    public class BlobImageUploader
    {
        public string CreateBlobStorage(string imageUrl)
        {
            // 画像のダウンロード
            var wc = new WebClient();
            byte[] data = wc.DownloadData(new Uri(imageUrl));

            // ストレージ アカウントのオブジェクト 
            string connectionString =
                RoleEnvironment.GetConfigurationSettingValue("StorageConnection");
            var account = CloudStorageAccount.Parse(connectionString);

            // ブロブ クライアントの作成 
            CloudBlobClient blobClient = account.CreateCloudBlobClient();

            // ブロブ ストレージのコンテナの作成 
            CloudBlobContainer container = blobClient.GetContainerReference("bijopic");
            container.CreateIfNotExist();

            // コンテナの公開許可 
            var permissions = new BlobContainerPermissions();
            permissions.PublicAccess = BlobContainerPublicAccessType.Container;
            container.SetPermissions(permissions);

            // 画像のアップロード 
            var name = imageUrl.Split('=').Last();
            CloudBlockBlob blob = container.GetBlockBlobReference(name);
            blob.UploadByteArray(data);

            return blob.Uri.ToString();
        }
    }

 

ソース(変更前)

いきなりですが、変更後のソースです。

BijokoyomiImageController.cs(抜粋)

        public string Index(string id)
        {
            string target = GetTargetDate(id);
            
            // 画像を抜く
            var UrlGetter = new BijokoyomiImageUrlGetter();
            var ImageUrlList = UrlGetter.GetImageUrl(target);

            // オリジナルURLとBlobURLの組み合わせ
            var UrlList = new Dictionary<string, string>();

            var sw = new Stopwatch();
            sw.Start();
            Parallel.ForEach(ImageUrlList, imageUrl =>
                {
                    // 画像のダウンロード
                    var wc = new WebClient();
                    byte[] data = wc.DownloadData(new Uri(imageUrl));

                    var name = imageUrl.Split('=').Last();

                    // 画像をBlobStorageへ登録
                    var img = new BlobImageUploader();
                    var BlobUrl = img.CreateBlobStorage(name, data);

                    UrlList.Add(imageUrl, BlobUrl);
                });

            sw.Stop();

            return "Blob:" + sw.ElapsedMilliseconds; 
        }

 

BlobImageUploader.cs

    public class BlobImageUploader
    {
        private CloudBlobContainer container;

        public BlobImageUploader()
        {
            // ストレージ アカウントのオブジェクト 
            string connectionString =
                RoleEnvironment.GetConfigurationSettingValue("StorageConnection");
            var account = CloudStorageAccount.Parse(connectionString);

            // ブロブ クライアントの作成 
            CloudBlobClient blobClient = account.CreateCloudBlobClient();

            // ブロブ ストレージのコンテナの作成 
            container = blobClient.GetContainerReference("bijopic");
            container.CreateIfNotExist();

            // コンテナの公開許可 
            var permissions = new BlobContainerPermissions();
            permissions.PublicAccess = BlobContainerPublicAccessType.Container;
            container.SetPermissions(permissions);
        }

        public string CreateBlobStorage(string name, byte[] data)
        {
            CloudBlockBlob blob = container.GetBlockBlobReference(name);
            blob.UploadByteArray(data);

            return blob.Uri.ToString();
        }
    }

 

ポイント

  • Parallel.ForEachを使用してループ処理を並列化しています
  • 変更前は画像URLのリストをBlobImageUploaderクラスに渡してループ処理をしていましたが、Blob操作を行うインスタンスを1つだけ作成して複数のスレッドから呼び出しを行うと例外が発生するため(確か…)、ループ内で都度BlobImageUploaderのインスタンスを生成しています
  • ループ内でBlob操作を行うインスタンスを生成するのでオーバーヘッドが大きいように思われますが、時間がかかっているのはアップロード処理なので、アップロード処理が並列化されるメリットのほうがはるかに大きいです
  • なぜかローカルのエミュレーターで実行すると異様に遅い(1分弱)ですが、Azureへデプロイすると処理時間が8秒→2秒弱に改善されました(約4倍)

作成したソースはこちらで公開しています。「BijokoyomiMobileAzure_1.3.0.zip」です。

 

最後に

結局、AzureStorageServiceと本質的に関係ない所で高速化してしまいました(笑) System.Threading.Tasks.Parallelクラスは.NET Framework4から追加されたクラスで、CPUのメニーコア化とともに登場時は話題になりましたが、今はほとんど話題になっていない気がします。(その代わりに、今は非同期処理がホットですね)

単純にForeachをParallel.ForEachに書き換えるだけで、今まで通り正しく、しかも高速に動くケースは少ないと思います。複数スレッドから操作されると不具合を発生させるインスタンスがあるため、ループのさせ方を含めて変更する必要がありますが、書き換えた効果はループ処理を行うデータ件数に応じて高くなると思われます。今回のケースで20件のデータ登録を並列化することで、4倍の高速化が実現しました。このデータが数百件、数千件だったら…と考えるとワクワクしますよね。

非同期処理化に比べると比較的簡単にソースを書き換えることができる(と、個人的には思う)ので、処理件数が多くてパフォーマンス向上が望めない場合は、ParallelクラスやTaskクラスを使用して処理を並列化してみてはいかがでしょうか。

 

プログラムの並列化にあたって、以下のサイトの内容を参考にさせていただきました。

連載:C# 4入門 第2回 タスク並列ライブラリ
http://www.atmarkit.co.jp/fdotnet/csharp4/csharp4_02/csharp4_02_01.html

連載:C# 4入門 第3回 TaskクラスとPLINQ(Parallel LINQ)
http://www.atmarkit.co.jp/fdotnet/csharp4/csharp4_03/csharp4_03_01.html

Written by david9142

2011年10月29日 @ 1:24 AM

カテゴリー: WindowsAzure

Tagged with , ,

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。