我有一系列多个小文件(~1-8KB),我想计算这些文件的字数。具体来说,序列I具有files: Seq[String]
,其中序列的每个字符串是每个文件的路径。我根据该序列计算总字数的方法是:
val totalWordCount = sc.union(
files.map(path => sc.textFile(path))
).flatMap(line => line.split(" "))
.map((_,1))
// I use a hash partitioner for better performance of reduceByKey
.partitionBy(new HashPartitioner(numPartitions))
.reduceByKey(_ + _)
我遇到的问题是,即使我有超过10000个小文件并使用上述技术,当我增加分区时执行时间也会增加。那是为什么?
请注意,我不能将这些小文件从头开始合并为一个输入,而是需要输入字符串序列。
答案 0 :(得分:3)
sc.textFile
未针对此案例进行优化。请记住,最佳分区大小约为100 MB,而现在,sc.union
RDD每个文件获得一个分区 - < 8k。火花开销绝对会占据你在这个范例中所做的任何事情。
你提到过#34;增加分区"在你的问题中,但我想你可能想要减少分区的数量。我不确定numPartitions
来自哪里,但这应该是大致的总数据大小/ 100MB。您的.partitionBy
步骤正在执行完全洗牌,因此原始的太多分区RDD仍然会有很多开销,但它可能会在下游执行得更好。
在这里尝试一些其他的事情:在工会上没有洗牌合并:
val optimalNPartitions = ??? // calculate total size / 100MB here
val totalWordCount = sc.union(files.map(path => sc.textFile(path)))
.flatMap(line => line.split(" "))
.coalesce(optimalNPartitions, shuffle = false) // try with shuf = true as well!
.map((_,1))
.reduceByKey(_ + _)
虽然您说您正在分区到新的散列分区程序以使reduceByKey更有效,但实际上这是错误的。
让我们来看看这两个模型。首先,你有一个:partitionBy
后跟reduceByKey
。分区步骤将对新的散列分区程序进行完全洗牌 - 所有数据都需要在网络中移动。当你调用reduce时,所有类似的键都已经在同一个地方,因此不需要进行随机播放。
其次,请忽略partitionBy
,然后致电reduceByKey
。在这个模型中,你进入reduce
没有分区,所以你必须洗牌。但是在你洗牌之前,你要在本地减少 - 如果你有“#34; dog"在一个分区上100次,您需要将("dog", 100)
而不是("dog", 1)
洗牌100次。看看我会去哪里? Reduce实际上只需要一个部分shuffle,其大小由键的稀疏性决定(如果你只有一些独特的键,很少被洗牌。如果一切都是独特的,那么一切都是洗牌的)。
显然,模型2是我们想要的。摆脱partitionBy
!