I am new to sequences, so I may have done something (more or less) terribly wrong, but I have a questiion:
I have written two functions:
fun isPrimeNumber1(number: Int): Boolean {
if (number <= 1) return false
for (divider in 2 .. number / 2) {
if ( number % divider == 0 ) return false
}
return true
}
and
fun isPrimeNumber2(number: Int): Boolean {
if (number <= 1) return false
return !(2 .. number / 2).asSequence().map { it }.any { number % it == 0 }
}
Now I am running a test written in kotest
, in which both functions recieve Int.MAX_VALUE
as number
.
class MyTestPrime : FunSpec({
context("Prime numbers") {
test("Should return true if number is prime (1st fun)") {
isPrimeNumber1(Int.MAX_VALUE) shouldBe true
}
test("Should return true if number is prime (2nd fun)") {
isPrimeNumber2(Int.MAX_VALUE) shouldBe true
}
}
})
Execution time of the isPrimeNumber1()
function is around 3,5 seconds, and for the 2nd function isPrimeNumber2()
- around 8,5 seconds.
Why is that so? Am I missing something about sequences? Or maybe my code does its purpose in a correct but very unoptimal way?
It is expected. The variant with sequence creates an
Iterator
object, and calls.hasNext()
and.next()
functions for each element.Since an
Iterator
works with objects and not primitives, all yourint
s are boxed viaInteger::valueOf
calls. (Note:.map { it }
step is redundant).I ran both functions via Java Flight Recorder in IntelliJ Idea, and we can see that the sequence variant causes many more function calls compared to the other variant.
isPrimeNumber1:
isPrimeNumber2:
As you can see, the
isPrimeNumber2
variant causes many more functions to be called under the hood, and therefore is impacted by their overhead.Another way of inspecting it is to decompile both functions' bytecode to Java. It can give you better insight on what is going on under the hood. Here's both of the functions decompiled (again using IntelliJ):
Final Notes: As others have mentioned, to get meaningful performance measurements, you need to use a tool like
jmh
. However, as a rule of thumb, simpler language constructs, such as a regular for/while loop over a sequence, tend to have less overhead due to the lower level of abstraction they offer.