S3に格納された約400MBのサイズのwekaモデルがあります。これで、モデルを実行して予測を実行するレコードのセットができました。
予測を実行するために、私が試したのは、
モデルを静的オブジェクトとしてドライバーにダウンロードしてロードし、すべてのエグゼキューターにブロードキャストします。予測RDDでマップ操作を実行します。 ---->予測を実行するためのWekaのように、モデルオブジェクトを変更する必要があり、ブロードキャストには読み取り専用コピーが必要なため、機能しません。
モデルを静的オブジェクトとしてドライバーにダウンロードしてロードし、各マップ操作でエグゼキューターに送信します。 ----->動作(各マップ操作のように効率的ではありません。400MBのオブジェクトを渡します)
ドライバーでモデルをダウンロードし、各エグゼキューターにロードしてそこにキャッシュします。 (それを行う方法がわからない)
誰かが各エクゼキューターにモデルを一度ロードして、他のレコードのためにモデルを再度ロードしないようにキャッシュする方法を知っていますか?
次の2つのオプションがあります。
object WekaModel {
lazy val data = {
// initialize data here. This will only happen once per JVM process
}
}
次に、map
関数で遅延valを使用できます。 lazy val
は、各ワーカーJVMがデータの独自のインスタンスを初期化するようにします。 data
のシリアル化またはブロードキャストは実行されません。
elementsRDD.map { element =>
// use WekaModel.data here
}
利点
短所
mapPartition
だけでなくforeachPartition
(またはmap
)メソッドを使用します。これにより、パーティション全体に必要なものをすべて初期化できます。
elementsRDD.mapPartition { elements =>
val model = new WekaModel()
elements.map { element =>
// use model and element. there is a single instance of model per partition.
}
}
利点:
短所
遅延イニシャライザよりも優れているのは次のとおりです。 nullに初期化されたオブジェクトレベルのポインターを作成し、各エグゼキューターに初期化させます。初期化ブロックでは、1回限りのコードを使用できます。各処理バッチはローカル変数をリセットしますが、オブジェクトレベルの変数はリセットしないことに注意してください。
object Thing1 {
var bigObject : BigObject = null
def main(args: Array[String]) : Unit = {
val sc = <spark/scala magic here>
sc.textFile(infile).map(line => {
if (bigObject == null) {
// this takes a minute but runs just once
bigObject = new BigObject(parameters)
}
bigObject.transform(line)
})
}
}
このアプローチでは、他のアプローチのパーティションごとに1つの大きなオブジェクトではなく、エグゼキューターごとに1つの大きなオブジェクトが作成されます。
メイン関数名前空間内にvar bigObject:BigObject = nullを配置すると、動作が異なります。その場合、各パーティション(つまり、バッチ)の先頭でbigObjectコンストラクターを実行します。メモリリークがある場合、最終的にはエグゼキュータが強制終了されます。ガベージコレクションには、さらに作業が必要です。