【问题标题】:Scala specialization for numeric operation of primitive typesScala 特化基本类型的数值运算
【发布时间】:2015-11-09 00:23:58
【问题描述】:

我写了一个函数来做简单的数学运算:

def clamp(num: Double, min: Double, max: Double) =
  if (num < min) min else if (num > max) max else num

这很简单,直到我需要与 Long 类型相同的功能。我用类型参数和特化对其进行了概括:

import Ordering.Implicits._
def clamp[@specialized N: Ordering](num: N, min: N, max: N) =
  if (num < min) min else if (num > max) max else num

它有效,但我发现字节码在后台做了很多装箱和拆箱:

public boolean clamp$mZc$sp(boolean num, boolean min, boolean max, Ordering<Object> evidence$1)
{
  return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence$1).$greater(BoxesRunTime.boxToBoolean(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence$1).$less(BoxesRunTime.boxToBoolean(min)) ? min : num;
}

public byte clamp$mBc$sp(byte num, byte min, byte max, Ordering<Object> evidence$1)
{
  return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence$1).$greater(BoxesRunTime.boxToByte(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence$1).$less(BoxesRunTime.boxToByte(min)) ? min : num;
}

public char clamp$mCc$sp(char num, char min, char max, Ordering<Object> evidence$1)
{
  return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence$1).$greater(BoxesRunTime.boxToCharacter(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence$1).$less(BoxesRunTime.boxToCharacter(min)) ? min : num;
}

有没有更好的方法在不装箱的情况下进行广义算术运算?

【问题讨论】:

    标签: scala types parameters specialization


    【解决方案1】:

    spire project 绝对是寻找高性能数字抽象的正确位置。它的所有类型类都专门用于常见类型,例如 long、double、float、int。

    这是您使用 spire 类型类的方法:

    import spire.algebra._
    import spire.implicits._
    def clamp[@specialized T:Order](a: T, min: T, max: T) =
      if(a < min) min else if(a > max) max else a
    

    这是专用字节码(long 版本),使用 :javap 从 scala REPL 中提取:

    public long clamp$mJc$sp(long, long, long, spire.algebra.Order<java.lang.Object>);
        descriptor: (JJJLspire/algebra/Order;)J
        flags: ACC_PUBLIC
        Code:
          stack=5, locals=8, args_size=5
             0: aload         7
             2: lload_1
             3: lload_3
             4: invokeinterface #96,  5           // InterfaceMethod spire/algebra/Order.lt$mcJ$sp:(JJ)Z
             9: ifeq          16
            12: lload_3
            13: goto          35
            16: aload         7
            18: lload_1
            19: lload         5
            21: invokeinterface #99,  5           // InterfaceMethod spire/algebra/Order.gt$mcJ$sp:(JJ)Z
            26: ifeq          34
            29: lload         5
            31: goto          35
            34: lload_1
            35: lreturn
    

    如您所见,它调用的是 spire.algebra.Order 的 gt 方法的长专用版本,因此不涉及装箱。

    您还可以注意到,从运算符()到类型类方法调用的转换并没有出现在代码中。这背后的机制相当复杂。请参阅 Erik Osheim 的 blog post,他是 spire 的主要作者之一。

    但最重要的是,即使代码是通用的,结果也非常快。

    【讨论】:

      【解决方案2】:

      这并不是问题的直接答案,更多的是评论,但它必须比评论更长,我认为格式化会很有用。

      spire project 的灵感来自要求能够抽象数学运算以便能够以最小的开销编写通用的数学代码。

      该项目的性能显然与 benchmarks 中的原生函数非常接近,例如上一篇文章中提到的那个。

      它通过结合专业化和额外的宏来重写代码来实现这一点,这在this paper 中有描述,我认为它来自 Scala Days 2012。

      鉴于参考基准的结果,我想这个项目可能会满足您的需求。

      【讨论】:

        【解决方案3】:

        据我所知,没有办法做到这一点,因为 scala 标准库使用 @specialized 非常罕见,尤其是 Ordering 不是专门的。

        即使是这样,您仍然需要调用Ordering.Implicits..MODULE$.infixOrderingOps。因此,类型上下文是高级别的,以帮助进行这种低级优化。

        因此,我看到的在没有开销的情况下进行广义算术运算的唯一方法是以某种方式生成代码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-10-05
          • 1970-01-01
          • 1970-01-01
          • 2011-11-13
          • 2019-11-17
          • 2017-01-20
          • 2017-04-07
          相关资源
          最近更新 更多