Будет ли структура в Swift вызывать проблемы с памятью, если ее много обходить? - PullRequest
0 голосов
/ 25 февраля 2019

В Swift structs - это типы значений.Если у меня есть структура, которая содержит большие данные (гипотетически), и я передаю структуру множеству различных функций, будет ли структура дублироваться каждый раз?Если я вызову это одновременно, то потребление памяти будет высоким, верно?

Ответы [ 3 ]

0 голосов
/ 25 февраля 2019

Это действительно зависит от двух основных факторов: количества раз, которое ваша структура передается, и как долго структуры сохраняются (иначе они также быстро очищаются ARC?).

Общий объем потребления памяти можно рассчитать с помощью: mem_usage = count * struct_size

, где count - это общее количество структур, которые «живы» в любой данный момент.Вы должны сами сделать суждение, если структуры остаются живыми или быстро очищаются.

0 голосов
/ 25 февраля 2019

Теоретически могут возникнуть проблемы с памятью, если вы передадите очень большие struct s, что приведет к их копированию.Несколько предостережений / наблюдений:

  1. На практике это редко является проблемой, потому что мы часто используем собственные «расширяемые» свойства Swift, такие как String, Array, Set, Dictionary, Data и т. Д., И те имеют поведение «копировать при записи» (CoW).Это означает, что если вы создаете копию struct, весь объект не обязательно копируется, а скорее использует внутреннее ссылочное поведение, чтобы избежать ненужного дублирования при сохранении семантики типа значения.Но если вы измените объект, о котором идет речь, только тогда будет сделана копия.

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

    Рассмотрим:

    struct Foo {
        private var data = Data(repeating: 0, count: 8_000)
    
        mutating func update(at: Int, with value: UInt8) {
            data[at] = value
        }
    }
    

    Приватный Data в этом примере будет использовать поведение CoW, поэтому при создании копий экземпляра Foo, большая полезная нагрузка выигралаНе копировать, пока вы не измените его.

    В итоге вы задали гипотетический вопрос, и ответ на самом деле зависит от того, какие типы задействованы в вашей большой полезной нагрузке.Но для многих нативных типов Swift это часто не проблема.

  2. Давайте представим, однако, что вы имеете дело с крайним случаем, когда (a) ваша общая полезная нагрузка велика;(б) ваш struct был составлен из типов, которые не используют CoW (то есть, не один из вышеупомянутых расширяемых типов Swift);и (c) вы хотите продолжать пользоваться семантикой значений (то есть не переходить на ссылочный тип с риском непреднамеренного совместного использования).В видео WWDC 2015 Создание улучшенных приложений с типами значений они показывают нам, как самостоятельно применять шаблон CoW, избегая ненужных копий и все же применяя истинное поведение типа значения после того, как объект мутирует.

    Обратите внимание:

    struct Foo {
        var value0 = 0.0
        var value1 = 0.0
        var value2 = 0.0
        ...
    }
    

    Вы могли бы переместить их в закрытый ссылочный тип:

    private class FooPayload {
        var value0 = 0.0
        var value1 = 0.0
        var value2 = 0.0
        ...
    }
    
    extension FooPayload: NSCopying {
        func copy(with zone: NSZone? = nil) -> Any {
            let object = FooPayload()
            object.value0 = value0
            ...
            return object
        }
    }
    

    Затем вы можете изменить свой выставленный тип значения, чтобы использовать этот закрытый ссылочный тип, а затем реализовать семантику CoW в любом изметоды мутации, например:

    struct Foo {
        private var _payload: FooPayload
    
        init() {
            _payload = FooPayload()
        }
    
        mutating func updateSomeValue(to value: Double) {
            copyIfNeeded()
    
            _payload.value0 = value
        }
    
        private mutating func copyIfNeeded() {
            if !isKnownUniquelyReferenced(&_payload) {
                _payload = _payload.copy() as! FooPayload
            }
        }
    }
    

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

    Это может бытьнемного, но это иллюстрирует, как добиться шаблона CoW для ваших собственных типов значений, если ваша большая полезная нагрузка еще не использует CoW.Я бы только предложил сделать это, если (а) ваша полезная нагрузка велика;(б) вы знаете, что соответствующие свойства полезной нагрузки еще не поддерживают CoW, и (в) вы определили, что вам действительно нужно такое поведение.

  3. Если вы столкнулись сПротоколы как типы, Swift автоматически использует CoW, сам за кулисами, Swift будет делать новые копии больших типов значений только тогда, когда тип значения мутирует.Но если ваши несколько экземпляров не изменились, он не создаст копии большой полезной нагрузки.

    Для получения дополнительной информации см. Видео WWDC 2017 Что нового в Swift: CoW Existential Buffers :

    Чтобы представить значение неизвестного типа, компилятор использует структуру данных, которую мы называем экзистенциальным контейнером.Внутри экзистенциального контейнера есть встроенный буфер для хранения небольших значений.В настоящее время мы переоцениваем размер этого буфера, но для Swift 4 он остается теми же 3 словами, что и в прошлом.Если значение слишком велико, чтобы поместиться в встроенный буфер, оно выделяется в куче.

    И хранилище в куче может быть очень дорогим.Это то, что вызвало прекращение работы, которое мы только что видели.Итак, что мы можем с этим поделать?Ответ - буферы коровы, экзистенциальные буферы CoW ...

    ... CoW - это сокращение от "копировать при записи".Возможно, вы слышали, что мы говорим о тего раньше, потому что это ключ к высокой производительности с семантикой значения.В Swift 4, если значение слишком велико, чтобы поместиться во встроенный буфер, оно выделяется в куче вместе со счетчиком ссылок.Несколько экзистенциальных контейнеров могут совместно использовать один и тот же буфер, если они только читают из него.

    И это позволяет избежать дорогостоящего выделения кучи.Буфер нужно копировать с отдельным выделением только в том случае, если он модифицирован, когда на него имеется несколько ссылок.И Swift теперь управляет сложностью этого для вас полностью автоматически.

    Для получения дополнительной информации о экзистенциальных контейнерах и CoW, я отсылаю вас к видео WWDC 2016 Понимание Swift Performance .

0 голосов
/ 25 февраля 2019

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

Также вы не можете поместить deinit в структурусмотреть на это напрямую, но есть обходной путь.Вы можете создать struct, который имеет ссылку на класс, который печатает что-либо, когда освобождается, например:

class DeallocPrinter {
    deinit {
        print("deallocated")
    }
}

struct SomeStruct {
    let printer = DeallocPrinter()
}

func makeStruct() {
    var foo = SomeStruct()
}
makeStruct() // deallocated becasue it escaped the scope

Credits

...