2013-03-05 218 views
2

与其他许多人一样,我的情况是我有一个类收集大量数据,并提供一种将数据作为numpy数组返回的方法。 (即使在返回数组后,其他数据也可以继续流入)。由于创建数组是一项昂贵的操作,我只想在必要时创建它,并尽可能高效地创建它(特别是在可能的情况下就地附加数据)。为此,我一直在阅读有关ndarray.resize()方法和refcheck参数的内容。我明白,只有当“你确定你没有与另一个Python对象共享这个数组的内存”时,refcheck应该被设置为False。ndarray.resize:为refcheck参数传递正确的值

事情是我不确定。有时候我有,有时候我没有。如果refcehck失败(我可以捕获它,然后创建一个新副本),但是我希望它只在存在“真实的”外部引用时才会失败,而忽略了我认为是安全的那些引用。

这里有一个简单的例证:

import numpy as np 

def array_append(arr, values, refcheck = True): 
    added_len = len(values) 
    if added_len == 0: 
     return arr 
    old_len = len(arr) 
    new_len = old_len + added_len 
    arr.resize(new_len, refcheck = refcheck) 
    arr[old_len:] = values 
    return arr 

class DataCollector(object): 

    def __init__(self): 
     self._new_data = [] 
     self._arr = np.array([]) 

    def add_data(self, data): 
     self._new_data.append(data) 

    def get_data_as_array(self): 
     self._flush() 
     return self._arr 

    def _flush(self): 
     if not self._new_data: 
      return 
#  self._arr = self._append1() 
#  self._arr = self._append2() 
     self._arr = self._append3() 
     self._new_data = [] 

    def _append1(self): 
     # always raises an error, because there are at least 2 refs: 
     # self._arr and local variable 'arr' in array_append() 
     return array_append(self._arr, self._new_data, refcheck = True) 

    def _append2(self): 
     # Does not raise an error, but unsafe in case there are other 
     # references to self._arr 
     return array_append(self._arr, self._new_data, refcheck = False) 

    def _append3(self): 
     # "inline" version: works if there are no other references 
     # to self._arr, but raises an error if there are. 
     added_len = len(self._new_data) 
     old_len = len(self._arr) 
     self._arr.resize(old_len + added_len, refcheck = True) 
     self._arr[old_len:] = self._new_data 
     return self._arr 

dc = DataCollector() 
dc.add_data(0) 
dc.add_data(1) 
print dc.get_data_as_array() 
dc.add_data(2) 
print dc.get_data_as_array() 
x = dc.get_data_as_array() # create an external reference 
print x.shape 
for i in xrange(5000): 
    dc.add_data(999) 
print dc.get_data_as_array() 
print x.shape 

问题:

  1. 有没有做什么,我试图做的(逐步建立numpy的数组)更好的(快)的方式?
  2. 有没有告诉resize()方法的方法:“执行refcheck,但忽略那个我知道安全的引用(或n个引用)”? (这将解决_append1问题()总是失败)

回答

1

resize方法有两个主要问题。首先,当用户调用get_data_as_array时,您返回对self._arr的引用。现在调整大小将根据您的实施做两件事之一。它会修改你给予用户的阵列,即用户将采取a.shape,并且形状将发生不可预测的变化。或者它会破坏该阵列,使其指向糟糕的内存。你可以通过总是让get_data_as_array返回self._arr.copy()来解决这个问题,但这使我想到了第二个问题。 resize实际上效率不高。我相信一般来说,调整大小必须分配新的内存并在每次调用数组时增加一个副本。此外,您现在需要在每次要将其返回给用户时复制阵列。

另一种方法是设计自己的dynamic array,这看起来是这样的:

class DynamicArray(object): 

    _data = np.empty(1) 
    data = _data[:0] 
    len = 0 
    scale_factor = 2 

    def append(self, values): 
     old_data = len(self.data) 
     total_data = len(values) + old_data 
     total_storage = len(self._data) 
     if total_storage < total_data: 
      while total_storage < total_data: 
       total_storage = np.ceil(total_storage * self.scale_factor) 
      self._data = np.empty(total_storage) 
      self._data[:old_data] = self.data 

     self._data[old_data:total_data] = values 
     self.data = self._data[:total_data] 

这应该是非常快的,因为你只需要增加数组数(N)次,你最多使用2 * N-1存储,其中N是阵列的最大尺寸。除了增长数组之外,您只是制作了_data的视图,它不涉及任何复制,并且应该是固定的时间。

希望这是有用的。

+0

这个解决方案的缺点是平均使用50%以上的内存,在我的情况下意味着几GB。但是,当然,在尺寸和速度之间有一个折衷,在我看来,速度可能更重要。我马上试一下。 – shx2 2013-03-07 07:08:51

1

我会用array.array()做数据收集:

import array 
a = array.array("d") 
for i in xrange(100): 
    a.append(i*2) 

,当你想要做一些计算所收集的数据每一次,将其转换为numpy.ndarraynumpy.frombuffer

b = np.frombuffer(a, dtype=float) 
print np.mean(b) 

ba共享数据存储,所以皈依非常快速。

+0

这很酷,解决了数字类型的问题。但是,在我现实生活中的问题中,我收集记录(并从中创建一个数据库),包含数字,字符串和对象。 – shx2 2013-03-05 14:13:17

+0

另外@HYRY能否解释一下这个数据共享如何在你的例子中起作用?当再次调用a.append()时共享数据需要重新分配时会发生什么? – shx2 2013-03-05 17:48:43

+0

'a.append()'可以重新分配内存,所以你不能使用前面创建的'ndarray',每次你需要用数据进行计算时,你可以通过'np.frombuffer'创建'ndarray' )'。 – HYRY 2013-03-05 22:12:30