Does "extends AnyVal" on extension-method implicit classes actually help? Benchmarks?

647 views
Skip to first unread message

Richard Bradley

unread,
Jun 29, 2015, 10:22:48 AM6/29/15
to scala...@googlegroups.com
Hi,

Are there any performance benchmarks available for adding "extends AnyVal" to "extension method" implicit classes?


I was curious to see what effect this had, so I tried to run some quick tests. Although the bytecode certainly looks like it will avoid the wrapper allocation, I didn't find any significant difference in actual memory usage by "hot" code between an extension method with "extends AnyVal" and one without. Could it be that the JVM is already eliminating the wrapper class allocation?

If this decoration doesn't have measurable performance effects in practice, I'd like to be able to not bother adding it to my extension method classes, as it's distracting. (As well as adding this unwanted side-effect: http://stackoverflow.com/questions/17944356/value-classes-introduce-unwanted-public-methods )


Thanks,


Rich

Dennis Haupt

unread,
Jun 29, 2015, 10:24:01 AM6/29/15
to Richard Bradley, scala-user
how did you test it?

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Richard Bradley

unread,
Jun 29, 2015, 10:40:23 AM6/29/15
to scala...@googlegroups.com, richard.brad...@gmail.com
On Monday, June 29, 2015 at 3:24:01 PM UTC+1, Dennis Haupt wrote:
how did you test it?

I wrote a short program which called some extension methods in a tight loop.
I ran this program a few times with the extension method class marked "extends AnyVal" and a few times without it.
I monitored speed and memory usage.

I found that the "extends AnyVal" version was very slightly slower to run, and used almost exactly the same amount of heap memory.

I don't think my testing was particularly rigorous, so I was hoping to find some more reliable results from this mailing list.

Thanks,


Rich

 

Dennis Haupt

unread,
Jun 29, 2015, 10:43:32 AM6/29/15
to Richard Bradley, scala-user
did you use a profiler? just monitoring the cpu/memory with jconsole & friends are not precise enough for this

--

Richard Bradley

unread,
Jun 29, 2015, 10:46:50 AM6/29/15
to scala...@googlegroups.com, richard.brad...@gmail.com
I was hoping to see benchmarks and discussion on the performance benefits of value-typed extension method classes, rather than get into a discussion about my preliminary testing.
I did not claim that it was accurate or precise, merely that it hadn't immediately confirmed a large performance gain from the "extends AnyVal" decoration.

Rex Kerr

unread,
Jun 29, 2015, 4:39:53 PM6/29/15
to Richard Bradley, scala-user
It matters.  Here's a REPL session demonstrating this (key points bolded):

scala> val th = new ichi.bench.Thyme
th: ichi.bench.Thyme = ichi.bench.Thyme@2e26c3a1

scala> implicit class FooString(s: String) { def foo = 42 - s.length }
defined class FooString

scala> implicit class BarString(private val s: String) extends AnyVal { def bar = 42 - s.length }
defined class BarString

scala> val ss = Array.tabulate(1024)(i => i.toString)
ss: Array[String] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...
scala> th.pbenchOff(){
    var s,i = 0; while (i < ss.length) { s += ss(i).foo; i += 1 }; s
  }{
    var s,i = 0; while(i < ss.length) { s += ss(i).bar; i += 1 }; s
  }
Benchmark comparison (in 673.4 ms)
Significantly different (p ~= 0)
  Time ratio:    0.50595   95% CI 0.48581 - 0.52608   (n=20)
    First     2.801 us   95% CI 2.722 us - 2.881 us
    Second    1.417 us   95% CI 1.378 us - 1.457 us
res0: Int = 40022


Note that in 2.11 you can make the val private.

  --Rex


Richard Bradley

unread,
Jun 30, 2015, 4:55:49 AM6/30/15
to scala...@googlegroups.com, richard.brad...@gmail.com
Thanks, that's very helpful.

I have been able to tweak my benchmark so that it sometimes shows a speed improvement. I think the previous version didn't return the accumulated value, so might have been suffering from the JIT optimising away some of the code.

I'm not seeing a very strong or consistent effect though.

Here's a benchmark which occasionally passes, but usually says "Not significantly different". Can you see what I've done wrong? Or is it just that the effect is very slight?



import ImplicitWrappersAnyValPerfSpec._
import org.specs2.mutable.Specification
import scala.xml.Node

class ImplicitWrappersAnyValPerfSpec
extends Specification {

"implicit extension method wrappers" should {
"run faster with 'extends AnyVal'" in {


val th = new ichi.bench.Thyme

      val inputs = (1 to 100000).map( i => <a><b><c>{i}</c></b></a> ).toArray

val (a, bf) = th.benchOffPair()({
var acc = 0
inputs.foreach { xml =>
acc += xml.childWithPath("b", "c").text.toInt
}
acc
}, ftitle = "Without extends AnyVal")({
var acc = 0
inputs.foreach { xml =>
acc += xml.childWithPathWithAnyVal("b", "c").text.toInt
}
acc
}, htitle = "With extends AnyVal")

val result = bf.toString()

println(result)

result must contain("Significantly different")
val rc = bf.foldChangeRuntime
rc.rdiff.mean must beLessThan(1.0)
}
}
}

object ImplicitWrappersAnyValPerfSpec {

implicit class AddChildHelpers(private val xml: Node) {
def childWithPath(elementNames: String*): Node = child(xml, elementNames: _*)
}

implicit class AddChildHelpersWithAnyVal(private val xml: Node) extends AnyVal {
def childWithPathWithAnyVal(elementNames: String*): Node = child(xml, elementNames: _*)
}

/**
* Walks down a chain of expected XML element names from the given Node.
*
* If at any stage there is not a unique child element with the next expected
* element name, then an IllegalArgumentException is thrown
*/
def child(
xml: Node,
elementNames: String*
): Node = {
elementNames.foldLeft(xml) { (parent, childName) =>
val matches = parent \ childName
require (matches.length == 1,
s"Invalid XML. Expected one '$childName' element; found: $parent")

matches.head
}
}
}

> Note that in 2.11 you can make the val private.

Thanks, good point. I've added a note to that SO question.


Thanks,


Rich

Rex Kerr

unread,
Jun 30, 2015, 5:00:22 PM6/30/15
to Richard Bradley, scala-user
It's not likely to matter much there because the methods you're calling are not lightweight.  It's usually only one or two nanoseconds' difference per call--a big deal when you're doing integer math, but hard to detect and not worth caring about when you're performing tree-based string parsing.

  --Rex

John Ky

unread,
Jun 25, 2016, 9:10:38 PM6/25/16
to scala-user, richard.brad...@gmail.com

Hi All,

I was curiously whether or not escape analysis eliminates the need to use AnyVal for implicit classes.

Cheers,

-John

Rex Kerr

unread,
Jun 25, 2016, 11:40:49 PM6/25/16
to John Ky, scala-user, Richard Bradley
Not completely.  The JVM generally can't elide all of the intialization and allocation logic.  It generally can elide all of the forwarding of methods to each other.  So with AnyVal you typically have no overhead, whereas without you generally have some modest overhead.

  --Rex
Reply all
Reply to author
Forward
0 new messages