Что такое копирование при записи?

Я хотел бы знать, что такое копирование при записи и для чего оно используется? Термин "массив копирования при записи" несколько раз упоминается в руководствах по Sun JDK, но я не понимаю, что это значит.

6 ответов

Решение

Я собирался написать свое собственное объяснение, но эта статья в Википедии в значительной степени подводит итог.

Вот основная концепция:

Копирование при записи (иногда называемое "COW") - это стратегия оптимизации, используемая в компьютерном программировании. Основная идея заключается в том, что если несколько абонентов запрашивают ресурсы, которые изначально неразличимы, вы можете дать им указатели на один и тот же ресурс. Эту функцию можно поддерживать до тех пор, пока вызывающая сторона не попытается изменить свою "копию" ресурса, после чего создается настоящая личная копия, чтобы изменения не становились видимыми для всех остальных. Все это происходит прозрачно для абонентов. Основное преимущество заключается в том, что если вызывающая сторона никогда не вносит никаких изменений, не нужно создавать личную копию.

Также вот приложение общего использования COW:

Концепция COW также используется для поддержки мгновенного снимка на серверах баз данных, таких как Microsoft SQL Server 2005. Мгновенные снимки сохраняют статическое представление базы данных, сохраняя копию данных перед изменением при обновлении нижележащих данных. Мгновенные снимки используются для тестирования или для отчетов, зависящих от момента, и не должны использоваться для замены резервных копий.

"Копировать при записи" означает более или менее то, на что это похоже: у всех есть одна общая копия одних и тех же данных до тех пор, пока они не будут записаны, а затем делается копия. Обычно копирование при записи используется для решения проблем параллелизма. Например, в ZFS блоки данных на диске размещаются при копировании при записи; пока нет изменений, вы сохраняете исходные блоки; изменение изменило только затронутые блоки. Это означает, что минимальное количество новых блоков выделено.

Эти изменения также обычно реализуются как транзакционные, то есть имеют свойства ACID. Это устраняет некоторые проблемы параллелизма, потому что тогда вы гарантированно, что все обновления являются атомарными.

Я не буду повторять тот же ответ на Copy-on-Write. Я думаю, что ответ Эндрю и ответ Чарли уже сделали это очень ясным. Я приведу вам пример из мира ОС, просто чтобы упомянуть, насколько широко эта концепция используется.

Мы можем использовать fork() или же vfork() создать новый процесс. vfork следует концепции копирования при записи. Например, дочерний процесс, созданный vfork, поделится сегментом данных и кода с родительским процессом. Это ускоряет время разветвления. Ожидается, что вы будете использовать vfork, если вы выполняете exec, а затем vfork. Таким образом, vfork создаст дочерний процесс, который поделится сегментом данных и кода со своим родителем, но когда мы вызовем exec, он загрузит образ нового исполняемого файла в адресное пространство дочернего процесса.

Просто в качестве другого примера, Mercurial использует копирование при записи, чтобы сделать клонирование локальных репозиториев действительно "дешевой" операцией.

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

Ниже представлена ​​реализация Python с копированием при записи с использованием шаблона проектирования прокси. Ссылка на неизменныйValue объект удерживается изменяемым ValueProxyобъект (прокси). ВValueProxy объект перенаправляет все запросы на чтение в неизменяемый Value объект и перехватывает все запросы на запись, создавая новый неизменяемый Valueобъект в правильном состоянии. ВValueProxy объект должен быть неглубоко скопирован между переменными, чтобы разрешить совместное использование Value объект.

import abc
import copy

class BaseValue(abc.ABC):
    @abc.abstractmethod
    def read(self):
        raise NotImplementedError
    @abc.abstractmethod
    def write(self, data):
        raise NotImplementedError

class Value(BaseValue):
    def __init__(self, data):
        self.data = data
    def read(self):
        return self.data
    def write(self, data):
        pass

class ValueProxy(BaseValue):
    def __init__(self, data):
        self.value = Value(data)
    def read(self):
        return self.value.read()
    def write(self, data):
        self.value = Value(data)

v = ValueProxy(1)
w = copy.copy(v)  # shares the immutable Value object
assert v.read() == w.read()
assert id(v.value) == id(w.value)
w.write(2)  # creates a new immutable Value object with the correct state
assert v.read() != w.read()
assert id(v.value) != id(w.value)

Я нашел эту хорошую статью о zval в PHP, которая также упоминала COW:

Копирование при записи (сокращенно "COW") - это прием, предназначенный для экономии памяти. Он используется более широко в разработке программного обеспечения. Это означает, что PHP будет копировать память (или выделять новую область памяти) при записи в символ, если он уже указывал на zval.

Он также используется в Ruby Enterprise Edition как отличный способ экономии памяти.

Хорошим примером является Git, который использует стратегию хранения больших двоичных объектов. Почему он использует хеши? Отчасти потому, что с ними легче выполнять различия, но также потому, что упрощает оптимизацию стратегии COW. Когда вы делаете новую фиксацию с несколькими изменениями файлов, подавляющее большинство объектов и деревьев не изменится. Следовательно, фиксация через различные указатели, состоящие из хэшей, будет ссылаться на группу уже существующих объектов, что значительно уменьшит пространство для хранения, необходимое для хранения всей истории.

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

Другие вопросы по тегам