【问题标题】:Complicated code for obvious operations用于明显操作的复杂代码
【发布时间】:2011-04-20 12:08:51
【问题描述】:

有时,主要出于优化目的,非常简单的操作被实现为复杂而笨拙的代码。

一个例子是这个整数初始化函数:

void assign( int* arg )
{
    __asm__ __volatile__ ( "mov %%eax, %0" : "=m" (*arg));
}

然后:

int a;
assign ( &a );

但其实我不明白为什么会这样写……

你有没有看到任何有真正理由这样做的例子?

【问题讨论】:

    标签: c++ optimization inline-assembly


    【解决方案1】:

    让编译器为您优化。这种“优化”不可能有任何帮助……永远!

    【讨论】:

    • +1 是一项出色的运动,直截了当。 :-)
    【解决方案2】:

    在您的示例中,我认为这是错误假设的结果,即在汇编中编写代码会自动更快。

    问题是写这篇文章的人不明白为什么程序集有时可以运行得更快。也就是说,您比编译器更了解您正在尝试做什么,并且有时可以使用这些知识来编写性能更高的较低级别的代码,而无需假设编译器会这样做。

    在简单的变量赋值的情况下,我严重怀疑它是否成立,并且代码可能执行得更慢,因为它具有管理堆栈上的赋值函数的额外开销。请注意,它不会明显变慢,这里的主要成本是可读性和可维护性较差的代码。

    这是一个教科书示例,说明为什么在不了解为什么是优化的情况下不应该实施优化。

    【讨论】:

    • 整体写得不错,但缺少 asm 最重要​​的用途——使用 C++ 没有符号表示的 CPU 指令。没有指令可以在 C++ 中进行高度并行操作,也没有原子操作、内存缓存控制等。除非您正在编写一个非常高性能的程序(例如笔记本电脑的实时高清视频、实时衍生品定价),否则它只是您可能会使用 asm 来执行此类其他无法访问的指令,如果幸运的话,该环境将在非标准支持标头中提供大多数这些功能。
    • +1 让我想起了我年轻时计算 t 状态的记忆。关于组装总是更快的假设是错误的。我的猜测是,对于给出的示例,它可能会更慢。
    • @user433534:这根本不是真的,也许很久以前,但现在几乎所有东西都是作为编译器内在实现的(在大多数新的/最新的编译器中),并行性是用 openmp 解决的, 带有 Interlocked* 的原子操作等。真正的问题是寄存器分配可能真的很差,或者某些真正特定于处理器的东西还不是内在形式(比如说一些 amd 字符串操作指令)
    【解决方案3】:

    似乎汇编代码的意图是确保每次都完成对 *arg int 位置的分配 - 防止(故意)编译器在这方面进行任何优化。

    通常volatile 关键字在 C++(和 C...)中用于告诉编译器该值不应保存在寄存器中(例如)并从该寄存器重用(优化以获得值更快),因为它可以异步(通过外部模块、汇编程序、中断等...)进行更改。

    例如,在函数中

      int a = 36;
      g(a);
      a = 21;
      f(a);
    

    在这种情况下,编译器知道变量 a 是函数的本地变量,不会在函数外部修改(例如,a 上的指针不提供给任何调用) .它可以使用处理器寄存器来存储和使用a 变量。

    总之,该 ASM 指令似乎被注入到 C++ 代码中,以便对该变量执行一些优化。

    【讨论】:

    • 好点子——你经常会在代码中发现一些东西,这些东西是为了解决缺失的语言能力(或在那个编译器中缺失的)而被纠正的。根据@rusw1 正在查看的代码的年龄,这可能是发生的事情。
    • 其实很多编译器会完全优化掉变量“a”,直接调用g(36); f(21);
    • @Larry:如果他们这样做了,你也可以说他们已经优化了a 以存储在与第一个整数参数相同的位置(无论是堆栈还是寄存器)调用约定 ;-)
    • +1 如果代码是由优秀的程序员编写的,这是一个很好的观点。但是我也希望一个好的程序员能说出她/他为什么首先求助于汇编程序。如果没有这个评论,警钟就会开始响起,我的感觉是示例中的代码是假的。
    • @Kate,看看代码的年龄是聪明的。如果它曾经有正当理由以这种方式编写,那么很有可能它不再这样做了。
    【解决方案4】:

    您给出的示例似乎有缺陷,因为 assign() 函数可能比直接分配变量要慢,原因是使用参数调用函数涉及堆栈使用,而仅说 int a = x 容易无需堆栈即可编译为高效代码。

    我从使用汇编程序中受益的唯一一次是手动优化编译器生成的汇编程序输出,那是在处理器速度通常在单兆赫兹范围内的日子。算法优化往往会带来更好的投资回报,因为您可以获得数量级的改进而不是小倍数。正如其他人已经说过的那样,您使用汇编程序的唯一其他时间是编译器或语言没有做您需要做的事情。对于 C 和 C++,这种情况已经很少见了。

    很可能有人炫耀他们知道如何编写一些琐碎的汇编代码,从而使下一个程序员的工作变得更加困难,并且可能是为了保护自己的工作而采取的半途而废的措施。对于给出的示例,代码令人困惑,可能比本机 C 慢,可移植性较差,并且可能应该被删除。当然,如果我在任何现代 C 代码中看到任何内联汇编器,我希望有大量的 cmets 解释为什么它是绝对必要的。

    【讨论】:

      【解决方案5】:

      虽然在汇编中写一些东西有几个合理的理由,但根据我的经验,这些都是不常见的实际原因。在我能够研究基本原理的地方,它们归结为:

      时代:代码写得太早了,它是处理那个时代的编译器最合理的选择。通常,大约在 1990 年之前是合理的,恕我直言。

      控制狂:一些程序员有trust issues with the compiler,但不倾向于调查它的实际行为。

      误解:一个令人惊讶的普遍和持久的神话是,用汇编语言编写的任何东西本质上都会比用“笨拙”的编译器编写更有效的代码——尽管它所有神秘的函数进入/退出代码等。当然,有一些编译器配得上这个声誉

      要“酷”:当时间和金钱都不是因素时,有什么比一些有男子气概的、最好是高深莫测的汇编语言更好的方法来支撑程序员显着提高的激素水平?

      【讨论】:

      • 关于您的第二点,我曾经在为非技术主管制作东西的实习中使用了 HC12 的 imagecraft 编译器。链接器不起作用(即使对于 1 文件程序);我必须编写一个穷人版本的变量来将我的变量放在正确的位置并将代码放在正确的位置。当我发现无法正确编译的逻辑条件测试时,我最终退出了使用编译器。我买不起另一个编译器,也没有时间让 GCC 链运行,所以我写了大约 2500 行汇编来做一个简单的命令/控制。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-06
      • 2022-01-13
      • 2016-01-04
      相关资源
      最近更新 更多