【问题标题】:Divide and Get Remainder at the same time?同时除法和取余数?
【发布时间】:2011-04-23 03:28:41
【问题描述】:

显然,x86(可能还有很多其他指令集)将除法运算的商和余数放在单独的寄存器中。

现在,我们或许可以相信编译器会优化这样的代码,以便只使用一次除法调用:

( x / 6 )
( x % 6 )

他们可能会这样做。不过,是否有任何语言(或库,但主要是寻找语言)支持同时给出除法和取模结果?如果是,它们是什么,语法是什么样的?

【问题讨论】:

  • 您的代码 sn-p 不是可以以这种方式优化的示例......
  • 我刚刚意识到我输入了错误的代码 sn-p。更新了。
  • 大家的反应很好。当其中许多是有效答案时,我只能选择一个作为“答案”。

标签: x86 modulo divide


【解决方案1】:

C 有div and ldiv。这些是否为商和余数生成单独的指令将取决于您特定的标准库实现以及编译器和优化设置。从 C99 开始,您还可以使用 lldiv 来获取更大的数字。

【讨论】:

  • 令人惊讶的是为什么这不是被接受的答案——它完全符合所要求的。
  • 有趣的是,在 4.8 中,div 没有单独实现模数:stackoverflow.com/questions/4361979/…
  • 继续并接受了这个答案。我知道这里仍然有很多有效的答案,所以很难说一个比其他的“更正确”,但 C 是谈论这些事情的一个很好的起点。
  • 不要在当前的编译器中使用它,尤其是除以常数时:它不会优化。参见godbolt.org/g/ydL27q:div(var, 10) 编译成实际的函数调用,div 库实现没有除数是常量10 的信息。所以它不能使用a multiplicative inverse。即使使用运行时变量除数,您也会在 x86 上获得更大的代码大小和非内联函数调用。
  • 我确实见过一个 div() 函数调用经过优化,可以从单个 DIV 指令中获得两个结果,其中单独的 / 和 % 语句有效地运行了整个计算两次(我不记得哪个编译器,虽然它是一个嵌入式平台)。如果x 是volatile,您的结果可能会因完全不同的原因而改变。同样,在针对/围绕它进行优化之前,始终使用您的特定用例测试特定于实现的行为。
【解决方案2】:

Python 可以。

>>> divmod(9, 4)
(2, 1)

这很奇怪,因为 Python 是一种高级语言。

Ruby 也是如此:

11.divmod(3) #=> [3, 2]

* 编辑 *

应该注意,这些运算符的目的可能不是尽可能高效地完成工作,更有可能是出于正确性/可移植性的原因而存在这些功能。

对于那些感兴趣的人,我相信this is the code 整数 divmod 的 Python 实现:

static enum divmod_result
i_divmod(register long x, register long y,
     long *p_xdivy, long *p_xmody)
{
long xdivy, xmody;

if (y == 0) {
    PyErr_SetString(PyExc_ZeroDivisionError,
                    "integer division or modulo by zero");
    return DIVMOD_ERROR;
}
/* (-sys.maxint-1)/-1 is the only overflow case. */
if (y == -1 && UNARY_NEG_WOULD_OVERFLOW(x))
    return DIVMOD_OVERFLOW;
xdivy = x / y;
/* xdiv*y can overflow on platforms where x/y gives floor(x/y)
 * for x and y with differing signs. (This is unusual
 * behaviour, and C99 prohibits it, but it's allowed by C89;
 * for an example of overflow, take x = LONG_MIN, y = 5 or x =
 * LONG_MAX, y = -5.)  However, x - xdivy*y is always
 * representable as a long, since it lies strictly between
 * -abs(y) and abs(y).  We add casts to avoid intermediate
 * overflow.
 */
xmody = (long)(x - (unsigned long)xdivy * y);
/* If the signs of x and y differ, and the remainder is non-0,
 * C89 doesn't define whether xdivy is now the floor or the
 * ceiling of the infinitely precise quotient.  We want the floor,
 * and we have it iff the remainder's sign matches y's.
 */
if (xmody && ((y ^ xmody) < 0) /* i.e. and signs differ */) {
    xmody += y;
    --xdivy;
    assert(xmody && ((y ^ xmody) >= 0));
}
*p_xdivy = xdivy;
*p_xmody = xmody;
return DIVMOD_OK;
}

【讨论】:

  • divmod 是否只运行一项操作?这个函数背后的代码是什么?
  • 打败我。 divmod() 是 Python 中的内置函数。
  • @BrunoLM 我敢打赌divmod 只是单独执行这两个操作并打包结果,但没有提供证据。
  • @BrunoLM:VM 调用原生函数,我希望它执行原生 div 指令。
  • @Russell:呵呵;我实际上用错了我的潜在赌注!我的意思是,我不认为它试图使用任何低级“技巧”来提高操作效率,而只是为开发人员节省一些击键的一种方式。 :-P
【解决方案3】:

在 C#/.NET 中,您有 Math.DivRem: http://msdn.microsoft.com/en-us/library/system.math.divrem.aspx

但根据this thread 的说法,这并不是什么优化。

【讨论】:

    【解决方案4】:

    在 Java(从 1.5 开始)中,BigDecimal 类的操作 divideAndRemainder 返回一个包含 2 个元素的数组,其中包含除法的结果和除法的余数。

    BigDecimal bDecimal = ...
    BigDecimal[] result = bDecimal.divideAndRemainder(new BigDecimal(60));
    

    Javadoc:https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#divideAndRemainder-java.math.BigDecimal-

    【讨论】:

      【解决方案5】:
      【解决方案6】:

      .NET 框架有Math.DivRem:

      int mod, div = Math.DivRem(11, 3, out mod);
      // mod = 2, div = 3
      

      虽然,DivRem 只是这样的包装:

      int div = x / y;
      int mod = x % y;
      

      (我不知道抖动是否可以/确实将这类事情优化为一条指令。)

      【讨论】:

        【解决方案7】:

        正如 Stringer Bell 提到的那样,DivRem 其中is not optimized 最高支持 .NET 3.5。

        在 .NET 4.0 it uses NGen.

        我使用Math.DivRem 得到的结果(调试;发布 = ~11000ms)

        11863
        11820
        11881
        11859
        11854
        

        我使用 MyDivRem 得到的结果(调试;发布 = ~11000ms)

        29177
        29214
        29472
        29277
        29196
        

        针对 x86 的项目。


        Math.DivRem 用法示例

        int mod1;
        int div1 = Math.DivRem(4, 2, out mod1);
        

        方法签名

        DivRem(Int32, Int32, Int32&) : Int32
        DivRem(Int64, Int64, Int64&) : Int64
        

        .NET 4.0 代码

        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        public static int DivRem(int a, int b, out int result)
        {
            result = a % b;
            return (a / b);
        }
        

        .NET 4.0 IL

        .custom instance void System.Runtime.TargetedPatchingOptOutAttribute::.ctor(string) = { string('Performance critical to inline across NGen image boundaries') }
        .maxstack 8
        L_0000: ldarg.2 
        L_0001: ldarg.0 
        L_0002: ldarg.1 
        L_0003: rem 
        L_0004: stind.i4 
        L_0005: ldarg.0 
        L_0006: ldarg.1 
        L_0007: div 
        L_0008: ret 
        

        MSDN Reference

        【讨论】:

        • 这个答案有点误导,因为跳出来的时间似乎表明 Math.DivRem 在 .Net 4.0 中得到了优化,但正如你注意到的一点,它实际上不是完全优化。事实上,在我的测试中,Math.DivRem() 在所有版本的 .Net 上都比单纯的 div 和 mod 操作稍微慢一些。换句话说,它根本没有优化。
        • 这是因为这是基准调试模式,因此与调用已编译的库函数相比,您自己代码中的任何内容都将变得糟糕。它确实提到“发布”时间大致相同,这很重要。 (但我认为在这种情况下“优化”意味着“不比让编译器CSEx/y 和x%y 在开放编码版本中更糟糕,而在.NET3.5 中它实际上可能更糟?) /跨度>
        【解决方案8】:

        FWIW,Haskell 同时拥有divMod 和quotRem,后者直接对应于机器指令(根据Integral operators quot vs. div),而divMod 可能没有。

        【讨论】:

          【解决方案9】:
              int result,rest;
              _asm
              {
                  xor edx, edx // pone edx a cero; edx = 0
                  mov eax, result// eax = 2AF0
                  mov ecx, radix // ecx = 4
                  div ecx
                  mov val, eax
                  mov rest, edx
              }
          

          【讨论】:

          • 编译器已经可以做这个优化了。以这种方式使用 MSVC 笨拙的坏内联汇编只会强制进行一些存储/重新加载往返。此外,您正在进行无符号除法,因此变量应该是unsigned,而不是int。此外,切勿将 div 用于 2 的已知幂,例如 4。使用 shr/and 获得商和余数。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-07
          • 2016-09-11
          • 1970-01-01
          • 2012-01-08
          • 2011-08-01
          • 1970-01-01
          相关资源
          最近更新 更多