写时复制#
写时复制是一种内存管理策略,它允许多个包含相同数据的 cuDF 对象引用同一内存地址,前提是它们中的任何一个都不修改底层数据。使用这种方法,任何生成对象未修改视图的操作(例如复制、切片或类似 DataFrame.head
的方法)都会返回一个指向与原始对象相同内存的新对象。但是,当现有对象或新对象被 修改 时,会在修改之前创建数据的副本,从而确保更改不会在两个对象之间传播。通过查看下面的示例可以最好地理解这种行为。
cuDF 中的默认行为是禁用写时复制,因此要使用它,必须通过设置 cuDF 选项来明确启用。建议在脚本执行开始时设置写时复制,因为如果在脚本执行中途更改此设置,将会出现意外行为:启用写时复制时创建的对象仍将具有写时复制行为,而禁用写时复制时创建的对象将具有不同的行为。
启用写时复制#
使用
cudf.set_option
>>> import cudf >>> cudf.set_option("copy_on_write", True)
在启动 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
当在 series
或 copied_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 内置容器(如 list
)的预期行为,而不是 numpy array
的索引行为。修改 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
显式深层复制和浅层复制比较#
写时复制已启用 |
写时复制已禁用(默认) |
|
---|---|---|
|
会进行真正的复制,并且更改不会传播到原始对象。 |
会进行真正的复制,并且更改不会传播到原始对象。 |
|
两个对象之间共享内存,但对其中一个对象的任何写入操作都会在执行写入之前触发真正的物理复制。因此,更改不会传播到原始对象。 |
两个对象之间共享内存,在一个对象上执行的更改会传播到另一个对象。 |