为什么标准迭代器比 Kotlin 中的序列更快?

dzi*_*iki 1 optimization sequence kotlin kotest

我是序列的新手,所以我可能做了一些(或多或少)非常错误的事情,但我有一个问题:

我写了两个函数:

fun isPrimeNumber1(number: Int): Boolean {
    if (number <= 1) return false
    for (divider in 2 .. number / 2) {
        if ( number % divider == 0 ) return false
    }
    return true
}
Run Code Online (Sandbox Code Playgroud)

fun isPrimeNumber2(number: Int): Boolean {
    if (number <= 1) return false
    return !(2 .. number / 2).asSequence().map { it }.any { number % it == 0 }
}
Run Code Online (Sandbox Code Playgroud)

现在我正在运行一个用 编写的测试kotest,其中两个函数都接收Int.MAX_VALUEnumber.

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
        }
    }
})
Run Code Online (Sandbox Code Playgroud)

isPrimeNumber1()函数的执行时间约为 3.5 秒,第二个函数的执行时间isPrimeNumber2()约为 8.5 秒。

为什么呢?我是否遗漏了一些关于序列的信息?或者也许我的代码以正确但非常不理想的方式完成了它的目的?

Utk*_*mir 6

这是预期的。带有序列的变体创建一个Iterator对象,并为每个元素调用.hasNext().next()函数。

由于 anIterator与对象而不是基元一起工作,因此您所有的ints 都通过Integer::valueOf调用进行装箱。(注意:.map { it }步骤是多余的)。

我通过 IntelliJ Idea 中的 Java Flight Recorder 运行了这两个函数,我们可以看到,与其他变体相比,序列变体导致了更多的函数调用。

isPrimeNumber1:

isPrimeNumber1

isPrimeNumber2:

isPrimeNumber2

如您所见,该isPrimeNumber2变体导致在后台调用更多函数,因此受到其开销的影响。

检查它的另一种方法是将两个函数的字节码反编译为 Java。它可以让您更好地了解幕后发生的事情。这是反编译的两个函数(再次使用 IntelliJ):

private static final boolean isPrimeNumber1(int number) {
  if (number <= 1) {
    return false;
  } else {
    int divider = 2;
    int var2 = number / 2;
    if (divider <= var2) {
      while (true) {
        if (number % divider == 0) {
          return false;
        }

        if (divider == var2) {
          break;
        }

        ++divider;
      }
    }

    return true;
  }
}

private static final boolean isPrimeNumber2(int number) {
  if (number <= 1) {
    return false;
  } else {
    byte var1 = 2;
    Sequence $this$any$iv =
        SequencesKt.map(
            CollectionsKt.asSequence((Iterable) (new IntRange(var1, number / 2))),
            (Function1) null.INSTANCE);
    int $i$f$any = false;
    Iterator var3 = $this$any$iv.iterator();

    boolean var10000;
    while (true) {
      if (var3.hasNext()) {
        Object element$iv = var3.next();
        int it = ((Number) element$iv).intValue();
        int var6 = false;
        if (number % it != 0) {
          continue;
        }

        var10000 = true;
        break;
      }

      var10000 = false;
      break;
    }

    return !var10000;
  } 
}
Run Code Online (Sandbox Code Playgroud)

最后说明:正如其他人提到的,要获得有意义的性能测量,您需要使用类似jmh. 然而,根据经验,更简单的语言结构,例如一个序列上的常规 for/while 循环,由于它们提供的抽象级别较低,因此往往具有较少的开销。