You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

mmap迭代分块映射大文件时段错误问题求助

分块mmap映射igraph向量的段错误问题与解决方案

问题场景

order向量加载时占用8MB内存,尝试通过分块mmap(CHUNK_SIZE=10240)迭代映射小块数据以降低内存开销,但程序在执行VECTOR(*order)[act_rank++] = actvect;访问索引11264时触发段错误。

疑问

  • 首次调用memory_map_order_iterative()后,chunk大小为10240,为何向量能访问到索引11263?
  • 第二次迭代调用memory_map_order_iterative()后,order向量本该访问10240至20479的索引,为何触发段错误?

期望逻辑

chunk1:0至10239,chunk2:10240至20479,以此类推。

迭代映射代码

#include <igraph.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

#define CHUNK_SIZE 10240

graph_error_t unmap_order(igraph_integer_t **mapped_order, igraph_integer_t chunk_size){
    if (munmap(*mapped_order, chunk_size * sizeof(igraph_integer_t)) == -1) {
        perror("munmap");
        return IGRAPH_EFILE;
    }
    *mapped_order = NULL;
    return IGRAPH_SUCCESS;
}

graph_error_t memory_map_order_iterative(igraph_vector_int_t *order, const char *order_filename, igraph_integer_t **mapped_order, igraph_integer_t no_of_elements, igraph_integer_t *remaining_elements, igraph_integer_t *offset) {
    int fd;

    fd = open(order_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        return IGRAPH_EFILE;
    }

    if (ftruncate(fd, no_of_elements * sizeof(igraph_integer_t)) == -1) {
        perror("ftruncate");
        close(fd);
        return IGRAPH_EFILE;
    }

    igraph_integer_t chunk_size = (*remaining_elements > CHUNK_SIZE) ? CHUNK_SIZE : *remaining_elements;

    *mapped_order = mmap(NULL, chunk_size * sizeof(igraph_integer_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, (*offset) * sizeof(igraph_integer_t));
    if (*mapped_order == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return IGRAPH_EFILE;
    }

    *offset += chunk_size;
    *remaining_elements -= chunk_size;

    close(fd);

    igraph_vector_int_view(order, *mapped_order, chunk_size);

    return IGRAPH_SUCCESS;
}

graph_error_t igraph_bfs(const igraph_t *graph,
               igraph_vector_int_t *order,
               void *extra) {

    const igraph_integer_t no_of_nodes = igraph_vcount(graph);

    igraph_dqueue_int_t Q;
    igraph_integer_t actroot = 0;
    igraph_integer_t act_rank = 0;

    IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100);

    igraph_integer_t *mapped_order = NULL;
    igraph_integer_t remaining_elements = 999998;
    igraph_integer_t offset = 0;
    igraph_error_t is_map_success;

    is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset);
    if (is_map_success != IGRAPH_SUCCESS) {
        return is_map_success;
    }

    while (1) {
        IGRAPH_CHECK(igraph_dqueue_int_push(&Q, actroot));
        IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0));

        while (!igraph_dqueue_int_empty(&Q)) {
            igraph_integer_t actvect = igraph_dqueue_int_pop(&Q);

            if (order) {
                if(act_rank>0 && act_rank%CHUNK_SIZE==0){
                    igraph_error_t unmap_error = unmap_order(&mapped_order, CHUNK_SIZE);
                    if (unmap_error != IGRAPH_SUCCESS) {
                        return unmap_error;
                    }
                    igraph_error_t is_map_success;
                    is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset);
                    if (is_map_success != IGRAPH_SUCCESS) {
                        return is_map_success;
                    }
                }
                VECTOR(*order)[act_rank++] = actvect;
            }
        }
    }
    return IGRAPH_SUCCESS;
}

int main(void) {

    igraph_t graph;
    FILE *file = fopen("random_graph.edgelist", "r");
    if (!file) {
        return 1;
    }

    if (igraph_read_graph_edgelist(&graph, file, 0, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS) {
    } else {
        return 1;
    }

    igraph_vector_int_t order;

    igraph_vector_int_init(&order, 0);

    igraph_bfs(&graph, &order,/*extra=*/ NULL);

    igraph_destroy(&graph);

    return 0;
}

全量映射代码

void unmap(igraph_integer_t *mapped_order, size_t size) {
    if (munmap(mapped_order, size) == -1) {
        perror("munmap");
    }
}

graph_error_t memory_map_order(const char *order_filename, igraph_integer_t **mapped_order, igraph_integer_t no_of_elements, igraph_vector_int_t *order) {
    int fd;

    fd = open(order_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        return IGRAPH_EFILE;
    }

    if (ftruncate(fd, no_of_elements * sizeof(igraph_integer_t)) == -1) {
        perror("ftruncate");
        close(fd);
        return IGRAPH_EFILE;
    }

    *mapped_order = mmap(NULL, no_of_elements * sizeof(igraph_integer_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (*mapped_order == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return IGRAPH_EFILE;
    }

    close(fd);

    igraph_vector_int_view(order, *mapped_order, no_of_elements);

    return IGRAPH_SUCCESS;
}

问题原因分析

  1. 首次调用后能访问超出chunk的索引:
    igraph的igraph_vector_int_view仅创建内存视图,不做任何边界校验。当你用VECTOR(*order)[act_rank]访问时,即使act_rank超过chunk_size(10240),igraph不会阻止你访问映射区域外的内存。此时只是刚好内存地址暂时有效(未触发段错误),但属于未定义行为,随时可能崩溃。

  2. 第二次调用后触发段错误:
    第二次映射后,igraph_vector_int_view将order视图绑定到了新的chunk(对应文件偏移10240的位置),此时视图的内存起始地址是新映射区域,索引0对应文件的10240位置。但你仍用全局的act_rank(此时为10240)作为索引访问,相当于访问新映射区域的10240位置,而新chunk只有10240个元素(索引范围0-10239),直接越界到映射区域外,触发段错误。


解决方案

1. 修正索引访问逻辑

每次切换chunk后,使用相对当前chunk的索引访问视图,而非全局act_rank:

if (order) {
    // 计算当前chunk的全局起始索引
    igraph_integer_t chunk_start = offset - ((*remaining_elements > CHUNK_SIZE) ? CHUNK_SIZE : *remaining_elements);
    // 计算当前元素在chunk内的相对索引
    igraph_integer_t local_rank = act_rank - chunk_start;

    if(act_rank > 0 && act_rank % CHUNK_SIZE == 0){
        // 卸载旧chunk
        igraph_error_t unmap_error = unmap_order(&mapped_order, CHUNK_SIZE);
        if (unmap_error != IGRAPH_SUCCESS) {
            return unmap_error;
        }
        // 映射新chunk
        is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset);
        if (is_map_success != IGRAPH_SUCCESS) {
            return is_map_success;
        }
        // 更新新chunk的起始索引和相对索引
        chunk_start = offset - CHUNK_SIZE;
        local_rank = 0;
    }
    // 用相对索引访问视图
    VECTOR(*order)[local_rank] = actvect;
    act_rank++;
}

2. 优化ftruncate调用

每次调用memory_map_order_iterative都执行ftruncate冗余且可能带来问题,仅在第一次创建文件时执行:
修改函数参数,增加is_first标记:

graph_error_t memory_map_order_iterative(igraph_vector_int_t *order, const char *order_filename, igraph_integer_t **mapped_order, igraph_integer_t no_of_elements, igraph_integer_t *remaining_elements, igraph_integer_t *offset, int is_first) {
    int fd;

    fd = open(order_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        return IGRAPH_EFILE;
    }

    // 仅首次调用时截断文件到目标大小
    if (is_first) {
        if (ftruncate(fd, no_of_elements * sizeof(igraph_integer_t)) == -1) {
            perror("ftruncate");
            close(fd);
            return IGRAPH_EFILE;
        }
    }

    igraph_integer_t chunk_size = (*remaining_elements > CHUNK_SIZE) ? CHUNK_SIZE : *remaining_elements;

    *mapped_order = mmap(NULL, chunk_size * sizeof(igraph_integer_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, (*offset) * sizeof(igraph_integer_t));
    if (*mapped_order == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return IGRAPH_EFILE;
    }

    *offset += chunk_size;
    *remaining_elements -= chunk_size;

    close(fd);

    igraph_vector_int_view(order, *mapped_order, chunk_size);

    return IGRAPH_SUCCESS;
}

调用时传入标记:

// 首次调用
is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset, 1);

// 后续切换chunk时调用
is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset, 0);

3. 修正remaining_elements初始化

硬编码的999998与实际节点数不匹配,改为用no_of_nodes:

igraph_integer_t remaining_elements = no_of_nodes;

内容的提问来源于stack exchange,提问作者SSM Tariq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:08:10