原始类型的数字操作的 Scala 专业化
Scala specialization for numeric operation of primitive types
我写了一个函数来做简单的数学运算:
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)
{
return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence).$greater(BoxesRunTime.boxToBoolean(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence).$less(BoxesRunTime.boxToBoolean(min)) ? min : num;
}
public byte clamp$mBc$sp(byte num, byte min, byte max, Ordering<Object> evidence)
{
return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence).$greater(BoxesRunTime.boxToByte(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence).$less(BoxesRunTime.boxToByte(min)) ? min : num;
}
public char clamp$mCc$sp(char num, char min, char max, Ordering<Object> evidence)
{
return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence).$greater(BoxesRunTime.boxToCharacter(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence).$less(BoxesRunTime.boxToCharacter(min)) ? min : num;
}
有没有更好的方法在不装箱的情况下进行广义算术运算?
据我所知,没有办法做到这一点,因为 scala 标准库使用 @specialized
非常罕见,特别是 Ordering
不是专门的。
即使是,您仍然有调用 Ordering.Implicits..MODULE$.infixOrderingOps
的开销。因此,类型上下文是高级的,以帮助进行此类低级优化。
因此,我认为在没有开销的情况下进行广义算术运算的唯一方法是以某种方式生成代码。
这并不是对问题的直接回答,更像是评论,但它必须比评论长,我认为格式化会很有用。
spire project 的灵感来自要求能够对数学运算进行抽象,以便能够以最小的开销编写通用的数学代码。
该项目确实表现得非常接近 benchmarks 中的本机功能,例如上一篇文章中引用的功能。
它使用专业化和附加宏的组合来重写代码来实现这一点,this paper 中对此进行了描述,我认为它来自 Scala Days 2012。
鉴于参考基准测试的结果,我想这个项目可能会满足您的需求。
spire project 绝对是寻找高性能数值抽象的正确位置。它的所有类型类都专用于常见类型,如 long、double、float、int。
这是您使用尖顶类型类的方法:
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 的主要作者之一。
但最重要的是,即使代码是通用的,结果也非常快。
我写了一个函数来做简单的数学运算:
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)
{
return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence).$greater(BoxesRunTime.boxToBoolean(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence).$less(BoxesRunTime.boxToBoolean(min)) ? min : num;
}
public byte clamp$mBc$sp(byte num, byte min, byte max, Ordering<Object> evidence)
{
return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence).$greater(BoxesRunTime.boxToByte(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence).$less(BoxesRunTime.boxToByte(min)) ? min : num;
}
public char clamp$mCc$sp(char num, char min, char max, Ordering<Object> evidence)
{
return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence).$greater(BoxesRunTime.boxToCharacter(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence).$less(BoxesRunTime.boxToCharacter(min)) ? min : num;
}
有没有更好的方法在不装箱的情况下进行广义算术运算?
据我所知,没有办法做到这一点,因为 scala 标准库使用 @specialized
非常罕见,特别是 Ordering
不是专门的。
即使是,您仍然有调用 Ordering.Implicits..MODULE$.infixOrderingOps
的开销。因此,类型上下文是高级的,以帮助进行此类低级优化。
因此,我认为在没有开销的情况下进行广义算术运算的唯一方法是以某种方式生成代码。
这并不是对问题的直接回答,更像是评论,但它必须比评论长,我认为格式化会很有用。
spire project 的灵感来自要求能够对数学运算进行抽象,以便能够以最小的开销编写通用的数学代码。
该项目确实表现得非常接近 benchmarks 中的本机功能,例如上一篇文章中引用的功能。
它使用专业化和附加宏的组合来重写代码来实现这一点,this paper 中对此进行了描述,我认为它来自 Scala Days 2012。
鉴于参考基准测试的结果,我想这个项目可能会满足您的需求。
spire project 绝对是寻找高性能数值抽象的正确位置。它的所有类型类都专用于常见类型,如 long、double、float、int。
这是您使用尖顶类型类的方法:
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 的主要作者之一。
但最重要的是,即使代码是通用的,结果也非常快。