什么是写时复制?

时间:2009-03-10 04:27:26

标签: data-structures copy-on-write

我想知道什么是copy-on-write以及它用于什么? Sun JDK教程中多次提到术语“写时复制数组”,但我不明白它的含义。

9 个答案:

答案 0 :(得分:113)

我打算写下我自己的解释,但this Wikipedia article几乎可以总结一下。

以下是基本概念:

  

写时复制(有时称为“COW”)是计算机编程中使用的优化策略。基本思想是,如果多个呼叫者要求最初无法区分的资源,您可以给他们指向同一资源的指针。可以维持此功能,直到调用者尝试修改其资源的“副本”,此时创建一​​个真正的私有副本以防止其他人看到更改。所有这些都对呼叫者透明地发生。主要优点是,如果调用者从不进行任何修改,则不需要创建私有副本。

此处还有一个COW常用的应用:

  

COW概念还用于维护数据库服务器(如Microsoft SQL Server 2005)上的即时快照。即时快照通过在更新底层数据时存储数据的预修改副本来保留数据库的静态视图。即时快照用于测试用途或与时间相关的报告,不应用于替换备份。

答案 1 :(得分:49)

“写入时复制”意味着或多或少的含义:每个人都有相同数据的单个共享副本,直到它被写入,然后进行复制。通常,copy-on-write用于解决并发类问题。例如,在ZFS中,磁盘上的数据块被分配为写时复制;只要没有变化,你保留原始块;更改仅更改了受影响的块。这意味着分配了最小数量的新块。

这些更改通常也实现为事务性,即它们具有ACID属性。这消除了一些并发问题,因为这样可以保证所有更新都是原子的。

答案 2 :(得分:9)

我不会在Copy-on-Write上重复相同的答案。我认为Andrew's answerCharlie's answer已经非常清楚了。我将从OS世界给你一个例子,只是提到这个概念的使用范围有多广泛。

我们可以使用fork()vfork()来创建新流程。 vfork遵循copy-on-write的概念。例如,vfork创建的子进程将与父进程共享数据和代码段。这加快了分叉时间。如果您正在执行exec,然后执行vfork,则应使用vfork。因此,vfork将创建子进程,该进程将与其父进程共享数据和代码段,但是当我们调用exec时,它将在子进程的地址空间中加载新可执行文件的映像。

答案 3 :(得分:6)

只是提供另一个例子,Mercurial uses copy-on-write使克隆本地存储库成为一种非常“廉价”的操作。

原理与其他示例相同,只是您在谈论物理文件而不是内存中的对象。最初,克隆不是重复的,而是原始的hard link。当您更改克隆中的文件时,将写入副本以表示新版本。

答案 4 :(得分:1)

它也在Ruby'Enterprise Edition中用作节省内存的简洁方法。

答案 5 :(得分:1)

我在PHP中找到了关于zval的this好文章,其中也提到了COW:

  

Copy On Write(缩写为“COW”)是一种旨在节省内存的技巧。它在软件工程中更常用。这意味着当您写入符号时,PHP将复制内存(或分配新的内存区域),如果此符号已经指向zval。

答案 6 :(得分:1)

写时复制是一种通过共享内存直到修改其中一个副本来减少资源副本的内存使用量的技术。换句话说,这些副本最初是虚拟副本,并且仅在第一次写操作时才成为真实副本,因此名称为“写时复制”。

以下是使用proxy design pattern的写时复制技术的Python实现。 ValueProxy对象( proxy )通过以下方式实现写时复制技术:

  • 具有绑定到不可变Value对象(主题)的属性;
  • 将读取请求转发给subject属性;
  • 将写入请求转换为具有新状态的新不可变Value对象的创建,并将subject属性重新绑定到新不可变Value对象;
  • 将复制请求转换为创建新的ValueProxy对象,该对象与原始ValueProxy对象具有相同的主题属性。
import abc

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, subject):
        self.subject = subject
    def read(self):
        return self.subject.read()
    def write(self, data):
        self.subject = Value(data)
    def clone(self):
        return ValueProxy(self.subject)

v1 = ValueProxy(Value('foo'))
v2 = v1.clone()  # shares the immutable Value object between the copies
assert v1.subject is v2.subject
v2.write('bar')  # creates a new immutable Value object with the new state
assert v1.subject is not v2.subject

答案 7 :(得分:0)

一个很好的例子是Git,它使用一种策略来存储斑点。为什么使用散列?一方面是因为它们更易于执行差异化处理,另一方面是因为它使优化COW策略更简单。当您使用很少的文件进行新提交时,绝大多数对象和树都不会改变。因此,提交将通过由哈希组成的各种指针引用一堆已经存在的对象,从而使存储整个历史记录所需的存储空间小得多。

答案 8 :(得分:0)

这是一个内存保护概念。在此编译器中,将创建额外的副本以修改子级中的数据,并且此更新后的数据不会反映在父级数据中。