Это не повлияет на производительность, и версия 1 очень предпочтительна. Даже если это повлияет на производительность, вам нужно будет продемонстрировать это в своем точном коде, прежде чем рассматривать какой-либо другой вариант, кроме версии 1. При работе с оптимизирующим компилятором не существует универсальных ответов по производительности. Выполнение чего-либо необычного «для производительности», которое вы не изучили глубоко с вашим кодом, с большой вероятностью ухудшает ситуацию. Нормальные случаи - самые оптимизированные случаи.
(Я знаю, что я преувеличиваю это. Есть определенно способы взглянуть на код и сказать «это будет ужасно неэффективно». И есть некоторые причудливые части Swift, где вещи, которые выглядят хорошо, на самом деле плохи, большинство особенно при использовании +
для объединения строк или при использовании pre-Swift4 reduce
для создания массива, но в тех случаях, когда это имеет значение, вы обнаружите его очень быстро, потому что они действительно плохо, когда они имеют значение.)
Но нам не нужно об этом догадываться. Мы можем просто спросить компилятор.
// inside.swift
import Foundation
func runme() {
var i = 0
while i < 100 {
let a = Int.random(in: 0...10)
print(a)
i += 1
}
}
// outside.swift
import Foundation
func runme() {
var i = 0
var a: Int
while i < 100 {
a = Int.random(in: 0...10)
print(a)
i += 1
}
}
Во-первых, обратите внимание, что я поместил их в функцию. Это важно Помещение их на верхний уровень делает a
глобальным в одном случае, а глобальные имеют специальную обработку, включая поточно-ориентированную инициализацию, что делает внешний случай более дорогим и сложным, чем при более обычном использовании. (Очень, очень трудно правильно протестировать микрооптимизацию таким образом, чтобы вы могли сделать общие выводы «это быстрее». Существует так много факторов.)
Второе замечание print
. Нам нужно убедиться, что вы используете a
побочным эффектом, иначе оптимизатор может удалить его полностью. print
довольно хорошо, хотя и довольно сложно. Вы также можете использовать результат для изменения глобала, но компилятор может определенно оптимизировать это гораздо более агрессивно и может устранить то, что мы хотели увидеть. (Вы действительно действительно должны протестировать этот материал на конкретном деле, о котором вы заботитесь.)
Теперь мы можем видеть, что Swift собирается делать с каждым из них, используя swiftc -O -emit-sil
. Это -O
имеет решающее значение. Так много людей пытаются проводить тестирование производительности без включения оптимизатора, и эти результаты не имеют смысла.
Так как же выглядит SIL? (Swift Intermediate Language. Это первый большой шаг к превращению вашей программы в машинный код. Если две вещи генерируют один и тот же SIL, они будут генерировать один и тот же машинный код.)
SIL немного длинный (8000 строк), поэтому я собираюсь немного его урезать. Мои комментарии в <>. Это будет немного утомительно, потому что изучение этого материала очень придирчиво. Если вы хотите пропустить это, TL-DR: нет разницы между этими двумя частями кода. Не «маленькая разница, которая не будет иметь значения». Буквально (кроме подсказки отладчику), без разницы.
// runme()
sil hidden @$S4main5runmeyyF : $@convention(thin) () -> () {
bb0:
... <define a bunch of variables and function calls> ...
<compute the random number and put it in %29>
// %19 // user: %49
bb1(%19 : $Builtin.Int64): // Preds: bb5 bb0
%20 = alloc_stack $SystemRandomNumberGenerator // users: %23, %30, %21
store %2 to %20 : $*SystemRandomNumberGenerator // id: %21
br bb2 // id: %22
bb2: // Preds: bb3 bb1
%23 = apply %6<SystemRandomNumberGenerator>(%20, %5) : $@convention(method) <τ_0_0 where τ_0_0 : RandomNumberGenerator> (@inout τ_0_0, @thin UInt.Type) -> UInt // user: %24
%24 = struct_extract %23 : $UInt, #UInt._value // users: %28, %25
%25 = builtin "cmp_ult_Int64"(%24 : $Builtin.Int64, %4 : $Builtin.Int64) : $Builtin.Int1 // user: %26
cond_br %25, bb3, bb4 // id: %26
bb3: // Preds: bb2
br bb2 // id: %27
bb4: // Preds: bb2
%28 = builtin "urem_Int64"(%24 : $Builtin.Int64, %3 : $Builtin.Int64) : $Builtin.Int64 // user: %29
%29 = struct $Int (%28 : $Builtin.Int64) // users: %42, %31
dealloc_stack %20 : $*SystemRandomNumberGenerator // id: %30
< *** Note that %29 is called "a" *** >
debug_value %29 : $Int, let, name "a" // id: %31
... < The print call. This is a lot more code than you think it is...> ...
< Add one to i and check for overflow >
%49 = builtin "sadd_with_overflow_Int64"(%19 : $Builtin.Int64, %8 : $Builtin.Int64, %13 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) // users: %51, %50
%50 = tuple_extract %49 : $(Builtin.Int64, Builtin.Int1), 0 // users: %55, %53
%51 = tuple_extract %49 : $(Builtin.Int64, Builtin.Int1), 1 // user: %52
cond_fail %51 : $Builtin.Int1 // id: %52
< Loop if i < 100 >
%53 = builtin "cmp_slt_Int64"(%50 : $Builtin.Int64, %1 : $Builtin.Int64) : $Builtin.Int1 // user: %54
cond_br %53, bb5, bb6 // id: %54
bb5: // Preds: bb4
br bb1(%50 : $Builtin.Int64) // id: %55
bb6: // Preds: bb4
%56 = tuple () // user: %57
return %56 : $() // id: %57
} // end sil function '$S4main5runmeyyF'
Внешний код практически идентичен. Чем отличается? Обратите внимание, где ***
в коде выше, отмечающем вызов debug_value
? Это отсутствует во "снаружи", потому что a
определяется как переменная функции, а не как блочная переменная.
Знаете, чего не хватает в обоих? alloc_stack
вызов для "а". Это целое число; это может поместиться в регистр. Компилятор нижнего уровня должен решать, хранится ли он в регистре или в стеке. Оптимизатор видит, что «a» не выходит за пределы этой области кода, поэтому он включает подсказку для отладчика, но на самом деле он не заботится о том, чтобы для него требовалось хранилище, даже в стеке. Он может просто взять регистр возврата Random
и переместить его в регистр параметров на print
. Решать все это должен LLVM и его оптимизатор.
Урок из всего этого заключается в том, что это буквально не имеет значения для производительности. В неясных случаях, когда это может иметь значение (например, когда a
является глобальным), версия 1 будет более эффективной, что, я полагаю, противоположно тому, что вы ожидали.