写时复制#

写时复制是一种内存管理策略,允许多个包含相同数据的cuDF对象引用相同的内存地址,只要它们都不修改底层数据。 通过这种方法,任何生成对象未修改视图的操作(如复制、切片或像DataFrame.head这样的方法)都会返回一个新对象,该对象指向与原始对象相同的内存。 然而,当现有对象或新对象被修改时,在修改之前会复制数据,确保更改不会在两个对象之间传播。 通过查看以下示例,可以最好地理解这种行为。

cuDF 中的默认行为是禁用写时复制,因此要使用它,必须通过设置 cuDF 选项明确选择启用。建议在脚本执行开始时设置写时复制,因为在脚本执行过程中更改此设置时,会出现意外行为,即在启用写时复制时创建的对象仍将具有写时复制行为,而在禁用写时复制时创建的对象将具有不同的行为。

启用写时复制#

  1. 使用 cudf.set_option:

    >>> import cudf
    >>> cudf.set_option("copy_on_write", True)
    
  2. 在启动Python解释器之前,将环境变量 CUDF_COPY_ON_WRITE 设置为 1

    export CUDF_COPY_ON_WRITE="1" python -c "import cudf"
    

禁用写时复制#

可以通过将copy_on_write选项设置为False来禁用写时复制:

>>> cudf.set_option("copy_on_write", False)

制作副本#

使用写时复制不需要在代码中进行额外的更改。

>>> series = cudf.Series([1, 2, 3, 4])

执行浅拷贝将创建一个新的Series对象,指向相同的底层设备内存:

>>> copied_series = series.copy(deep=False)
>>> series
0    1
1    2
2    3
3    4
dtype: int64
>>> copied_series
0    1
1    2
2    3
3    4
dtype: int64

当对seriescopied_series执行写操作时,会创建一个真实的数据物理副本:

>>> series[0:2] = 10
>>> series
0    10
1    10
2     3
3     4
dtype: int64
>>> copied_series
0    1
1    2
2    3
3    4
dtype: int64

注释#

当启用写时复制时,切片或索引时不再有视图的概念。从这个意义上说,索引的行为就像人们期望的内置Python容器(如lists)一样,而不是索引numpy arrays。修改由cuDF创建的“视图”将始终触发复制,并且不会修改原始对象。

写时复制产生了更加一致的复制语义。由于每个对象都是原始对象的副本,用户不再需要考虑何时可能会意外地在原地发生修改。这将使操作之间保持一致,并在启用写时复制时使cudf和pandas的行为保持一致。以下是一个例子,展示了在没有启用写时复制的情况下,pandas和cudf目前的不一致行为:


>>> import pandas as pd
>>> s = pd.Series([1, 2, 3, 4, 5])
>>> s1 = s[0:2]
>>> s1[0] = 10
>>> s1
0    10
1     2
dtype: int64
>>> s
0    10
1     2
2     3
3     4
4     5
dtype: int64

>>> import cudf
>>> s = cudf.Series([1, 2, 3, 4, 5])
>>> s1 = s[0:2]
>>> s1[0] = 10
>>> s1
0    10
1     2
>>> s
0    1
1    2
2    3
3    4
4    5
dtype: int64

当启用写时复制时,上述不一致性得到解决:

>>> import pandas as pd
>>> pd.set_option("mode.copy_on_write", True)
>>> s = pd.Series([1, 2, 3, 4, 5])
>>> s1 = s[0:2]
>>> s1[0] = 10
>>> s1
0    10
1     2
dtype: int64
>>> s
0    1
1    2
2    3
3    4
4    5
dtype: int64


>>> import cudf
>>> cudf.set_option("copy_on_write", True)
>>> s = cudf.Series([1, 2, 3, 4, 5])
>>> s1 = s[0:2]
>>> s1[0] = 10
>>> s1
0    10
1     2
dtype: int64
>>> s
0    1
1    2
2    3
3    4
4    5
dtype: int64

显式深拷贝和浅拷贝比较#

启用写时复制

禁用写时复制(默认)

.copy(deep=True)

创建了一个真实的副本,更改不会传播到原始对象。

创建了一个真实的副本,更改不会传播到原始对象。

.copy(deep=False)

内存在这两个对象之间共享,但在一个对象上执行任何写操作之前,都会触发真正的物理复制。因此,更改不会传播到原始对象。

内存在这两个对象之间共享,对一个对象所做的更改将传播到另一个对象。