【问题标题】:Why is vertical traversal faster than horizontal traversal when it should be the opposite?为什么垂直遍历比水平遍历快,而应该相反?
【发布时间】:2020-01-26 20:19:50
【问题描述】:

所以我正在学习 scala 并行编程课程,它挑战我们使用不同的实现来实现框模糊。

其中一个是按行分块图像,另一个是按列分块图像。图像存储为(行主要顺序):

  type RGBA = Int  

  /** Image is a two-dimensional matrix of pixel values. */
  class Img(val width: Int, val height: Int, private val data: Array[RGBA]) {
    def this(w: Int, h: Int) = this(w, h, new Array(w * h))

    def apply(x: Int, y: Int): RGBA = {
      data(y * width + x)
    }

    def update(x: Int, y: Int, c: RGBA): Unit = data(y * width + x) = c
  }

这是基本模糊的实现,在所有实现中都是一样的。

  def boxBlurKernel(src: Img, x: Int, y: Int, radius: Int): RGBA = {
    val pixels = for {
      j <- (y - radius to y + radius)
      i <- (x - radius to x + radius)
      if (i > 0 && i < src.width && j > 0 && j < src.height)
    } yield src(i,j)

    val reds = pixels.map(red)
    val greens = pixels.map(green)
    val blues = pixels.map(blue)
    val alphas = pixels.map(alpha)

    val redComponent = reds.sum / pixels.size
    val greenComponent = greens.sum / pixels.size
    val blueComponent = blues.sum / pixels.size
    val alphaComponent = alphas.sum / pixels.size

    rgba(redComponent,greenComponent,blueComponent,alphaComponent)
  }

现在我们实现一个垂直模糊的实现——

def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit = {

    val imageHeight = src.height

    val xCoordinates: Seq[Int] = from until end
    val yCoordinates: Seq[Int] = 0 until imageHeight

    for {
      xCoordinate <- xCoordinates
      yCoordinate <- yCoordinates
    } yield dst.update(xCoordinate, yCoordinate, boxBlurKernel(src, xCoordinate, yCoordinate, radius))

  }

  def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit = {

    val imageWidth = src.width
    val boundaries = linspace(0, imageWidth, numTasks + 1).map(_.toInt).toScalaVector.sliding(2)
    val tasks = boundaries.toList.map { case Seq(from, end) => task {
      blur(src, dst, from, end, radius)
    }
    }
    tasks.foreach(_.join())
  }

然后我们实现水平模糊

  def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit = {
    val imageWidth = src.width

    val xCoordinates = 0 until imageWidth
    val yCoordinates = from until end

    for {
      yCoordinate <- yCoordinates
      xCoordinate <- xCoordinates
    } yield dst.update(xCoordinate, yCoordinate, boxBlurKernel(src, xCoordinate, yCoordinate, radius))
  }

def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit = {
    val imageHeight = src.height
    val boundaries = linspace(0, imageHeight, numTasks + 1).map(_.toInt).toScalaVector.sliding(2)

    boundaries.toList.map {
      case Seq(from: Int, end: Int) => task(from, end, blur(src, dst, from, end, radius))
    }.foreach(_.join())

  }

现在,由于图像以行主要格式存储,因此预计水平模糊会更有效地利用处理器缓存,并且应该比垂直模糊计时快一些。 但是,我发现相反的结果。

垂直框模糊时间 -

[info] Running (fork) scalashop.VerticalBoxBlurRunner 
fork/join blur time: 2281.5884644 ms

水平框模糊时间 -

[info] Running (fork) scalashop.HorizontalBoxBlurRunner 
fork/join blur time with number of tasks = 32: 2680.8516574 ms

我正在使用 scalameter 和 Mac OS 2.2 GHz 运行这些基准测试 task 并行原语依次返回 ForkJoinTask。

【问题讨论】:

    标签: scala parallel-processing


    【解决方案1】:

    性能取决于一系列因素,包括映像大小、内核大小、处理器架构和 JIT 编译器的效率。

    在这种情况下,boxBlurKernel 的效率非常低,因为当计算可以一次完成时,它至少使用 9 个循环。它还每次都剪裁坐标,而不是在进入循环之前剪裁边界。

    差异似乎不太可能是由于缓存造成的,因为只有一小部分代码正在将新数据读取到缓存中。循环的数量使得 JIT 编译器很难优化代码,不同之处可能仅仅是它在第二种情况下生成更高效的代码。

    使用多个任务会使事情变得复杂,因此最好使用单个任务进行测试,看看哪个版本实际上更有效。之后,在担心两个不同版本的相对效率之前,先研究算法的整体效率。

    【讨论】:

      猜你喜欢
      • 2014-02-11
      • 2016-10-22
      • 2020-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-13
      • 2014-10-03
      相关资源
      最近更新 更多