zoukankan      html  css  js  c++  java
  • 『Numpy』内存分析_高级切片和内存数据解析

    在计算机中,没有任何数据类型是固定的,完全取决于如何看待这片数据的内存区域。
    在numpy.ndarray.view中,提供对内存区域不同的切割方式,来完成数据类型的转换,而无须要对数据进行额外的copy,可以节约内存空间,我们可以将view看做对内存的展示方式。
    如:
    import numpy as np
    x = np.arange(10, dtype=np.int)
    
    print('An integer array:', x)
    print ('An float array:', x.view(np.float))
    
    An integer array: [0 1 2 3 4 5 6 7 8 9]

    An float array:
    [ 0.00000000e+000 4.94065646e-324 9.88131292e-324 1.48219694e-323 1.97626258e-323 2.47032823e-323 2.96439388e-323 3.45845952e-323 3.95252517e-323 4.44659081e-323]

    在实际使用中我们往往会采取更复杂的dtype(也就是说view可以与dtype搭配使用)输出内存中的值,后面我们会示范对于结构化数组的较为复杂的view使用。

    一、view和copy

    我们从numpy.reshape()函数入手,文档对于其返回值的解释:

    Returns
        -------
        reshaped_array : ndarray
            This will be a new view object if possible; otherwise, it will
            be a copy.  Note there is no guarantee of the *memory layout* (C- or
            Fortran- contiguous) of the returned array.
    其返回值可能是一个view,或是一个copy。相应的条件为:
      1、返回一个view条件:数据区域连续的时候
      2、反之,则返回一个copy
    我们得到了一个新概念,数组内存区域是否连续,numpy数组有flags['C_CONTIGUOUS']表示是否连续,有np.may_share_memory方法判断两个数组内存区域是否一致:
    a = np.zeros([2,10], dtype=np.int32)
    b = a.T  # 转置破坏连续结构
    
    a.flags['C_CONTIGUOUS']  # True
    b.flags['C_CONTIGUOUS']  # False
    
    np.may_share_memory(a,b)  # True
    b.base is a  # True
    id(b)==id(a)  # False
    
    
    a.shape = 20  # a的shape变了
    a.flags['C_CONTIGUOUS']  # True
    
    # b.shape = 20
    # AttributeError: incompatible shape for a non-contiguous array
    # 想要使用指定shape的方式,只能是连续数组,但是reshape方法由于不改变原数组,所以reshape不受影响
    

    数组切片是否会copy数据?

    不过,数组的切片对象虽然并非contiguous,但是对它的reshape操作并不会copy新的对象,

    a = np.arange(16).reshape(4,4)  
    
    print(a.T.flags['C_CONTIGUOUS'],a[:,0].flags['C_CONTIGUOUS'])
    # False False
    
    print (np.may_share_memory(a,a.T.reshape(16)),
           np.may_share_memory(a,a[:,0].reshape(4)))
    # False True
    

    但是,下一小节会介绍,高级切片会copy数组,开辟新的内存。

    二、numpy的结构数组

    利用np.dtype可以构建结构数组,numpy.ndarray.base会返回内存主人的信息,文档如下,

    Help on getset descriptor numpy.ndarray.base:

    base
        Base object if memory is from some other object.
        
        Examples
        --------
        The base of an array that owns its memory is None:
        
        >>> x = np.array([1,2,3,4])
        >>> x.base is None
        True
        
        Slicing creates a view, whose memory is shared with x:
        
        >>> y = x[2:]
        >>> y.base is x
        True

    1、建立结构数组

    persontype = np.dtype({
        'names':['name','age','weight','height'],
        'formats':['S30','i','f','f']}, align=True)
    a = np.array([('Zhang',32,72.5,167),
                  ('Wang',24,65,170)],dtype=persontype)
    a['age'].base
    

     array([(b'Zhang', 32, 72.5, 167.),

                (b'Wang', 24, 65. , 170.)],

                dtype={'names':['name','age','weight','height'],

                'formats':['S30','<i4','<f4','<f4'], 

                'offsets':[0,32,36,40],

                'itemsize':44,

                'aligned':True})

    2、高级切片和普通切片的不同

    In [26]: a.base
    In [27]: a[0].base
    In [28]: a[:1].base
    Out[28]: array([123,   4,   5,   6,  78])
    In [29]: a[[0,1]].base
    
    In [30]: a.base is None
    Out[30]: True
    In [31]: a[0].base is None
    Out[31]: True
    In [32]: a[:1].base is None
    Out[32]: False
    In [33]: a[[0,1]].base is None
    Out[33]: True
    

     由上可见高级切片会开辟新的内存,复制被切出的数据,这是因为这种不规则的内存访问使用原来的内存结构效率很低(逻辑相邻元素内存不相邻,标准的访问由于固定了起始和步长相当于访问相邻元素,所以效率较高),拷贝出来就是连续的内存数组了。

    3、高级切片且不开辟新内存的方法

    回到上上小节的结构数组,

    print(a['age'].base is a)
    print(a[['age', 'height']].base is None)
    

    True

    True

    我们通过指定内存解析方式,实现不开辟新内存,将原内存解析为高级切片指定的结构数组,

    def fields_view(arr, fields):
        dtype2 = np.dtype({name:arr.dtype.fields[name] for name in fields})
        # print(dtype2)
        # {'names':['age','weight'], 'formats':['<i4','<f4'], 'offsets':[32,36], 'itemsize':40}
        # print([(name,arr.dtype.fields[name]) for name in fields])
        # [('age', (dtype('int32'), 32)), ('weight', (dtype('float32'), 36))]
        # print(arr.strides)
        # (44,)
        return np.ndarray(arr.shape, dtype2, arr, 0, arr.strides)
    '''
    ndarray(shape, dtype=float, buffer=None, offset=0,
     |          strides=None, order=None)
     
    参数 	类型 	作用
    shape 	int型tuple 	多维数组的形状
    dtype 	data-type 	数组中元素的类型
    buffer 		用于初始化数组的buffer
    offset 	int 	buffer中用于初始化数组的首个数据的偏移
    strides 	int型tuple 	每个轴的下标增加1时,数据指针在内存中增加的字节数
    order 	'C' 或者 'F' 	'C':行优先;'F':列优先
    '''
    
    v = fields_view(a, ['age', 'weight'])
    print(v.base is a)
    
    v['age'] += 10
    print('+++'*10)
    print(v)
    print(v.dtype)
    print(v.dtype.fields)
    print('+++'*10)
    print(a)
    print(a.dtype)
    print(a.dtype.fields)
    
    True
    ++++++++++++++++++++++++++++++
    [(42,  72.5) (34,  65. )]
    {'names':['age','weight'], 'formats':['<i4','<f4'], 'offsets':[32,36], 'itemsize':40}
    {'age': (dtype('int32'), 32), 'weight': (dtype('float32'), 36)}
    ++++++++++++++++++++++++++++++
    [(b'Zhang', 42,  72.5,  167.) (b'Wang', 34,  65. ,  170.)]
    {'names':['name','age','weight','height'], 'formats':['S30','<i4','<f4','<f4'], 'offsets':[0,32,36,40], 'itemsize':44, 'aligned':True}
    {'name': (dtype('S30'), 0), 'age': (dtype('int32'), 32), 'weight': (dtype('float32'), 36), 'height': (dtype('float32'), 40)}

    这里注意一下.dtype的’itemsize‘参数,表示添加一条(行)数据,内存增加了多少字节,由于保存了'offsets'偏移信息,我们生成的dtype展示的是一个稀疏的结构,但是每一行不会有多余的尾巴,这是因为空元素是由实元素记录偏移量的空隙产生的。

    『Numpy』内存分析_numpy.dtype解析内存数据中我们会更详细的介绍有关数组内存解析的方法。

  • 相关阅读:
    2015年终总结
    mmzb游戏事故分析
    为sproto手写了一个python parser
    Lua小技巧
    Techparty-广州 10 月 31 日 Docker 专场沙龙 后记
    1password密码库格式更新
    SSL加密与系统时间
    webpack的学习使用三
    webpack的学习使用二
    webpack的学习使用一
  • 原文地址:https://www.cnblogs.com/hellcat/p/8715830.html
Copyright © 2011-2022 走看看