!= null ПРОТИВ ?.пусть { } производительность для неизменяемых переменных в kotlin

#kotlin #null-check

#kotlin #нулевая проверка

Вопрос:

Насколько я понимаю, большое преимущество ?.let{} over != null заключается в том, что оно гарантирует, что изменяемое значение не изменяется внутри блока.

Однако, в случае неизменяемой переменной есть ли разница в производительности?

Например, у меня есть простой метод

 private fun test() {
    val x: String? = ""

    if (x != null) {
        print("test")
    }

    x?.let {
        print("test")
    }

}
  

Когда я вижу результирующий байт-код Kotlin, кажется, что для let в нем гораздо больше кода.

Для случая x != null:

     LINENUMBER 8 L1
   L2
    LINENUMBER 9 L2
    LDC "test"
    ASTORE 2
   L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/Object;)V
   L4
   L5
  

Для случая x?.let { } это:

     LINENUMBER 12 L5
    ALOAD 1
    ASTORE 2
   L6
   L7
    ALOAD 2
    ASTORE 3
   L8
    ICONST_0
    ISTORE 4
   L9
    LINENUMBER 13 L9
    LDC "test"
    ASTORE 5
   L10
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 5
    INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/Object;)V
   L11
   L12
    LINENUMBER 14 L12
   L13
    NOP
   L14
    LINENUMBER 12 L14
   L15
    NOP
   L16
  

Если я декомпилирую в java, то результирующий код выглядит похожим, и для let назначается еще одна переменная (любопытно, что переменная int имеет значение false)

Для x != null:

   String var2 = "test";
  System.out.print(var2);
  

Для x?.let { }

   int var4 = false;
  String var5 = "test";
  System.out.print(var5);
  

В конце концов, мой вопрос: отличается ли производительность между let и != для неизменяемых переменных?

Комментарии:

1. Это случай микрооптимизации — разница настолько мала, что вам будет трудно даже измерить ее. Просто используйте все, что более читаемо в контексте.

2. @Pawel: Согласен. Большие выигрыши в производительности достигаются за счет отказа от ненужных: синхронизации / потоковой передачи, алгоритмической сложности, ввода / вывода сети / файла / базы данных, временных объектов и (в меньшей степени) доступа к основной памяти. Оптимизация здесь не уменьшает ни одного из них, поэтому на практике это никогда не будет иметь никакого практического значения. На самом деле, JVM, вероятно, все равно оптимизирует его.

3. Я просто вижу слишком много строк в декомпилированной версии, но я не могу прочитать, что это такое, вот почему мне было интересно!

Ответ №1:

Эти два утверждения на самом деле не эквивалентны. Результат, очевидно, одинаков в ваших двух примерах, но let является функцией области видимости и, следовательно, делает немного больше, чем простое управление потоком.

Из связанной документации:

Стандартная библиотека Kotlin содержит несколько функций, единственной целью которых является выполнение блока кода в контексте объекта. Когда вы вызываете такую функцию для объекта с предоставленным лямбда-выражением, она формирует временную область видимости.

Например, даже если вы не используете it в своем примере, этот контекст с it-переменной все равно создается, и это влечет за собой некоторые накладные расходы.

Однако, как указывали другие, я думаю, что это тот случай, когда в данном случае лучше оптимизировать код для удобства чтения, а не для скорости. В тех случаях, когда let позволяет использовать более читаемый код (что обычно имеет место в IMO), это больший выигрыш, чем незначительный прирост производительности, который вы можете получить от использования if вместо этого.

Комментарии:

1. стоит отметить let , что это встроенная функция, что означает, что она вставляет как саму себя, так и лямбда-вызов. Если бы это было не так, возможно, были бы значительные накладные расходы на использование let вместо простого if . К счастью, все функции области видимости в Kotlin inline