Быстрое копирование при записи для всех структур?

Я знаю, что Swift оптимизирует копирование при записи для массивов, но будет ли он делать это для всех структур? Например:

struct Point {
   var x:Float = 0
}

var p1 = Point()
var p2 = p1 //p1 and p2 share the same data under the hood
p2.x += 1 //p2 now has its own copy of the data

person gloo    schedule 19.04.2017    source источник
comment
@vadian откуда ты знаешь?   -  person matt    schedule 19.04.2017
comment
Nitpick: такое поведение является свойством компилятора Swift, а не языка Swift. Пока поведение программы соответствует спецификации языка, компилятор может делать то, что считает нужным.   -  person Alexander    schedule 19.04.2017


Ответы (1)


Array реализован с поведением копирования при записи — вы получите его независимо от каких-либо оптимизаций компилятора (хотя, конечно, оптимизации могут уменьшить количество случаев, когда необходимо копирование).

На базовом уровне Array — это просто структура, которая содержит ссылку на выделенный в куче буфер, содержащий элементы, поэтому несколько экземпляров Array могут ссылаться на один и тот же буфер. Когда вы приступите к изменению данного экземпляра массива, реализация проверит, является ли буфер уникальным, и если это так, измените его напрямую. В противном случае массив выполнит копию базового буфера, чтобы сохранить семантику значений.

Однако с вашей структурой Point вы не реализуете копирование при записи на уровне языка. Конечно, как говорит @Alexander, это не не мешать компилятору выполнять все виды оптимизации, чтобы минимизировать стоимость копирования целых структур. Тем не менее, эти оптимизации не должны точно следовать поведению копирования при записи — компилятор просто может делать все, если программа работает в соответствии со спецификацией языка.

В вашем конкретном примере и p1, и p2 являются глобальными, поэтому компилятору необходимо сделать их отдельными экземплярами, поскольку другие файлы .swift в том же модуле имеют к ним доступ (хотя это потенциально может быть оптимизировано за счет оптимизации всего модуля). Однако компилятору по-прежнему не нужно копировать экземпляры — он может просто ">оценить добавление с плавающей запятой во время компиляции и инициализировать один из глобальных переменных с помощью 0.0, а другой с 1.0.

А если бы они были локальными переменными в функции, например:

struct Point {
    var x: Float = 0
}

func foo() {
    var p1 = Point()
    var p2 = p1
    p2.x += 1
    print(p2.x)
}

foo()

Для начала компилятору даже не нужно создавать два экземпляра Point — он может просто создать одну локальную переменную с плавающей запятой, инициализированную значением 1.0, и распечатать ее.

Что касается передачи типов значений в качестве аргументов функций, для достаточно больших типов и (в случае структур) функций, которые используют достаточно своих свойств, компилятор -value-type-parameters-to-pass-by-reference" rel="noreferrer">может передавать их по ссылке, а не копировать. Затем вызываемый объект может сделать их копию только в случае необходимости, например, когда ему нужно работать с изменяемой копией.

В других случаях, когда структуры передаются по значению, компилятор также может использовать значения структуры, которая им нужна" rel="noreferrer">специализировать функции, чтобы копировать только те свойства, которые нужны функции.

Для следующего кода:

struct Point {
    var x: Float = 0
    var y: Float = 1
}

func foo(p: Point) {
    print(p.x)
}

var p1 = Point()
foo(p: p1)

Предполагая, что foo(p:) не встроен компилятором (в этом примере он будет встроен, но как только его реализация достигнет определенного размера, компилятор не решит, что это того стоит) - компилятор может специализировать функцию как:

func foo(px: Float) {
    print(px)
}

foo(px: 0)

Он передает в функцию только значение свойства x объекта Point, тем самым снижая затраты на копирование свойства y.

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

person Hamish    schedule 19.04.2017
comment
Таким образом, в Xcode с включенной оптимизацией всего модуля, если я создам структуру с var, а затем передам ее группе функций, которые НЕ изменяют структуру, будет ли Xcode оптимизировать все эти копии? - person gloo; 20.04.2017
comment
@gloo Это зависит от функций и структуры, но да, это вполне возможно — только что выяснил (пройдя IR для оптимизированной сборки), что для достаточно больших структур Swift может передавать их по ссылке на функции, поэтому полностью исключая копирование (то есть до тех пор, пока вызываемый объект не изменит копию). Но с таким количеством различных оптимизаций и крайних случаев, когда их нельзя применить, вы не можете просто свести поведение к копированию при записи. Есть ли реальное узкое место в производительности, о котором вы беспокоитесь, или вам просто любопытно? - person Hamish; 20.04.2017
comment
Я написал игровой движок на swift/metal. Я передаю множество структур, представляющих команды рисования, которые будут использоваться графическим процессором, и данные текущего кадра. В то время я думал, что все мои структуры будут использовать COW, чтобы избежать ненужных копий, но потом я узнал, что на самом деле было много разногласий по поводу того, что на самом деле делает Xcode. Поэтому я забеспокоился, что мой движок не так оптимизирован, как я думал. Моя игра работает со скоростью 60 кадров в секунду, так что сейчас это не проблема, просто боюсь, что она не будет хорошо масштабироваться для будущих проектов. - person gloo; 20.04.2017
comment
@gloo Если бы в настоящее время это не было узким местом в производительности, я бы не стал об этом беспокоиться. Как уже говорилось, компилятор может выполнять множество оптимизаций, чтобы уменьшить количество копий типов значений. Если позже это станет проблемой, вы можете относительно легко реорганизовать свою структуру (структуры) для использования копирования при записи; но вы должны делать это только после того, как определите это как проблему при профилировании, и увидев, что внесение изменений действительно повышает производительность... - person Hamish; 20.04.2017
comment
поскольку реализация копирования при записи на уровне языка требует ссылок и, следовательно, требует затрат как на выделение кучи, так и на подсчет ссылок. Попытка изменить свою логику сейчас, не зная наверняка, улучшаете вы ситуацию или ухудшаете, была бы контрпродуктивной. - person Hamish; 20.04.2017
comment
@Hamish CoW также требует добавления ветки (чтобы проверить, нужна ли копия) для каждого метода мутации. Между предсказанием ветвлений и спекулятивным выполнением я не уверен, как это будет происходить, но я достаточно уверен, что это будет медленнее, чем безусловное копирование всех небольших структур. - person Alexander; 14.06.2019