原计划按照微批处理的思路继续完善实验,但在实践的过程中发现了自身思考上的不足,走了一些弯路。
本来应该尽快更新的,这两天解决了一些问题,在两个方面对数据处理认识有了进步
一、按行读取和按块读取的区别是什么
在按行读取的情况下,虽然想了很多办法,但性能并没有提升
1.指定每次读取100000行
在Scala中,采用微批次的方式读取大文本文件,同时输出每一行的内容,核心代码如下:
`// 使用Source对象读取文件
val source = Source.fromFile(filePath)
try {
// 创建一个迭代器来逐行读取文件,使用 buffered 来提高性能
val bufferedLines = source.getLines().buffered
val batchSize = 1000000 // 每次读取的行数(批次大小)
var lineCount = 0L
val start = System.currentTimeMillis()
println("start of read :"+currentTimestamp)
while (bufferedLines.hasNext) {
val batch = bufferedLines.take(batchSize).toList // 读取一个批次
lineCount += batch.size
// 输出批次内容
batch.foreach(println)
val end = System.currentTimeMillis()
println("end-start:") println(end-start)
println(lineCount)
} finally {
source.close()
}`
共用时1146647 毫秒,采用缓存实现批处理,方法并没有提升性能,反而耗时增加。
2.按块读取(准备和测试工作)
`object LargeFileBlockReader {
def main(args: Array[String]): Unit = {
val source = Source.fromFile( "d:\Comments.xml")
val blockSizeInMB = 200L
val blockSizeInBytes = (blockSizeInMB * 1024 * 1024).toLong
var buffer = new ArrayChar
var bytesRead = source.bufferedReader()
var blockContent = bytesRead.read(buffer,0,buffer.length)
var blockNumber = 0
while (blockContent > 0) {
blockContent = bytesRead.read(buffer,0,buffer.length)
blockNumber +=1
println(s"Total blocks read: $blockNumber")
}
source.close()
println(s"Total blocks read: $blockNumber")
} }`
22.3G的文件,每块设为200M,共120块。 按块读入内存并计数,需要87804ms,约1分半。
性能得到了大幅提升,原因分析:
(1)采用按行读取的方式,需要逐行读文件内容,并同时计数。那么即使设了内容缓存,因为仍然是从文件中逐行读入数据放入缓存,性能瓶颈是在逐行读取文件内容。因为多了一个缓存处理过程,反而增加了处理时间。
(2)采用按块读取的方式,blockSizeInMB = 200L,文件中200M的大小按指定字节数定位偏移地址,将200M数据直接按块读入内存,这是批处理的方式,性能只和磁盘读取速度相关。所需时间87804ms约等于大文件硬盘读取时间(即取决于硬盘速度,估算87804ms和磁盘读写速度之间的关系)
(3)但是按块读取也存在问题,即行的截断问题。
3.实际的按块读取(加入统计行数的代码)
`def main(args: Array[String]): Unit = {
val source = Source.fromFile( "d:\Comments.xml","UTF-8")
val blockSizeInMB = 200L
val blockSizeInBytes = (blockSizeInMB * 1024 * 1024).toLong
var buffer = new ArrayChar
var bytesRead = source.bufferedReader()
var blockBytes = bytesRead.read(buffer,0,buffer.length)
var lineNumber = 0
var blockNumber = 0
val start = System.currentTimeMillis()
println("start of read :" + start)
while (blockBytes > 0) {
println(blockBytes)
val blockContent = new String(buffer.take(blockBytes))
val lines = blockContent.split(" <row ")
lines.foreach { line =>
lineNumber += 1
}
blockBytes = bytesRead.read(buffer,0,buffer.length)
blockNumber +=1
println(s"Total blocks read: $blockNumber")
}
source.close()
val end = System.currentTimeMillis()
println("end of read :" + end)
println("used time :")
println(end-start)
println(s"Total blocks read: lineNumber")
}`
性能分析:(1)加入统计行数,即对读入缓存的数据进行处理,最简单的处理方式,分行并计数。时间141611ms,减去读取块的时间87804ms,缓存数据处理的时间为 53807ms
(2)行数为9038443行,与按行流式读取的90380326行,对应上面提到的行的截断问题,因为按块读取计算的文件偏移量,某些行会被拆分到两个块中。
二、性能瓶颈在哪里
`object LargeFileBlockLineReader {
def main(args: Array[String]): Unit = {
val source = Source.fromFile( "d:\Comments.xml","UTF-8")
val blockSizeInMB = 200L
val blockSizeInBytes = (blockSizeInMB * 1024 * 1024).toLong
var buffer = new ArrayChar
var bytesRead = source.bufferedReader()
var blockBytes = bytesRead.read(buffer,0,buffer.length)
var lineNumber = 0
var blockNumber = 0
val start = System.currentTimeMillis()
println("start of read :" + start)
while (blockBytes > 0) {
println(blockBytes)
val blockContent = new String(buffer.take(blockBytes))
val lines = blockContent.split(" <row ")
lines.foreach { line =>
print(line) //与上面程序相比,输出行的内容
lineNumber += 1
}
blockBytes = bytesRead.read(buffer,0,buffer.length)
blockNumber +=1
println(s"Total blocks read: $blockNumber")
}
source.close()
val end = System.currentTimeMillis()
println("end of read :" + end)
println("used time :")
println(end-start)
println(s"Total blocks read: lineNumber")
}
}`
性能分析,打印输出每一行并计数时间741874ms,增加了print(line),耗时增加。
在前面的分析中忽略了print(line)输出的问题,性能表现由三部分组成:输入(读文件)、处理、输出(显示在屏幕中),显示器输出本身也是系统调用外设的过程,性能较慢。
对于以上内容的总结。
(1)简化思维,从基础做起,例如,缓存数据在分析前,只是切分行并统计行数,减少复杂性且利于查看性能。
(2)了解数据本身很重要,例如,因为大文件无法直接打开,首选采用流式读取,输出前10行,看到数据组织结构,发现数据中存在标签,才能在后续处理数据时使用<row切分数据
(3)上述实现过程,可以改变块大小,改变行数,更详细的分析性能变化
下一步,将采用大数据分布式处理方法