什么是书面复印?

我想知道什么是 书面复印和它的用途。这个术语在 Sun JDK 教程中多次提到。

134956 次浏览

我本来想写出自己的解释,但是 这篇维基百科的文章基本上总结出来了。

基本概念如下:

写上复制(有时称为“ COW”)是计算机编程中使用的一种优化策略。其基本思想是,如果多个调用方请求最初无法区分的资源,您可以为它们提供指向同一资源的指针。可以维护这个函数,直到调用方尝试修改其资源的“副本”,此时将创建一个真正的私有副本,以防止其他所有人都可以看到这些更改。所有这一切对打电话的人来说都是显而易见的。主要优点是,如果调用方从未进行任何修改,则不需要创建任何私有副本。

下面是 COW 的一个常用应用:

COW 概念也用于维护数据库服务器上的即时快照,比如2005年的 Microsoft SQL Server。即时快照通过在更新基础数据时存储数据的预修改副本来保存数据库的静态视图。即时快照用于测试用途或依赖于时刻的报告,不应用于替换备份。

“书面复制”或多或少意味着它听起来像: 每个人都有一个共享的副本相同的数据 直到它被写下来,然后复制。通常,写上复制(copy-on-write)用于解决并发类型的问题。例如,在 ZFS中,磁盘上的数据块是以写上复制的方式分配的; 只要没有更改,就保留原始块; 更改只更改了受影响的块。这意味着分配的新块的最小数量。

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

我不会重复同样的答案在复制写。我想 安德鲁的回答查理的回答已经说得很清楚了。我将给你一个来自操作系统世界的例子,只是为了说明这个概念被广泛使用。

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

它也被用在 Ruby 的“企业版”中,作为一种节省内存的简单方法。

仅仅提供另一个例子,Mercurial 使用写上复制使克隆本地存储库成为一个真正“廉价”的操作。

其原理与其他示例相同,只不过讨论的是物理文件而不是内存中的对象。最初,克隆不是原来的复制品,而是原来的 硬链接。当您在克隆中更改文件时,将编写副本来表示新版本。

我发现 这个有一篇关于 zval 在 PHP 中的优秀文章,其中也提到了 COW:

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

Git 就是一个很好的例子,它使用一种策略来存储 blobs。它为什么使用散列?部分原因是因为它们更容易执行差异,但也因为它更容易优化一个 COW 策略。当您使用很少的文件更改进行新的提交时,绝大多数对象和树都不会更改。因此,提交,将通过各种指针的散列引用一堆已经存在的对象,使存储整个历史所需的存储空间小得多。

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

Erich Gamma 等人的书《 设计模式: 可重用面向对象软件的元素》清楚地描述了 书面复印优化(“后果”一节,“代理”一章) :

代理模式在访问 附加的间接方法有许多用途,具体取决于 类似代理人:

  1. 远程代理可以隐藏对象位于不同地址空间的事实。
  2. 虚拟代理可以执行优化,例如按需创建对象。
  3. 保护代理和智能引用都允许在访问对象时执行额外的内务管理任务。

代理模式还可以隐藏另一个优化 客户端。它被称为 书面复印,它与创建 复制一个大而复杂的对象可能是昂贵的 操作。如果副本从未被修改,那么就没有必要 通过使用代理延迟复制过程,我们 确保我们支付的价格复制对象,只有当它的 修改过的。

为了使写上复制的工作,主题必须被引用计数。 复制代理只会增加这个引用 只有当客户端请求修改 在这种情况下,代理必须 也递减主题的引用计数。当引用计数 如果数字为零,主题就会被删除。

写上复制可以降低复制重量级主题的成本 意义重大。

下面是使用 代理模式进行写上复制优化的 Python 实现。此设计模式的目的是为另一个对象提供一个代理,以控制对它的访问。

代理模式的类图:

Class diagram of the Proxy pattern

代理模式的对象图:

Object diagram of the Proxy pattern

首先,我们定义主题的界面:

import abc




class Subject(abc.ABC):


@abc.abstractmethod
def clone(self):
raise NotImplementedError


@abc.abstractmethod
def read(self):
raise NotImplementedError


@abc.abstractmethod
def write(self, data):
raise NotImplementedError

接下来,我们定义实现主题接口的实际主题:

import copy




class RealSubject(Subject):


def __init__(self, data):
self.data = data


def clone(self):
return copy.deepcopy(self)


def read(self):
return self.data


def write(self, data):
self.data = data

最后,我们定义了实现主题接口并引用真实主题的代理:

class Proxy(Subject):


def __init__(self, subject):
self.subject = subject
try:
self.subject.counter += 1
except AttributeError:
self.subject.counter = 1


def clone(self):
return Proxy(self.subject)  # attribute sharing (shallow copy)


def read(self):
return self.subject.read()


def write(self, data):
if self.subject.counter > 1:
self.subject.counter -= 1
self.subject = self.subject.clone() # attribute copying (deep copy)
self.subject.counter = 1
self.subject.write(data)

然后,通过使用代理作为真实主题的替身,客户端可以从即写即拷优化中受益:

if __name__ == '__main__':
x = Proxy(RealSubject('foo'))
x.write('bar')
y = x.clone()  # the real subject is shared instead of being copied
print(x.read(), y.read())  # bar bar
assert x.subject is y.subject
x.write('baz')  # the real subject is copied on write because it was shared
print(x.read(), y.read())  # baz bar
assert x.subject is not y.subject