【问题标题】:Can I use Duff's Device on an array in C?我可以在 C 中的数组上使用 Duff 的设备吗?
【发布时间】:2010-04-30 21:28:13
【问题描述】:

我这里有一个循环,我想让它运行得更快。我正在传递一个大数组。我最近听说达夫的设备可以应用于这个for循环吗?有什么想法吗?

for (i = 0; i < dim; i++) {
    for (j = 0; j < dim; j++) {
        dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
    }
}

【问题讨论】:

  • 宏(?)RIDX 扩展为什么? dim 可以是什么?这段代码将在什么架构上运行,它是用什么编译器构建的?如果不知道更多关于你在做什么,就不可能以任何有意义的方式回答你的问题。 (虽然我确信这不会阻止人们发布答案)
  • RIDX 查找像素在数组中的位置。我使用的是单个数组,但 RIDX 将其设为双数组。 dim 是图像的大小。
  • 向后循环;与0 相比,与内存位置相比通常使用更少的字节码。不要使用达夫的设备。
  • 很可能你的编译器已经为你做了循环展开并且用干净自然的代码来做。无论如何,学习 Duff 的设备,因为你会学到一些关于 c 的东西,但不要像那样正确的代码。只是不要。

标签: c optimization duffs-device


【解决方案1】:

请,请不要使用达夫的设备。一千名维护程序员会感谢你的。我曾经在一家培训公司工作,有人认为在他们的 C 编程课程的前十页介绍该设备很有趣。作为一名讲师,这是不可能处理的,除非(就像编写该课程的那个人显然那样)你相信“kewl”编码。

不用说,我尽快从课程中删除了这个东西。

【讨论】:

    【解决方案2】:

    为什么要让它跑得更快?

    是否存在实际的性能问题?

    如果是这样,您是否进行了分析并发现它执行得足够频繁,因此值得优化?

    如果是这样,您可能希望以两种方式编写它,您现在使用的直接方式和使用 Duff 的设备,或者您喜欢的任何其他方式。

    此时,您将测试性能。你可能会感到惊讶。现代优化器相当不错,现代 CPU 真的很复杂,所以源代码级优化往往适得其反。 (我曾经在一个花费大量时间的循环中执行此操作,并发现收紧循环,即使在引入一些间接性的同时,也可以提高性能。您的里程几乎肯定会有所不同。)

    最后,如果 Duff 的 Device 确实更快,您必须决定性能改进是否值得采用这种直接且可优化的代码,并在下一个编译器版本中替换一个可能根本不会提高性能的维护问题。

    【讨论】:

      【解决方案3】:

      您不应该手动展开循环。如果有的话,它只会给你一个非常特定于平台的优势。所有好的编译器都可以展开循环,但甚至不能保证让代码更快,因为从主内存中读取更长的程序会占用更多的内存带宽。

      如果您希望循环快速运行,您应该确保无论 RIDX 计算什么,dst 都是按顺序访问的,因此您可以最大限度地减少缓存未命中的次数。除此之外,我看不出如何使循环更快。

      【讨论】:

      • 我不同意手动展开循环。我已经成功展开循环,它们是特定于平台的。所有处理器都讨厌分支语句。他们宁愿处理顺序数据和数学运算。循环展开有助于满足这一要求。此外,索引更新和检查语句在展开循环中的执行频率较低,从而加快了执行速度,所有这些都与平台无关。
      • 关于内存带宽的话题,傻瓜;取决于循环的内容。大多数循环都足够小,可以放入处理器的指令缓存中,因此没有额外的内存带宽,因为循环已经被缓存了。当循环包含对外部函数的调用时,内存带宽就会发挥作用。此外,清楚地说明您将循环展开到编译器的意图不会影响性能(只是不要优化空间)。
      • @Thomas Matthews:您说得对,大多数循环都适合指令缓存。你确定处理器真的讨厌分支吗?我会假设现代处理器非常擅长预测循环中的分支。不过我可能错了。
      【解决方案4】:

      Duff 的设备只是一种循环展开技术。由于可以展开任何循环,因此您可以使用 Duff 的设备。

      【讨论】:

      • +1。具体来说,Duff 的设备是一种展开循环的方法,您不知道迭代次数是否是您想要展开循环的长度的倍数。
      【解决方案5】:

      如果您能够弄清楚这一点并获得收益,那将是微不足道的,并且绝不会证明复杂性是合理的。

      你最好把精力花在一个级别上——重新考虑你的整个解决方案。也许与其复制值,不如创建一个翻译数组,并在需要时花更多时间间接查找答案(构建图像并不是一个好主意——只是试图给你一种不同的方式来看待它)。

      或者也许有一些完全不同的方法——看看你的整个问题,尝试完全抛弃你当前的方法和概念,看看是否有一些你没有考虑过的东西,因为你太依赖这个实现了。

      你的显卡可以做这些工作吗?

      从高层次上重新思考问题比您想象的要频繁得多。

      编辑: 更多地查看您的样本,看起来您正在获取图像块并将其逐个像素地复制到另一个图像中。如果是这样,几乎可以肯定有一些方法可以摆脱宏并逐字节复制,或者甚至使用块移动汇编函数,然后调整结果的边缘以匹配。

      或者我可能猜错了,但很有可能在比逐个像素更大的范围内查看它可能比展开循环更有帮助。

      【讨论】:

        【解决方案6】:

        执行语句的指令周期数

        dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
        

        将远远超过循环开销,因此展开循环在百分比基础上的帮助很小。

        【讨论】:

          【解决方案7】:

          我相信这是 Duff 设备的候选者,这取决于 RIDX() 函数的作用。但我希望您不要指望有人为您编写代码……另外,您可能希望正确地格式化您的代码,以便它实际上是可读的。

          【讨论】:

          • 具体来说,如果 RIDX 修改了它的第二个或第三个参数(在 C 中这样做,它需要一个宏),那么可能会出现问题。 Duff 的设备完全是为了省略大部分延续检查,因此需要能够预测延续检查的行为。
          【解决方案8】:

          可能,只要 dim 是 2 的幂,或者您的目标系统具有快速模数。今天学到了新东西。我在 5 年前独立发现了这个结构,并将其放入我们的 memCopy() 例程中。谁知道:)

          【讨论】:

            【解决方案9】:

            迂腐,不。 Duff 的设备用于写入硬件寄存器(因此副本的目标始终是相同的地址)。

            可以为这样的副本实现非常类似于 Duff 的设备的东西,但会有清晰度和维护成本。我会先进行分析以确保这是一个问题。我还想看看您是否可以简化索引,因为这可能使编译器能够完成展开循环的繁琐工作。

            【讨论】:

              【解决方案10】:

              如果您使用它,请确保您对其进行衡量以确定改进是真实的、重要的,并且就您的性能要求而言是必要的。我怀疑它会是。

              对于大循环,Duff 的设备处理的余数在操作中将是微不足道的一部分,而对于余数很重要的小循环,只有当你有很多这样的循环(它们自己在一个循环中)时,你才会看到好处,因为根据定义,小循环不会花费那么长时间!即使这样,编译器的优化器也可能会做得更好或更好,而不会使您的代码不可读。也有可能应用 Duff 的设备会阻止优化器应用更有效的优化,这就是为什么如果你使用它,你需要测量它。

              您可能会节省很多时间(如果有的话),但您可能已经浪费了好几次阅读这个问题的回复。

              【讨论】:

                【解决方案11】:

                Duff 的设备可能不是展开循环中的优化解决方案。

                我有一个函数向一个端口发送一个位,然后向另一个端口发送一个时钟脉冲。对于每一位,功能是:

                  if (bit == 1)
                  {
                     write to the set port.
                  }
                  else
                  {
                     write to the clear port.
                  }
                  write high clock bit.
                  write low clock bit.
                

                这被放入 Duff 的设备循环中,以及位移和位计数递增。

                我通过使用半字节值而不是位(半字节为 4 位)提高了循环的效率。 switch 语句基于半字节值。这允许在没有任何 if 语句的情况下处理 4 位,从而改进通过指令缓存(管道)的流程。

                有时 Duff 的设备可能不是最佳解决方案;但可以成为更有效解决方案的基础。

                【讨论】:

                  【解决方案12】:

                  当启用优化时,现代编译器已经为您执行循环展开,这会使 Duff 的设备过时。编译器比您更了解编译目标的最佳展开级别,您无需编写任何额外的代码来执行此操作。这在当时是一种巧妙的 hack,但如今 Duff 的设备只是一种历史奇闻,而不是一种好的编程实践。

                  【讨论】:

                    【解决方案13】:

                    最后,无论是谁调用优化,每个参与的人都需要确保它有良好的文档记录,并以尽可能自我记录的风格编写,为变量、函数等使用正确拼写有意义的名称。所以很明显,如果cmets 和代码不同步。

                    对优化的需求永远不会结束。我正在和一个研究生交谈,他破坏了 malloc()/free() 处理有史以来最大的遗传数据文件一次尝试。之后堆变得太碎片化,malloc 无法找到一块连续的 RAM 来分配给调用函数。他不得不切换到一个 malloc 只在 32k 边界上发布内存块的库。旧库占用的内存增加了 160%,运行速度慢了很多,但它完成了工作。

                    您必须小心使用 Duff 的设备和许多其他优化,以确保编译器不会将您的优化优化为模糊的损坏目标代码。当我们进入使用自动并行化工具的环境时,这将成为一个更大的问题。

                    我预计优化级别越低,未来的优化就越有可能破坏代码。我可以看到,我在设计用于在多个平台上运行的代码中丢弃换行符并将换行符放回每个平台上的打印和写入函数中的习惯将在本线程中讨论的一些事情中遇到问题。

                    -gcouger

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-12-28
                      • 2011-01-22
                      • 2011-01-27
                      • 1970-01-01
                      • 2016-08-29
                      • 1970-01-01
                      相关资源
                      最近更新 更多