我正在尝试分析20亿行(HDFS中的文本文件)。每个文件的行包含一个排序整数数组:
[1,2,3,4]
整数值可以是0到100,000。我希望在每个整数数组中重叠所有可能的组合(单向也称为1,2和2,1不是必需的)。然后减少并总计这些重叠的计数。例如:
文件:
[1,2,3,4]
[2,3,4]
最终输出:
(1,2) - 1
(1,3) - 1
(1,4) - 1
(2,3) - 2
(2,4) - 2
(3,4) - 2
我尝试过的方法是使用Apache Spark来创建一个简单的工作,它可以并行处理和减少数据块。但是我遇到的问题是内存无法保存((100,000)^ 2)/ 2选项的哈希,因此我不得不导致运行传统的map减少map,sort,shuffle,reduce local,sort,洗牌,全球减少。我知道创建组合是一个双循环所以O(n ^ 2),但是以编程方式执行此操作的最有效方法是什么,所以我可以最低限度地写入磁盘?我试图在100个节点(64gb ram / 2个核心)的集群上执行此任务2小时以及任何推荐的技术或框架。以下是我在Apache Spark和Pydoop中使用的内容。我尝试使用更多内存优化的哈希,但它们仍然是太多的内存。
import collection.mutable.HashMap
import collection.mutable.ListBuffer
def getArray(line: String):List[Int] = {
var a = line.split("\\x01")(1).split("\\x02")
var ids = new ListBuffer[Int]
for (x <- 0 to a.length - 1){
ids += Integer.parseInt(a(x).split("\\x03")(0))
}
return ids.toList
}
var textFile = sc.textFile("hdfs://data/")
val counts = textFile.mapPartitions(lines => {
val hashmap = new HashMap[(Int,Int),Int]()
lines.foreach( line => {
val array = getArray(line)
for((x,i) <- array.view.zipWithIndex){
for (j <- (i+1) to array.length - 1){
hashmap((x,array(j))) = hashmap.getOrElse((x,array(j)),0) + 1
}
}
})
hashmap.toIterator
}).reduceByKey(_ + _)
还试过PyDoop:
def mapper(_, text, writer):
columns = text.split("\x01")
slices = columns[1].split("\x02")
slice_array = []
for slice_obj in slices:
slice_id = slice_obj.split("\x03")[0]
slice_array.append(int(slice_id))
val array = getArray(line)
for (i, x) in enumerate(array):
for j in range(i+1, len(array) - 1):
write.emit((x,array[j]),1)
def reducer(key, vals, writer):
writer.emit(key, sum(map(int, vals)))
def combiner(key, vals, writer):
writer.count('combiner calls', 1)
reducer(key, vals, writer)
答案 0 :(得分:1)
我认为你的问题可以简化为字数统计,其中语料库包含最多50亿个不同的单词。
在两个代码示例中,您都试图预先计算每个分区中出现的所有项目,并在减少阶段对每个分区的计数求和。
考虑最坏情况下的内存要求,当每个分区包含所有50亿个密钥时会发生这种情况。如果我们将它表示为64位整数,则哈希表需要至少8个字节来表示每个键(作为两个32位整数)和8个字节用于计数。忽略Java / Scala哈希表的额外开销(这不是无关紧要的),您可能需要至少74千兆字节的RAM来保存地图侧哈希表:
num_keys = 100000**2 / 2
bytes_per_key = 4 + 4 + 8
bytes_per_gigabyte = 1024 **3
hashtable_size_gb = (num_keys * bytes_per_key) / (1.0 * bytes_per_gigabyte)
这里的问题是任何特定映射器的键空间都很大。但是在减速器方面做得更好:假设一个好的散列分区,每个reducer处理密钥空间的均匀分配,因此reducer只需要大约(74千兆字节/ 100台机器)〜= 740 MB每台机器来保存它们的哈希表。 / p>
在没有预聚合的情况下执行数据集的完全混乱可能是一个坏主意,因为一旦将它扩展成对,20亿行数据集可能会变得更大。
我会探索partial pre-aggregation,在这里您为地图侧哈希表选择固定大小,并在哈希表变满后将记录溢出到reducers。您可以使用不同的策略(例如LRU或随机驱逐)来从哈希表中选择要逐出的元素。最好的技术可能取决于数据集中键的分布(如果分布表现出明显的偏差,您可能会看到部分预聚合的更大好处)。
这使您可以在使用固定数量的内存时减少频繁键的数据传输量。
您还可以考虑使用磁盘支持的哈希表,该哈希表可以将块溢出到磁盘以限制其内存需求。