我需要打开一个巨大的内存映射。该文件为 1 TB。
然而我收到了 errno: ENOMEM 12 Cannot allocate memory
。我不明白是什么在阻碍我。请求RLIMIT_AS
值中的结果:18446744073709551615。这就足够了。我的系统也是64位的,所以并不是我的虚拟内存太小。ulimit -v
是ulimited
我用 python 创建了数据,np.lib.format.open_memmap
因此它在物理上是可能的。我正在尝试用 C 语言阅读它。Python 阅读没有问题,numpy.load('terabytearray.npy', mmap_mode='r')
可以。
这是一个最小的例子。
创建一个 numpy 数组,如下所示:
import numpy as np
shape = (75000, 5000000)
filename = 'datafile.obj'
if __name__ == '__main__':
arr = np.lib.format.open_memmap(filename, mode='w+', dtype=np.float32, shape=shape)
读它是这样的:
#include <stdbool.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <errno.h>
typedef enum {
CNPY_LE, /* little endian (least significant byte to most significant byte) */
CNPY_BE, /* big endian (most significant byte to least significant byte) */
CNPY_NE, /* no / neutral endianness (each element is a single byte) */
/* Host endianness is not supported because it is an incredibly bad idea to
use it for storage. */
} cnpy_byte_order;
typedef enum {
CNPY_B = 0, /* We want to use the values as index to the following arrays. */
CNPY_I1,
CNPY_I2,
CNPY_I4,
CNPY_I8,
CNPY_U1,
CNPY_U2,
CNPY_U4,
CNPY_U8,
CNPY_F4,
CNPY_F8,
CNPY_C8,
CNPY_C16,
} cnpy_dtype;
typedef enum {
CNPY_C_ORDER, /* C order (row major) */
CNPY_FORTRAN_ORDER, /* Fortran order (column major) */
} cnpy_flat_order;
typedef enum {
CNPY_SUCCESS, /* success */
CNPY_ERROR_FILE, /* some error regarding handling of a file */
CNPY_ERROR_MMAP, /* some error regarding mmaping a file */
CNPY_ERROR_FORMAT, /* file format error while reading some file */
} cnpy_status;
#define CNPY_MAX_DIM 4
typedef struct {
cnpy_byte_order byte_order;
cnpy_dtype dtype;
cnpy_flat_order order;
size_t n_dim;
size_t dims[CNPY_MAX_DIM];
char *raw_data;
size_t data_begin;
size_t raw_data_size;
} cnpy_array;
cnpy_status cnpy_open(const char * const fn, bool writable, cnpy_array *arr) {
assert(arr != NULL);
cnpy_array tmp_arr;
/* open, mmap, and close the file */
int fd = open(fn, writable? O_RDWR : O_RDONLY);
if (fd == -1) {
return CNPY_ERROR_FILE;
}
size_t raw_data_size = (size_t) lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
printf("%lu\n", raw_data_size);
if (raw_data_size == 0) {
close(fd); /* no point in checking for errors */
return CNPY_ERROR_FORMAT;
}
if (raw_data_size == SIZE_MAX) {
/* This is just because the author is too lazy to check for overflow on every pos+1 calculation. */
close(fd);
return CNPY_ERROR_FORMAT;
}
void *raw_data = mmap(
NULL,
raw_data_size,
PROT_READ | PROT_WRITE,
writable? MAP_SHARED : MAP_PRIVATE,
fd,
0
);
if (raw_data == MAP_FAILED) {
close(fd);
return CNPY_ERROR_MMAP;
}
if (close(fd) != 0) {
munmap(raw_data, raw_data_size);
return CNPY_ERROR_FILE;
}
/* parse the file */
// cnpy_status status = cnpy_parse(raw_data, raw_data_size, &tmp_arr); // library call ignore
// if (status != CNPY_SUCCESS) {
// munmap(raw_data, raw_data_size);
// return status;
// }
// *arr = tmp_arr;
return CNPY_SUCCESS;
}
int main(){
cnpy_array arr = {};
cnpy_status status = cnpy_open("datafile.obj", false, &arr);
printf("status %i\n",(int) status);
if(status != CNPY_SUCCESS){
printf("failure\n");
printf("errno %i\n", errno);
}
struct rlimit lim;
printf("getrlimit RLIMIT_AS %s\n", (getrlimit(RLIMIT_AS, &lim) == 0 ? "success" : "failure") );
printf("lim.rlim_cur %lu\n", lim.rlim_cur );
printf("lim.rlim_max %lu\n", lim.rlim_max );
printf("RLIM_INFINITY; %lu\n", RLIM_INFINITY );
return 0;
}
编译用
gcc -std=c11 -o mmap_testing main.c
我在用着〜quf/cnpy库中,我包含了相关部分,以使其与 numpy 的东西一起工作。
答案1
void *raw_data = mmap(
NULL,
raw_data_size,
PROT_READ | PROT_WRITE,
writable? MAP_SHARED : MAP_PRIVATE,
fd,
0
);
因此,当 时writable == false
,您请求与PROT_READ | PROT_WRITE
和 的映射MAP_PRIVATE
。这意味着您希望能够写入映射的内存,但不希望写入修改文件。因此,如果您执行这样的写入操作,则会生成一份副本(写入时复制,逐页),但它要保存在哪里呢?它无法写回磁盘上的文件,因此只能驻留在物理内存或交换区中。因此,此调用实际上是在请求分配或保留 1 TB 的实内存。想必你没有。
正如评论中所讨论的,启用过度使用将允许这个工作。在这种情况下,系统会假装保留 1 TB,但实际上并没有这样做。如果您确实写入了过多的内存,那么您的进程最终将被杀死,并且无法恢复(并且其他一些不相关的进程也可能被杀死)。
但听起来在这种情况下您实际上根本不需要写入该内存writable == false
,因此只需PROT_READ
单独使用即可。您可能想要的参数类似于PROT_READ | (writable ? PROT_WRITE : 0)
。