views:

253

answers:

4
+9  Q: 

Slow Scala assert

We've been profiling our code recently and we've come across a few annoying hotspots. They're in the form

assert(a == b, a + " is not equal to " + b)

Because some of these asserts can be in code called a huge amount of times the string concat starts to add up. assert is defined as:

def assert(assumption : Boolean, message : Any) = ....

why isn't it defined as:

def assert(assumption : Boolean, message : => Any) = ....

That way it would evaluate lazily. Given that it's not defined that way is there an inline way of calling assert with a message param that is evaluated lazily?

Thanks

+12  A: 

Lazy evaluation has also some overhead for the function object created. If your message object is already fully constructed (a static message) this overhead is unnecessary.

The appropriate method for your use case would be sprintf-style:

assert(a == b,  "%s is not equal to %s", a, b)

As long as there is a speciaized function

assert(Boolean, String, Any, Any)

this implementation has no overhead or the cost of the var args array

assert(Boolean, String, Any*)

for the general case.

Implementing toString would be evaluated lazily, but is not readable:

assert(a == b, new { override def toString =  a + " is not equal to " + b })
Thomas Jung
Thanks Thomas. I hadn't considered the overhead of lazy evaluation. From our code the overhead of the lazy evaluation is much much lower than the string concat so I think we'll go with writing our own assert method. It's not pretty but speeds things up a lot.
Dave
A: 

Try: assert( a==b, "%s is not equals to %s".format(a,b)) The format should only be called when the assert needs the string. Format is added to RichString via implicit.

Jim Barrows
This is **wrong** for precisely the reason that the questioner has highlighted: that the assert method takes an `Any` and not a `=> Any`
oxbow_lakes
I'm pretty sure there's no magic in format so it will just get called as normal, whether a==b is true or not.
Dave
d'oh. I'm an id10t. It's obvious now.
Jim Barrows
A: 

Thomas' answer is great, but just in case you like the idea of the last answer but dislike the unreadability, you can get around it:

object LazyS {
  def apply(f: => String): AnyRef = new {
    override def toString = f
  }
}

Example:

object KnightSpeak {
  override def toString = { println("Turned into a string") ; "Ni" }
}

scala> assert(true != false , LazyS("I say " + KnightSpeak))

scala> println( LazyS("I say " + KnightSpeak) )
Turned into a string
I say Ni
Rex Kerr
+2  A: 

It is by-name, I changed it over a year ago.

http://www.scala-lang.org/node/825

Current Predef:

@elidable(ASSERTION)
def assert(assertion: Boolean, message: => Any) {
  if (!assertion)
    throw new java.lang.AssertionError("assertion failed: "+ message)
}
extempore
@extempore: Is that for 2.8 only? Or 2.7 as well?
Randall Schulz