有没有办法强制单个进程将其大部分/全部内存交换到磁盘(非根)

有没有办法强制单个进程将其大部分/全部内存交换到磁盘(非根)

我们目前正在分析工作中某些机器的性能,我们发现有些机器的交换次数很多,当这种情况开始发生时,由于我们使用的是旋转磁盘,所以系统就开始变得缓慢。

我有一个理论:启动一个进程并完成所有初始化操作可能比等待进程将其内存从磁盘加载出来更快。

为了测试这个理论,我想强制一个进程交换到磁盘,我想看看它被换出后需要多长时间才能响应,并将其与新进程启动进行比较,有没有办法强制进程交换而不修改交换设置或运行内存不足的机器?

我没有这些盒子上的root权限,所以基本上我需要一种非特权的方式来执行/提示内核来做到这一点。

答案1

假设这是 Linux,您可能希望将 swappiness 变量设置为其最大值。这基本上告诉内存管理尽可能地交换出当前未使用的所有内存。此值决定了您的 Linux 内核将多少(以及多久)将 RAM 内容复制到交换区

永久设置:

  1. 编辑/etc/sysconfig.conf

  2. 将以下行添加到文件 vm.swappiness=100

  3. 重启

或者暂时设置到下次重启:

sudo sysctl vm.swappiness=100

这将允许您针对您的工作组合测试您的假设。

答案2

是的,就是你实际上可以在较新的内核上执行此操作,但您仍然需要 root。

您可以使用一个特殊的系统调用process_madvise,它可以让您从外部进程中选择内存映射,并且可以通过平面将MADVISE_PAGEOUT内存故障转移到交换区。

madvise(2)

MADV_PAGEOUT (since Linux 5.4)
       Reclaim  a given range of pages.  
       This is done to free up memory occupied by these pages. 
       If a page is anonymous, it will be swapped out.  
       If a page is file-backed and dirty, it will be written
       back to the backing storage.  The advice might be ignored for 
       some pages in the range when it is not applicable.

这个想法让我很感兴趣,所以我写了一个程序来尝试一下。你可以用 编译它gcc -o swapout swapout.c。你给它传递一个 pid:

$ sudo ./swapout 1217683
Swapping out 3075.215 MiB of process memory
Swapped out 2479.293 MiB of process memory

我的测试表明内核并不总是尊重你的意愿,但是我手动检查了一个占用大量内存的 pid,发现内核至少交换了一些的记忆。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <sys/pidfd.h>

ssize_t process_madvise(
        int fd,
        const struct iovec *vec,
        size_t vlen,
        int advice,
        int flags)
{
    return syscall(SYS_process_madvise, fd, vec, vlen, advice, flags);
}

/* Retrieve a list of all anonymous mappings that this process
 * is using */
size_t get_anonymous_mappings(
        pid_t pid,
        struct iovec **vecs,
        int *nvecs)
{
    char buf[1024] = {0};
    FILE *maps = NULL;
    struct iovec *v = NULL;
    size_t total = 0;
    int nv = 0;

    v = alloca(32768 * sizeof(struct iovec));

    /* Open the map file for pid */
    snprintf(buf, 1023, "/proc/%lu/maps", pid);
    maps = fopen(buf, "re");
    if (!maps) {
        fprintf(stderr, "Error: Cannot access process maps: %m\n");
        goto fail;
    }

    while (1) {
        char buf[1024] = {0};
        char path[256] = {0};
        char perm[32] = {0};
        uint64_t off, inode;
        uint8_t *e, *b, dev_min, dev_maj;
        int rc;

        if (!fgets(buf, 1023, maps))
            break;

        memset(path, 0, 256);
        memset(perm, 0, 32);
        rc = sscanf(buf, "%llx-%llx %s %llx %hhx:%hhx %llu %s", 
                &b, &e, perm, &off, &dev_maj, &dev_min, &inode, path);
        if (rc < 6) 
            continue;

        /* Must all be 0 to be an anonymous mapping */
        if ((off || dev_min || dev_maj || inode)) 
            continue;

        if (strcmp(path, "[heap]") != 0 && strcmp(path, "[stack]") != 0 && path[0] != 0)
            continue;

        total += (e-b);
        v[nv].iov_base = b;
        v[nv].iov_len = (size_t)(e-b);
        nv++;
    }

    if (ferror(maps)) {
        fprintf(stderr, "Error reading file: %m\n");
        goto fail;
    }

    *nvecs = nv;
    *vecs = calloc(nv, sizeof(struct iovec));
    if (!(*vecs)) {
        fprintf(stderr, "Cannot allocate vectors: %m\n");
        goto fail;
    }

    memcpy((*vecs), v, nv * sizeof(struct iovec));

    fclose(maps);
    return total;

fail:
    if (maps)
        fclose(maps);
    if (*vecs)
        free(*vecs);
    return -1;
}

int main(
        int argc,
        char **argv)
{
    int n, rc;
    int pfd = -1;
    struct iovec *vecs = NULL;
    int nvecs = -1;
    size_t mapsz;
    pid_t pid = -1;

    if (argc < 2) {
        fprintf(stderr, "Error: Give process ID of process to swap out.\n");
        exit(EXIT_FAILURE);
    }

    pid = atoi(argv[1]);
    if (pid <= 0) {
        fprintf(stderr, "Error: Pid is not a valid process ID.\n");
        exit(EXIT_FAILURE);
    }

    pfd = pidfd_open(pid, 0);
    if (pfd < 0) {
        fprintf(stderr, "Error: Cannot access process: %m\n");
        exit(EXIT_FAILURE);
    }

    mapsz = get_anonymous_mappings(pid, &vecs, &nvecs);
    if (mapsz < 0)
        exit(EXIT_FAILURE);

    printf("Swapping out %.3f MiB of process memory\n", (double)mapsz / 1048576.0);

    /* Only 1024 vectors can be processed at once */
    n=0;
    mapsz = 0;
    while (n < nvecs) {
        int l;
        if (nvecs-n > 1024)
            l = 1024;
        else
            l = nvecs-n;

        rc = process_madvise(pfd, &vecs[n], l, MADV_PAGEOUT, 0);
        if (rc < 0) {
            fprintf(stderr, "Cannot swap out process: %m\n");
            exit(EXIT_FAILURE);
        }

        n += l;
        mapsz += rc;
    }

    printf("Swapped out %.3f MiB of process memory\n", (double)mapsz / 1048576.0);
    free(vecs);
    exit(EXIT_SUCCESS);
}

答案3

无论您做什么,都要收集实际应用程序的响应时间数据。记录用户响应时间、进程启动时间以及对您而言重要的任何其他性能指标。

在 Linux 上,cgroup 可以设置内存限制,超过该限制,该组中的进程将被调出内存页面。systemd 服务可以使用指令设置此类限制MemoryLimit,请参阅手册页或RHEL 的资源管理指南请记住,systemd 单元可以属于每个用户,不需要 root 权限。

不要花很多时间尝试模拟低内存条件,它不太可能像生产工作负载那样执行。如果您可以将应用程序更改为延迟启动或加载进程,请在测试中执行此操作,然后在生产中执行此操作。并测量结果。

相关内容