1. 並列実行

並列実行 

タスクの順序付け 

タスクの順序付けは、タスクの入力を宣言することで指定されます。実行の正確性には、正しい入力宣言が必要です。たとえば、次の2つのタスクには順序付けが指定されていません。

write := IO.write(file("/tmp/sample.txt"), "Some content.")

read := IO.read(file("/tmp/sample.txt"))

sbtは、最初にwriteを実行してからreadを実行したり、最初にreadを実行してからwriteを実行したり、readwriteを同時に実行したりすることができます。これらのタスクはファイルを共有しているため、実行は非決定的です。タスクの正しい宣言は次のようになります。

write := {
  val f = file("/tmp/sample.txt")
  IO.write(f, "Some content.")
  f
}

read := IO.read(write.value)

これにより、順序付けが確立されます。readwriteの後に実行する必要があります。また、readwriteで作成された同じファイルから読み取ることを保証しました。

実用的な制約 

注:このセクションで説明されている機能は実験的です。特に、機能のデフォルト構成は変更される可能性があります。

背景 

タスクの入力と依存関係を宣言すると、タスクが適切に順序付けられ、コードが正しく実行されます。実際には、タスクは有限のハードウェアおよびソフトウェアリソースを共有し、これらのリソースの使用を制御する必要がある場合があります。デフォルトでは、sbtは(すでに説明した順序付け制約に従って)使用可能なすべてのプロセッサを活用するために並列でタスクを実行します。また、デフォルトでは、テストを並列で実行できるように、各テストクラスが独自のタスクにマッピングされます。

sbt 0.12より前は、このプロセスに対するユーザー制御は以下に制限されていました。

  1. すべての並列実行を有効または無効にする(例:parallelExecution := false)。
  2. テストを独自のタスクにマッピングすることを有効または無効にする(例:Test / parallelExecution := false)。

(設定として公開されたことはありませんが、特定の時間に実行されるタスクの最大数も内部的に構成可能でした。)

上記で説明した2番目の構成メカニズムは、プロジェクトのすべてのテストを同じタスクで実行するか、別々のタスクで実行するかを選択するだけでした。各プロジェクトには、テストを実行するための個別のタスクがあり、全体的な実行が並列であれば、別々のプロジェクトのテストタスクを並列で実行できました。すべてのプロジェクトから1つのテストのみを実行するように実行を制限する方法はありませんでした。

構成 

sbt 0.12.0では、通常の順序付け宣言を超えてタスクの並行性を制限するための一般的なインフラストラクチャが導入されました。これらの制限には2つの部分があります。

  1. タスクは、その目的とリソース使用率を分類するためにタグ付けされます。たとえば、コンパイルタスクはTags.CompileおよびTags.CPUとしてタグ付けされる場合があります。
  2. 一連のルールは、同時に実行できるタスクを制限します。たとえば、Tags.limit(Tags.CPU, 4)は、一度に最大4つの計算負荷の高いタスクを実行できるようにします。

したがって、このシステムはタスクの適切なタグ付けと、適切なルールのセットに依存しています。

タスクのタグ付け 

一般に、タグには、タグで表されるリソースのタスクの相対的な使用率を表す重みが関連付けられています。現在、この重みは整数ですが、将来的には浮動小数点になる可能性があります。Initialize[Task[T]]は、構築されたタスクをタグ付けするための2つのメソッドtagtagwを定義します。最初のメソッドtagは、引数として指定されたタグの重みを1に固定します。2番目のメソッドtagwは、タグと重みのペアを受け入れます。たとえば、次の例では、CPUタグとCompileタグをcompileタスクに関連付けます(重み1)。

def myCompileTask = Def.task { ... } tag(Tags.CPU, Tags.Compile)

compile := myCompileTask.value

異なる重みは、タグ/重みのペアをtagwに渡すことで指定できます。

def downloadImpl = Def.task { ... } tagw(Tags.Network -> 3)

download := downloadImpl.value

制限の定義 

タスクがタグ付けされたら、concurrentRestrictions設定は、これらのタスクの重み付きタグに基づいて、同時に実行できるタスクに制限を設定します。これは必然的にグローバルなルールのセットであるため、Global /でスコープを設定する必要があります。例えば、

Global / concurrentRestrictions := Seq(
  Tags.limit(Tags.CPU, 2),
  Tags.limit(Tags.Network, 10),
  Tags.limit(Tags.Test, 1),
  Tags.limitAll( 15 )
)

この例では、以下の制限が設定されています。

  • CPUを使用するタスクの数を2以下にする
  • ネットワークを使用するタスクの数を10以下にする
  • すべてのプロジェクトで一度に1つのテストのみを実行する
  • タスクの合計数を15以下にする

これらの制限は、タスクの適切なタグ付けに依存していることに注意してください。また、制限として指定された値は、すべてのタスクが実行できるように1以上である必要があります。この条件が満たされない場合、sbtはエラーを生成します。

ほとんどのタスクは非常に短命であるため、タグ付けされません。これらのタスクには、Untaggedというラベルが自動的に割り当てられます。limitSumメソッドを使用して、これらのタスクをCPUルールに含めることができます。例えば

...
Tags.limitSum(2, Tags.CPU, Tags.Untagged)
...

制限は最初の引数であるため、タグは可変長引数として指定できることに注意してください。

もう1つの便利な関数は、Tags.exclusiveです。これは、指定されたタグを持つタスクが単独で実行される必要があることを指定します。他のタスクが実行されていない場合(排他的タグを持っている場合でも)のみ実行を開始し、完了するまで他のタスクは実行を開始できません。たとえば、タスクにカスタムタグBenchmarkをタグ付けし、そのようなタスクが単独で実行されるようにルールを設定できます。

...
Tags.exclusive(Benchmark)
...

最後に、最大の柔軟性のために、Map[Tag,Int] => Boolean型のカスタム関数を指定できます。Map[Tag,Int]は、一連のタスクの重み付きタグを表します。関数がtrueを返した場合、そのタスクセットは同時に実行できることを示します。戻り値がfalseの場合、そのタスクセットは同時に実行できません。たとえば、Tags.exclusive(Benchmark)は次と同等です。

...
Tags.customLimit { (tags: Map[Tag,Int]) =>
  val exclusive = tags.getOrElse(Benchmark, 0)
   //  the total number of tasks in the group
  val all = tags.getOrElse(Tags.All, 0)
   // if there are no exclusive tasks in this group, this rule adds no restrictions
  exclusive == 0 ||
    // If there is only one task, allow it to execute.
    all == 1
}
...

カスタム関数が従う必要があるいくつかの基本的なルールがありますが、実際に注意すべき主なルールは、タスクが1つしかない場合は、実行が許可される必要があるということです。ユーザーがタスクの実行を完全に妨げる制限を定義した場合、sbtは警告を生成し、それでもタスクを実行します。

組み込みのタグとルール 

組み込みのタグは、Tagsオブジェクトで定義されています。以下に示すすべてのタグは、このオブジェクトで修飾する必要があります。たとえば、CPUTags.CPU値を参照します。

組み込みのセマンティックタグは次のとおりです。

  • Compile - ソースをコンパイルするタスクを記述します。
  • Test - テストを実行するタスクを記述します。
  • Publish
  • Update
  • Untagged - タスクがタグを明示的に定義していない場合に自動的に追加されます。
  • All- すべてのタスクに自動的に追加されます。

組み込みのリソースタグは次のとおりです。

  • Network - タスクのネットワーク使用率を記述します。
  • Disk - タスクのファイルシステム使用率を記述します。
  • CPU - タスクの計算使用率を記述します。

現在デフォルトでタグ付けされているタスクは次のとおりです。

  • compile : Compile, CPU
  • test : Test
  • update : Update, Network
  • publish, publishLocal : Publish, Network

追加の注意事項として、デフォルトのtestタスクは、各テストクラス用に作成された各子タスクにタグを伝播します。

デフォルトのルールは、以前のバージョンのsbtと同じ動作を提供します。

Global / concurrentRestrictions := {
  val max = Runtime.getRuntime.availableProcessors
  Tags.limitAll(if(parallelExecution.value) max else 1) :: Nil
}

以前と同様に、Test / parallelExecution はテストを別々のタスクにマップするかどうかを制御します。すべてのプロジェクトで同時に実行するテストの数を制限するには、以下を使用します。

Global / concurrentRestrictions += Tags.limit(Tags.Test, 1)

カスタムタグ 

新しいタグを定義するには、Tags.Tag メソッドに String を渡します。例えば

val Custom = Tags.Tag("custom")

次に、このタグを他のタグと同様に使用します。例えば

def aImpl = Def.task { ... } tag(Custom)

aCustomTask := aImpl.value

Global / concurrentRestrictions +=
  Tags.limit(Custom, 1)

今後の作業 

これは実験的な機能であり、変更が必要であったり、さらに作業が必要な点がいくつかあります。

タスクのタグ付け 

現在、タグはそれが定義されている直近の計算にのみ適用されます。例えば、次の例では、2 番目のコンパイル定義にはタグが適用されていません。最初の計算のみがラベル付けされます。

def myCompileTask = Def.task { ... } tag(Tags.CPU, Tags.Compile)

compile := myCompileTask.value

compile := {
  val result = compile.value
  ... do some post processing ...
}

これは望ましい動作でしょうか?予想される動作でしょうか?そうでない場合、より良い代替動作は何でしょうか?

小数重み付け 

重みは現在 int ですが、小数の重みが有用な場合は double に変更できます。組み込みタスクとカスタムタスクがこの定義を共有し、有用なルールが記述できるように、重み 1 の意味の一貫した概念を維持することが重要です。

デフォルトの動作 

どのようなカスタムルールがどのようなワークロードで機能するかについてのユーザーからのフィードバックは、適切なデフォルトのタグとルールのセットを決定するのに役立ちます。

デフォルトの調整 

ルールは、名前を付けることなどで、削除または再定義が容易になるはずです。現状では、ルールを追加するか、すべてのルールを完全に再定義する必要があります。また、:= 構文を使用する場合、タグは元の定義サイトのタスクに対してのみ定義できます。

タグを削除する場合、removeTag の実装は、tag の実装から簡単な方法で導き出されるはずです。

その他の特性 

重み付きのタグのシステムは、複雑になりすぎることなく、かなり強力で柔軟であると判断されました。この選択は根本的なものではなく、必要に応じて強化、簡略化、または置き換えることができます。システムが動作する必要のある制約を記述する基本的なインターフェイスは sbt.ConcurrentRestrictions です。このインターフェイスは、タスク実行 (sbt.Execute) と基盤となるスレッドベースの並列実行サービス (java.util.concurrent.CompletionService) の間に中間スケジューリングキューを提供するために使用されます。この中間キューは、sbt.ConcurrentRestrictions の実装に従って、新しいタスクが j.u.c.CompletionService に転送されるのを制限します。詳細については、sbt.ConcurrentRestrictions API ドキュメントを参照してください。