【问题标题】:What is the performance impact of using the type class pattern in Scala在 Scala 中使用类型类模式对性能有何影响
【发布时间】:2012-02-17 11:31:21
【问题描述】:

我目前正在广泛使用类型类模式作为我的代码中与性能相关的部分。我至少找出了两个潜在的低效率来源。

  1. 隐式参数随消息调用一起传递。我不知道这是否真的发生。也许 scalac 可以简单地在使用它们的地方插入隐式参数并将它们从方法签名中删除。在您手动插入隐式参数的情况下,这可能是不可能的,因为它们可能仅在运行时被解析。 在传递隐式参数方面应用了哪些优化

  2. 如果类型类实例由def 提供(与val 相反),则必须在每次调用“类型分类方法”时重新创建对象。 JVM 可能会解决这个问题,这可能会优化对象创建。 scalac 也可以通过重用这些对象来解决这个问题。 在创建隐式参数对象方面应用了哪些优化?

当然,在应用类型类模式时可能还有其他低效率的来源。请告诉我他们的情况。

【问题讨论】:

    标签: scala optimization implicit scalac


    【解决方案1】:

    如果你真正关心编写超高性能代码(你可能认为你这样做但非常错误)那么类型类将导致一些疼痛的原因如下:

    • 许多额外的虚拟方法调用
    • 可能的基元装箱(例如,如果将 scalaz 的类型类用于幺半群等)
    • 通过 def 创建对象是必要的,因为函数无法参数化
    • 创建对象以访问“拉皮条”方法

    在运行时,JVM 可能会优化一些错误的创建(例如创建一个 MA 只是为了调用 <*>),但 scalac 并没有多大帮助。您可以通过编译一些使用类型类并使用-Xprint:icode 作为参数的代码来简单地看到这一点。

    这是一个例子:

    import scalaz._; import Scalaz._
    object TC {
      def main(args: Array[String]) {
        println((args(0).parseInt.liftFailNel |@| args(1).parseInt.liftFailNel)(_ |+| _))
      }
    }
    

    这是代码:

    final object TC extends java.lang.Object with ScalaObject {
      def main(args: Array[java.lang.String]): Unit = scala.this.Predef.println(scalaz.this.Scalaz.ValidationMA(scalaz.this.Scalaz.StringTo(args.apply(0)).parseInt().liftFailNel()).|@|(scalaz.this.Scalaz.StringTo(args.apply(1)).parseInt().liftFailNel()).apply({
      (new anonymous class TC$$anonfun$main$1(): Function2)
    }, scalaz.this.Functor.ValidationFunctor(), scalaz.this.Apply.ValidationApply(scalaz.this.Semigroup.NonEmptyListSemigroup())));
    def this(): object TC = {
      TC.super.this();
      ()
    }
    };
    @SerialVersionUID(0) final <synthetic> class TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2 extends scala.runtime.AbstractFunction0 with Serializable {
      final def apply(): Int = TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2.this.v1$1;
      final <bridge> def apply(): java.lang.Object = scala.Int.box(TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2.this.apply());
      <synthetic> <paramaccessor> private[this] val v1$1: Int = _;
      def this($outer: anonymous class TC$$anonfun$main$1, v1$1: Int): anonymous class TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2 = {
        TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2.this.v1$1 = v1$1;
        TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2.super.this();
        ()
      }
    };
    @SerialVersionUID(0) final <synthetic> class TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1 extends scala.runtime.AbstractFunction0$mcI$sp with Serializable {
      final def apply(): Int = TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1.this.apply$mcI$sp();
      <specialized> def apply$mcI$sp(): Int = TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1.this.v2$1;
      final <bridge> def apply(): java.lang.Object = scala.Int.box(TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1.this.apply());
      <synthetic> <paramaccessor> private[this] val v2$1: Int = _;
      def this($outer: anonymous class TC$$anonfun$main$1, v2$1: Int): anonymous class TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1 = {
        TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1.this.v2$1 = v2$1;
       TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1.super.this();
      ()
      }
    };
    @SerialVersionUID(0) final <synthetic> class TC$$anonfun$main$1 extends scala.runtime.AbstractFunction2$mcIII$sp with Serializable {
      final def apply(x$1: Int, x$2: Int): Int = TC$$anonfun$main$1.this.apply$mcIII$sp(x$1, x$2);
      <specialized> def apply$mcIII$sp(v1$1: Int, v2$1: Int): Int = scala.Int.unbox(scalaz.this.Scalaz.mkIdentity({
    (new anonymous class TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$2(TC$$anonfun$main$1.this, v1$1): Function0)
    }).|+|({
        (new anonymous class TC$$anonfun$main$1$$anonfun$apply$mcIII$sp$1(TC$$anonfun$main$1.this, v2$1): Function0)
    }, scalaz.this.Semigroup.IntSemigroup()));
    final <bridge> def apply(v1: java.lang.Object, v2: java.lang.Object): java.lang.Object = scala.Int.box(TC$$anonfun$main$1.this.apply(scala.Int.unbox(v1), scala.Int.unbox(v2)));
      def this(): anonymous class TC$$anonfun$main$1 = {
        TC$$anonfun$main$1.super.this();
        ()
       }
     }
    

    }

    您可以看到这里正在进行大量的对象创建

    【讨论】:

    • 所以我从您的回答中读到的一个建议可能是用自己的专业版本替换我确实在使用的scalaz.Monoid?虽然专业化似乎非常有问题......即使Numeric似乎也不是专业化的。
    • 我会避开专业化。如果我站在你的立场上,我会希望非常、非常确定我确实需要从代码中挤出每一滴性能。是什么让你确信你会这样做?如果事情必须接近金属,那么我会说你必须回到可变集合、命令式代码和 while 循环。真的没有其他答案了
    • 它并不一定要尽可能快。但我不想承受拳击对性能的影响。我目前正在使用原始数组(不改变它们)。关于这个问题,可以保持代码非常通用以应用于大型问题空间(我什至使用代数环作为抽象)。目前我不想在性能(比如拳击受到 10 倍的打击)和抽象之间做出决定;这不是一个容易的选择,我想知道我能走多远。
    • 作为评论:您真的认为摆脱拳击是“从代码中挤出最后一滴性能”吗?拳击是一个相当大的下降。
    • 这类事情的通用答案是先以可维护、直观、灵活的方式编写它,然后profile你的程序,看看真正的痛点是什么。 然后开始优化。这里有一堆额外的对象创建吗?是的。那会比C慢吗?是的。但它也比原始 C 代码更易于维护、更直观、更灵活。
    猜你喜欢
    • 2011-09-16
    • 2017-10-21
    • 2014-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-04
    • 1970-01-01
    相关资源
    最近更新 更多