Быстро ли компилятор / компоновщик swift удаляет неиспользуемые методы / классы / расширения и т. Д.? - PullRequest
0 голосов
/ 17 сентября 2018

У нас много кода, который можно использовать в любом приложении iOS, которое мы пишем. Такие вещи, как:

  • Пользовательские / Общие элементы управления
  • Расширения для распространенных объектов, таких как UIView, UIImage и UIViewController
  • Глобальные функции полезности
  • Глобальные константы
  • Связанные наборы файлов, которые составляют общие «функции», такие как экран выбора, который может использоваться со всем, что можно перечислить.

По причинам, не связанным с этим вопросом, мы не можем использовать статические или динамические библиотеки. Они должны быть включены в проект как фактические исходные файлы.

Существует несколько сотен этих «базовых» файлов, поэтому я занимался добавлением всех файлов в проект (ссылаясь на общую папку на диске), но только добавляя их к определенным целям по мере их использования / необходимости. .

Проблема в том, что это становится довольно утомительным, особенно когда есть наборы связанных файлов, которые все ссылаются на другие. Отслеживать их по одному - настоящая боль!

Что мне интересно, так это то, что я могу просто включить все в цель, а затем рассчитывать на компилятор для удаления всего неиспользуемого кода.

Например, у меня есть несколько методов расширения в UIView. Если я не использую их в определенной цели, компилятор исключит этот код из скомпилированного двоичного файла или он будет скомпилирован в недоступном месте, что приведет к раздутию размера кода без причины?

1 Ответ

0 голосов
/ 18 сентября 2018

Мертвые функции

SILOptimizer компилятора имеет этап устранения мертвых функций , который удаляет функции и методы, о которых известно, что они не вызываются (вместе со всеми связанными записями таблицы vtable / witness).

Чтобы в полной мере воспользоваться этим, вы захотите использовать оптимизацию всего модуля (-wmo), чтобы компилятор мог анализировать, являются ли функции internalвызывается или нет из одного и того же модуля.

Вы можете легко проверить это сами, например, используя следующий код:

class C {}
extension C {
  @inline(never) func foo() {}
  @inline(never) func bar() {}
}

@inline(never) func foo() {}
@inline(never) func bar() {}
bar()

let c = C()
c.bar()

(я используюнеофициальный @inline(never) здесь, чтобы гарантировать, что функции не будут оптимизированы путем вставки)

Если мы запустим xcrun swiftc -emit-sil -O -wmo main.swift | xcrun swift-demangle, мы увидим сгенерированный SIL:

sil_stage canonical

import Builtin
import Swift
import SwiftShims

class C {
  init()
  deinit
}

extension C {
  @inline(never) func foo()
  @inline(never) func bar()
}

@inline(never) func foo()

@inline(never) func bar()

let c: C

// c
sil_global hidden [let] @main.c : main.C : $C

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  // function_ref bar()
  %2 = function_ref @main.bar() -> () : $@convention(thin) () -> () // user: %3
  %3 = apply %2() : $@convention(thin) () -> ()
  alloc_global @main.c : main.C                  // id: %4
  %5 = global_addr @main.c : main.C : $*C        // user: %8
  %6 = alloc_ref $C                               // users: %8, %7
  debug_value %6 : $C, let, name "self", argno 1  // id: %7
  store %6 to %5 : $*C                            // id: %8
  // function_ref specialized C.bar()
  %9 = function_ref @function signature specialization <Arg[0] = Dead> of main.C.bar() -> () : $@convention(thin) () -> () // user: %10
  %10 = apply %9() : $@convention(thin) () -> ()
  %11 = integer_literal $Builtin.Int32, 0         // user: %12
  %12 = struct $Int32 (%11 : $Builtin.Int32)      // user: %13
  return %12 : $Int32                             // id: %13
} // end sil function 'main'

// C.__deallocating_deinit
sil hidden @main.C.__deallocating_deinit : $@convention(method) (@owned C) -> () {
// %0                                             // users: %3, %2, %1
bb0(%0 : $C):
  debug_value %0 : $C, let, name "self", argno 1  // id: %1
  debug_value %0 : $C, let, name "self", argno 1  // id: %2
  dealloc_ref %0 : $C                             // id: %3
  %4 = tuple ()                                   // user: %5
  return %4 : $()                                 // id: %5
} // end sil function 'main.C.__deallocating_deinit'

<b>// bar()
sil hidden [noinline] @main.bar() -> () : $@convention(thin) () -> () {
bb0:
  %0 = tuple ()                                   // user: %1
  return %0 : $()                                 // id: %1
} // end sil function 'main.bar() -> ()'

// specialized C.bar()
sil shared [noinline] @function signature specialization <Arg[0] = Dead> of main.C.bar() -> () : $@convention(thin) () -> () {
bb0:
  %0 = tuple ()                                   // user: %1
  return %0 : $()                                 // id: %1
} // end sil function 'function signature specialization <Arg[0] = Dead> of main.C.bar() -> ()'</b>

sil_vtable C {
  #C.deinit!deallocator: @main.C.__deallocating_deinit  // C.__deallocating_deinit
}

YouОтметим, что только тело и метод bar излучали свои тела (около дна).Хотя все еще есть определения для foo в верхней части SIL, они удаляются, когда SIL понижается до IR LLVM.

Мне было интересно, могли бы вы разметить / приписать методговоря: не удаляйте это!как вы можете на других языках

В настоящее время нет официального атрибута для этого, но есть подчеркнутый атрибут @_optimize(none), который говорит оптимизатору не трогать что-либо:

@_optimize(none) func foo() {}

Хотя данный атрибут подчеркнут, используйте его на свой страх и риск.


Метаданные мертвого типа

К сожалению, в настоящее время компилятор (начиная с Swift 4.2) не Покажите , чтобы иметь проход оптимизации, который исключает связанные метаданные для типов, которые, как известно, не используются.

...