我正在研究使用执行器服务来并行化任务的代码(想想一遍又一遍地在小数据集上完成的机器学习计算)。 我的目标是尽可能快地执行一些代码,并将结果存储在某处(总执行时间至少为100M)。
逻辑看起来像这样(它是一个简化的例子):
dbconn = new dbconn() //This is reused by all threads
for a in listOfSize1000:
for b in listofSize10:
for c in listOfSize2:
taskcompletionexecutorservice.submit(new runner(a, b, c, dbconn))
最后,调用taskcompletionexecutorservice.take()并将结果存储在" Future"在数据库中。 但是这种方法在一点之后并没有真正扩展。
所以这就是我现在正在做的事情(这是一个残酷的黑客,但我正在寻找关于如何最好地构建这个的建议):
sparkContext.parallelize(listOfSize1000).filter(a -> {
dbconn = new dbconn() //Cannot init it outsize parallelize since its not serializable
for b in listofSize10:
for c in listOfSize2:
Result r = new runner(a, b, c. dbconn))
dbconn.store(r)
return true //It serves no purpose.
}).count();
这种方法对我来说看起来效率低下,因为它并没有真正平行于最小的工作单元,尽管这项工作没有问题。对于我来说,计数并没有真正做任何事情,我添加它来触发执行。它的灵感来自于计算这里的pi示例:http://spark.apache.org/examples.html
所以有关如何更好地构建我的spark runner的任何建议,以便我可以有效地使用spark执行器?
答案 0 :(得分:1)
因此我们可以做一些事情来使这个代码更像Spark。首先是您使用filter
和count
,但实际上使用的是其中之一。函数foreach
可能更接近你想要的。
那就是说你正在创建一个数据库连接来存储结果,我们可以通过几种方式来看这个。一个是:如果数据库确实是您要用于存储的内容,则可以使用foreachPartition
或mapPartitionsWithIndex
为每个分区创建一个连接,然后执行count()
(我知道)有点难看,但从1.0.0开始不推荐使用foreachWith
。您也可以只做一个简单的map
,然后将结果保存到许多支持的输出格式中(例如saveAsSequenceFile)。
答案 1 :(得分:1)
你可以尝试另一种方法来更好地并行化它,不过价格。代码在scala中,但是python有 cartesian 方法。为简单起见,我的列表包含整数。
val rdd1000 = sc.parallelize(list1000)
val rdd10 = sc.parallelize(list10)
val rdd2 = sc.parallelize(list2)
rdd1000.cartesian(rdd10).cartesian(rdd2)
.foreachPartition((tuples: Iterator[Tuple2[Tuple2[Int, Int], Int]]) => {
dbconn =...
for (tuple <- tuples) {
val a = tuple._1._1
val b = tuple._1._2
val c = tuple._2
val r = new Result(a, b, c, dbconn)
dbconn.store(r)
}
})
在您的情况下,过滤器是转换,这是懒惰的 - 在调用时spark不会对其进行评估。只有在调用操作时才会启动该过程。 收集是操作,它会在您的示例中开始实际处理。 ForeachPartition 也是一个动作,火花马上启动它。此处需要 ForeachPartition ,因为它允许为整个数据分区打开一次连接。
对于笛卡儿来说,糟糕的事情可能是它可能意味着在群集上进行混乱,所以如果你有复杂的对象,那可能会影响性能。如果您要从外部源读取数据,可能会发生这种情况。如果你打算使用parallelize,那很好。
还有一点需要注意的是,根据群集的大小,火花可能会对您使用的数据库造成很大的压力。