Почему копирование массивa>> 16 ГБ Numpy задает все его элементы в 0?

В моем дистрибутиве Anaconda Python копирование массива Numpy размером ровно 16 ГБ или больше (независимо от dtype) устанавливает все элементы копии в 0:

>>> np.arange(2 ** 31 - 1).copy()  # works fine
array([         0,          1,          2, ..., 2147483644, 2147483645,
       2147483646])
>>> np.arange(2 ** 31).copy()  # wait, what?!
array([0, 0, 0, ..., 0, 0, 0])
>>> np.arange(2 ** 32 - 1, dtype=np.float32).copy()
array([  0.00000000e+00,   1.00000000e+00,   2.00000000e+00, ...,
         4.29496730e+09,   4.29496730e+09,   4.29496730e+09], dtype=float32)
>>> np.arange(2 ** 32, dtype=np.float32).copy()
array([ 0.,  0.,  0., ...,  0.,  0.,  0.], dtype=float32)

Здесь np.__config__.show() для этого распределения:

blas_opt_info:
    library_dirs = ['/users/username/.anaconda3/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/users/username/.anaconda3/include']
    libraries = ['mkl_rt', 'pthread']
lapack_opt_info:
    library_dirs = ['/users/username/.anaconda3/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/users/username/.anaconda3/include']
    libraries = ['mkl_rt', 'pthread']
mkl_info:
    library_dirs = ['/users/username/.anaconda3/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/users/username/.anaconda3/include']
    libraries = ['mkl_rt', 'pthread']
openblas_lapack_info:
  NOT AVAILABLE
lapack_mkl_info:
    library_dirs = ['/users/username/.anaconda3/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/users/username/.anaconda3/include']
    libraries = ['mkl_rt', 'pthread']
blas_mkl_info:
    library_dirs = ['/users/username/.anaconda3/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/users/username/.anaconda3/include']
    libraries = ['mkl_rt', 'pthread']

Для сравнения здесь np.__config__.show() для моего системного дистрибутива Python, который не имеет этой проблемы:

blas_opt_info:
    define_macros = [('HAVE_CBLAS', None)]
    libraries = ['openblas', 'openblas']
    language = c
    library_dirs = ['/usr/local/lib']
openblas_lapack_info:
    define_macros = [('HAVE_CBLAS', None)]
    libraries = ['openblas', 'openblas']
    language = c
    library_dirs = ['/usr/local/lib']
openblas_info:
    define_macros = [('HAVE_CBLAS', None)]
    libraries = ['openblas', 'openblas']
    language = c
    library_dirs = ['/usr/local/lib']
lapack_opt_info:
    define_macros = [('HAVE_CBLAS', None)]
    libraries = ['openblas', 'openblas']
    language = c
    library_dirs = ['/usr/local/lib']
blas_mkl_info:
  NOT AVAILABLE

Мне интересно, является ли ускорение MKL проблемой. Я воспроизвел ошибку как на Python 2, так и на 3.

Ответ 1

Это просто предположение. На данный момент у меня нет никаких доказательств, подтверждающих следующие утверждения, но я предполагаю, что это простая проблема с переполнением:

>>> np.arange(2 ** 31 - 1).size
2147483647

Это просто самое большое значение int32:

>>> np.iinfo(np.int32)
iinfo(min=-2147483648, max=2147483647, dtype=int32)

Итак, когда у вас на самом деле есть массив размером 2147483648 (2**31), и использование int32 будет переполняться и давать фактическое отрицательное значение. Тогда, возможно, что-то вроде этого внутри метода numpy.ndarray.copy:

for (i = 0 ; i < size ; i ++) {
    newarray[i] = oldarray[i]
}

Но учитывая, что размер теперь отрицательный, цикл не будет выполняться, потому что 0 > -2147483648.

То, что новый массив фактически инициализирован нулями, странно, потому что не имеет смысла фактически помещать нули перед копированием массива (но это может быть что-то вроде в этом вопросе).

Опять же: это просто угадывание в этот момент, но это будет соответствовать поведению.