2012-01-27 8 views
3

次のコードは、私が最適化しようとしているコードの簡略化されたバージョンです。IEnumerable実装でタスク並列ライブラリを使用して速度向上を達成する

void Main() 
{ 
    var words = new List<string> {"abcd", "wxyz", "1234"}; 

    foreach (var character in SplitItOut(words)) 
    { 
     Console.WriteLine (character); 
    } 
} 

public IEnumerable<char> SplitItOut(IEnumerable<string> words) 
{ 
    foreach (string word in words) 
    { 
     var characters = GetCharacters(word); 

     foreach (char c in characters) 
     { 
      yield return c; 
     } 
    } 
} 

char[] GetCharacters(string word) 
{ 
    Thread.Sleep(5000); 
    return word.ToCharArray(); 
} 

私は方法SplitItOut.The GetCharactersメソッドのシグネチャを変更することはできませんが呼び出すことが高価ですが、スレッドセーフです。 SplitItOutメソッドへの入力には100,000以上のエントリが含まれ、GetCharacters()メソッドへの1回の呼び出しには約200msかかる場合があります。私は無視することができる例外をスローすることもできます。結果の順序は関係ありません。

私の最初の試みでは、TPLを使用して次の実装に着手しました。これはかなり高速ですが、すべての単語を処理するまでブロックしています。

public IEnumerable<char> SplitItOut(IEnumerable<string> words) 
{ 
    Task<char[][]> tasks = Task<char[][]>.Factory.StartNew(() => 
    { 
     ConcurrentBag<char[]> taskResults = new ConcurrentBag<char[]>(); 

     Parallel.ForEach(words, 
      word => 
      { 
       taskResults.Add(GetCharacters(word)); 
      }); 

     return taskResults.ToArray(); 
    }); 

    foreach (var wordResult in tasks.Result) 
    { 
     foreach (var c in wordResult) 
     { 
      yield return c; 
     } 
    } 
} 

私はこれよりもSplitItOut()メソッドの方が優れた実装を探しています。処理時間を短縮することが私の優先事項です。

+0

を出力を置く

  • 高価なメソッドを呼び出します'GetCharacters'?私はあなたのアルゴリズムが遅い理由だと思う。 –

  • +0

    これは単なるサンプルコードです。 Thread.Sleep(5000)は、実際のGetCharacters()メソッドが呼び出すのに費用がかかることを示しています。 – Snakebyte

    +0

    それは何に縛られていますか? CPU?ディスク?ネットワーク? –

    答えて

    4

    私が正しくあなたの質問を読んでいる場合は、あなただけの言葉から文字を作成し、並列処理をスピードアップするために見ていませんあなたの列挙型は、準備ができたらすぐにをそれぞれ生成するようにしてください。あなたが現在持っている実装(と私が現在見ている他の答え)で、はすべての単語がGetCharactersに送られるまで待って、最初のものを作る前にすべての結果を返します。

    このような場合、私は自分のプロセスをプロデューサーと消費者に分割することを考えています。 プロデューサスレッドは、使用可能な単語を取り、GetCharactersを呼び出し、その結果をどこかにダンプします。 消費者は、準備ができ次第、SplitItOutの発信者に文字を送信します。実際、消費者はSplitItOutの発信者です。

    BlockingCollectionは、文字を出力する方法と結果を出力する「どこか」の両方の方法で使用できます。、これらはあなたの制約を表すので、(発信者を変更することはできません変更することはできません -

    static void Main() 
         { 
          var words = new List<string> { "abcd", "wxyz", "1234"}; 
    
          foreach (var character in SplitItOut(words)) 
          { 
           Console.WriteLine(character); 
          } 
         } 
    
    
         static char[] GetCharacters(string word) 
         { 
          Thread.Sleep(5000); 
          return word.ToCharArray(); 
         } 
    

    あなたmainGetCharactersへの変更なし:我々は分割する至っていない単語を置く場所としてConcurrentBagを使用することができます

    1. がconcurreを初期化します。高価な操作)ここで

       public static IEnumerable<char> SplitItOut(IEnumerable<string> words) 
           { 
            var source = new ConcurrentBag<string>(words); 
            var chars = new BlockingCollection<char>(); 
      
            var tasks = new[] 
              { 
               Task.Factory.StartNew(() => CharProducer(source, chars)), 
               Task.Factory.StartNew(() => CharProducer(source, chars)), 
               //add more, tweak away, or use a factory to create tasks. 
               //measure before you simply add more! 
              }; 
      
            Task.Factory.ContinueWhenAll(tasks, t => chars.CompleteAdding()); 
      
            return chars.GetConsumingEnumerable(); 
           } 
      

      、我々は4つの物事を行うためにSplitItOut方法を変更します私たちが分割したい言葉のすべてでntbag。 (補足:必要に応じて単語を列挙したい場合は、コンストラクタではなくプッシュするために新しいタスクを開始できます)

    2. 「プロデューサ」タスクを起動します。あなたはセットナンバーを始めることも、工場を使うこともできます。私はあなたが測定する前にタスククレイジーに行かないことをお勧めします。
    3. すべてのタスクが完了したら完了したことを通知してください。BlockingCollection
    4. すべてのこと欠けている生産文字のすべて(私たち自身にそれが簡単に作成し、ちょうどIEnumerable<char>ではなく、foreachの、歩留まりを返しますが、あなたが望むなら、あなたはそれを長い道のりを行うことができます)

    を「消費」私たちのプロデューサー実装です。私はそれを明確にするために、すべてのLINQのショートカットを拡大してきましたが、それは超簡単です:

     private static void CharProducer(ConcurrentBag<string> words, BlockingCollection<char> output) 
         { 
          while(!words.IsEmpty) 
          { 
           string word; 
           if(words.TryTake(out word)) 
           { 
            foreach (var c in GetCharacters(word)) 
            { 
             output.Add(c); 
            } 
           } 
          } 
         } 
    

    この単純

    1. が空でない限り(ConcurrentBag外の単語を取り - それがある場合には、タスクが実行されます!)
    2. あなたが ``のThread.sleep(5000)を持っていないのはなぜBlockingCollectionに
    +0

    有望そうです。私のオリジナルアルゴリズムでこのパターンを実装しようとして、それがどのように機能するかを見てみましょう。ありがとう。 – Snakebyte

    +0

    +1ここでTPLはどこに質問していますか? –

    +0

    @ GennadyVanin - Novosibirsk:System.Threading.Tasks.TaskがTPLの基盤を形成したので、あなたの混乱がどこにあるか分かりません。 SplitItOutがプロデューサTasksを作成する上記を参照してから、結果を待ちます。 –

    2

    私はあなたのコードをVisual Studioに組み込まれているプロファイラに渡しました。タスクのオーバーヘッドがあなたを傷つけているようです。私はTaskを削除するためにそれを少しリファクタリングして、パフォーマンスを少し改善しました。あなたの実際のアルゴリズムとデータセットがなければ、その問題が何であるか、あるいはパフォーマンスを改善できるかを正確に伝えることは難しいです。 VS PremiumまたはUltimateをお持ちの場合は、多くの手助けとなるプロファイリングツールが組み込まれています。あなたはANTSの裁判をつかむこともできます。

    気にすること:早めに最適化しないでください。コードが許容範囲内で実行されている場合は、などのものを追加しないでください。可読性と保守性を犠牲にして高速化してください。それが許容レベルに達していない場合は、それをつぶす前にプロファイルしてください。いずれの場合においても

    は、ここにあなたのアルゴリズムの私のリファクタリングです:

    public static IEnumerable<char> SplitItOut(IEnumerable<string> words) 
        { 
         var taskResults = new ConcurrentBag<char[]>(); 
    
         Parallel.ForEach(words, word => taskResults.Add(GetCharacters(word))); 
    
         return taskResults.SelectMany(wordResult => wordResult); 
        } 
    
    関連する問題