2.2 C序F序与连续性检测


文档摘要

2.2 C序F序与连续性检测 本节摘要:C 序按行填满内存,F 序按列填满内存,两者是同一块数据的两种铺法。flags 属性的 CCONTIGUOUS 与 FCONTIGUOUS 标志用于检测连续性;ascontiguousarray 与 asfortranarray 负责在不满足时生成连续拷贝。什么时候必须转换、什么时候不必,本节给出工程判断。 两种铺法从哪来 一个 2×3 数组,六元素的数据块只有一种物理排列:1、2、3、4、5、6 依次躺在内存里。但把它解释成"先填满第一行再填第二行"(C 序,C 语言传统),还是"先填满第一列再填第二列"(F 序,Fortran 传统),得到的是两个不同的逻辑数组。

2.2 C序F序与连续性检测

本节摘要:C 序按行填满内存,F 序按列填满内存,两者是同一块数据的两种铺法。flags 属性的 C_CONTIGUOUS 与 F_CONTIGUOUS 标志用于检测连续性;ascontiguousarray 与 asfortranarray 负责在不满足时生成连续拷贝。什么时候必须转换、什么时候不必,本节给出工程判断。

两种铺法从哪来

一个 2×3 数组,六元素的数据块只有一种物理排列:1、2、3、4、5、6 依次躺在内存里。但把它解释成"先填满第一行再填第二行"(C 序,C 语言传统),还是"先填满第一列再填第二列"(F 序,Fortran 传统),得到的是两个不同的逻辑数组。行列顺序之争是历史遗留:数值计算的老祖宗 Fortran 按列存,C 语言按行存,NumPy 两种都支持,默认 C 序。

import numpy as np c = np.array([[1, 2, 3], [4, 5, 6]], order="C") # 默认,按行填充 f = np.array([[1, 2, 3], [4, 5, 6]], order="F") # 按列填充 print("C 序 strides:", c.strides) # (24, 8) 行内相邻 print("F 序 strides:", f.strides) # (8, 24) 列内相邻 print("两者数值相等:", np.array_equal(c, f)) # True,逻辑值完全一样

数值一样、步长相反。逻辑层面看不出区别的数组,在内存层面是两种东西——这就是本章反复强调的:不看 strides 就不算真正认识一个数组。

C 序与 F 序对照图

C 序与 F 序对照图

用 flags 检测连续性

数组的 flags 属性是一张"体检表",其中两项最常用:

import numpy as np a = np.arange(12).reshape(3, 4) print(a.flags["C_CONTIGUOUS"]) # True:按行连续 print(a.flags["F_CONTIGUOUS"]) # False at = a.T print(at.flags["C_CONTIGUOUS"]) # False print(at.flags["F_CONTIGUOUS"]) # True:转置后反而变成 F 序连续! # 一维数组两种连续都为 True v = np.arange(5) print(v.flags["C_CONTIGUOUS"], v.flags["F_CONTIGUOUS"]) # True True # 跨步切片:两种连续都不是 s = a[:, ::2] print(s.flags["C_CONTIGUOUS"], s.flags["F_CONTIGUOUS"]) # False False

注意转置的例子:C 序数组转置后 F_CONTIGUOUS 变为 True。连续性没有丢,只是换了一个方向。真正两头都不连续的,是跨步切片这类操作的结果。

ascontiguousarray:转换的代价与时机

当某个接口只接受 C 序连续数组时,用 ascontiguousarray 生成连续拷贝:

import numpy as np a = np.arange(12).reshape(3, 4) at = a.T c_view = np.ascontiguousarray(at) print(c_view.flags["C_CONTIGUOUS"]) # True print(np.shares_memory(c_view, at)) # False —— 发生了真实拷贝 print(np.array_equal(c_view, at)) # True,数值不变 # 已连续的数组传入,不会白白拷贝,原样返回 same = np.ascontiguousarray(a) print(np.shares_memory(same, a)) # True

工程判断:一次性使用就吃转置视图的零拷贝红利;反复使用(循环里多次参与计算)就花一次拷贝买回连续性。经验公式是"拷贝成本约等于一次同规模运算",后续计算超过两三次,拷贝就值回票价。

一个真实场景的排查演练。背景:某图像处理函数接收外部传来的数组,内部做上百次卷积。现象:同样的数据,从不同入口进来速度差 5 倍。排查操作:打一行 flags——

import numpy as np img = np.random.rand(1080, 1920) img_f = np.asfortranarray(img) # 模拟从 F 序接口进来的数据 print("C 序输入:", img.flags["C_CONTIGUOUS"]) # True print("F 序输入:", img_f.flags["C_CONTIGUOUS"]) # False # 统一入口:一行代码修复 def process(x): x = np.ascontiguousarray(x) # 已连续则零开销,不连续则拷贝一次 return x.sum(axis=1) # 后续上百次行方向计算全部吃满缓存 print(process(img).shape) # (1080,) print(process(img_f).shape) # (1080,)

结果:入口统一后,两种来源的耗时一致。解读:慢的不是算法,是每次调用都在非连续视图上做行方向扫描。变式:如果函数内部是列方向计算,就换成 asfortranarray,或干脆把算法改成沿连续轴计算。

本节要点回顾

  • 两种铺法:C 序按行填充、strides 后缀连乘;F 序按列填充、前缀连乘;逻辑数值可以完全相同
  • 检测工具:flags 的 C_CONTIGUOUS 与 F_CONTIGUOUS,一维数组两者皆 True,转置会互换两个标志
  • 转换函数:ascontiguousarray 与 asfortranarray,输入已连续时原样返回、零拷贝
  • 决策准则:一次性用视图,反复用买拷贝;与外部 F 序世界(MATLAB、Fortran 库)交接时主动检查
  • 排查套路:数据来源可疑时先打 flags,速度异常先怀疑连续性

下一节把视角从"怎么摆"切换到"动没动"——视图与拷贝的分界线,这是全册最重要的一节。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U