欢迎光临
我们一直在努力

CMake / make 编译时“用到的内存”到底是什么,以及它和服务器物理内存之间的关系。

这其实是一个在构建大型项目(尤其是 CMake + make)时很关键但容易混淆的点。
我们一步步讲清楚——CMake / make 编译时“用到的内存”到底是什么,以及它和服务器物理内存之间的关系。


🧩 一、首先区分:CMake vs make

  • CMake:只是一个 构建系统生成器,它根据 CMakeLists.txt 生成构建规则(比如 Makefile)。
    👉 CMake 本身几乎不消耗多少内存。
  • make:真正执行编译、链接、依赖分析的工具。
    👉 编译时的内存消耗主要发生在 make 运行阶段。

🧠 二、make 编译时内存用在哪?

当你执行:

make -j8

时,make 会启动多个编译进程(一般是调用 gcc / g++ 等),这些进程在编译过程中会大量占用内存,主要分为几部分:

内存用途说明典型消耗
预处理缓存 处理 #include、宏展开时,编译器要加载头文件、展开宏 几十 MB
语法分析与中间表示 编译器把源代码解析为 AST(抽象语法树)和中间代码(IR) 几百 MB(大项目)
优化阶段 各种优化(如内联、寄存器分配、循环展开等)需要保留 IR、副本 可达上 GB
链接阶段(ld / lld) 把多个 .o 文件合并成可执行文件或库,需要加载所有目标文件 几百 MB~数 GB
并行编译 -jN 同时编译多个文件时,每个编译进程都要独立占用内存 N 倍放大

💡 三、“编译用的内存” = 实际消耗服务器的物理内存(RAM)

是的,这就是服务器的内存。

  • 每个编译进程使用的内存都来自服务器的物理内存(RAM)。
  • 如果物理内存不足,系统会开始使用 swap(磁盘虚拟内存),这时性能急剧下降。
  • 一旦 swap 也用完,就会触发 OOM(Out Of Memory)被 Linux 杀进程。

所以你看到编译时:

make -j32

可能非常快,但如果服务器只有 16GB 内存,反而会因为 OOM 导致失败。


📊 四、举个真实的例子

假设你的项目有 1000 个 .cpp 文件。

情况 A:单线程编译

make -j1

  • 同时只启动 1 个编译进程。
  • 每个编译进程大约占 300 MB。
  • 内存占用约 300 MB,总体安全。

情况 B:多线程编译

make -j16

  • 同时启动 16 个编译进程。
  • 每个占 300 MB → 总计约 4.8 GB。
  • 如果加上链接阶段,峰值可能达到 8~10 GB。

👉 所以“编译内存”是所有活跃编译任务占用的总和。


🧮 五、怎么估算编译需要多少内存?

经验公式:

总内存需求 ≈ 每个编译任务内存 × 并行数 + 链接阶段峰值

例如:

  • 每个编译任务约 500MB;
  • 你用 make -j8;
  • 链接峰值 3GB;

则需要大约 500×8 + 3000 = 7000 MB ≈ 7GB 内存。


🧰 六、如何控制内存占用

  • 减少并行度

    make -j4

    并行越多,内存消耗越高。

  • 分阶段编译

    • 先编译静态库;
    • 再单独链接主程序;
      避免一次性全部编译。
  • 使用 ccache

    • 可缓存已编译的目标文件;
    • 减少重复编译。
  • 使用更高效的链接器

    export LD=ld.lld

    LLVM 的 lld 比 GNU ld 快且占用更低。

  • 避免大文件和模板膨胀

    • 模板和头文件过多会显著增加编译内存。

  • 📌 七、总结一句话

    💬 “CMake / make 编译时用的内存” 指的就是 编译器和链接器在执行过程中使用的服务器物理内存(RAM)。
    内存越大,允许的并行编译任务越多,编译速度越快;
    内存不足则会导致 swap 或 OOM。


    💡我在工作中遇到这种情况,写了一个小 Python 脚本,根据服务器内存自动计算推荐的 make -j 并行度,这样就不会内存打爆了。

    下面是完整可运行版本


    🐍 Python 脚本:智能计算 make 并行度

    import os
    import psutil
    import math
    import multiprocessing

    def recommend_make_jobs(avg_mem_per_job_mb=500, reserve_mem_ratio=0.2):
    """
    根据服务器总内存和CPU核心数,自动计算make -j并行度。

    参数:
    avg_mem_per_job_mb: 每个编译任务平均占用内存 (MB),默认 500MB
    reserve_mem_ratio: 预留比例(例如0.2表示预留20%系统内存不参与编译)
    返回:
    推荐的并行数 (int)
    """

    # 1️⃣ 获取系统总内存(MB)
    total_mem_mb = psutil.virtual_memory().total / (1024 ** 2)

    # 2️⃣ 获取CPU核心数
    cpu_count = multiprocessing.cpu_count()

    # 3️⃣ 计算可用内存(去掉预留)
    usable_mem_mb = total_mem_mb * (1 reserve_mem_ratio)

    # 4️⃣ 基于内存限制计算最大并行度
    max_jobs_by_mem = usable_mem_mb // avg_mem_per_job_mb

    # 5️⃣ 推荐值 = min(内存允许的并行数, CPU核心数)
    recommended_jobs = int(max(1, min(max_jobs_by_mem, cpu_count)))

    print("🧮 系统总内存: {:.1f} GB".format(total_mem_mb / 1024))
    print("💻 CPU核心数: {}".format(cpu_count))
    print("📦 假设每个编译任务使用约 {} MB 内存".format(avg_mem_per_job_mb))
    print("🛡️ 预留内存比例: {}%".format(int(reserve_mem_ratio * 100)))
    print("✅ 推荐并行度: make -j{}".format(recommended_jobs))
    return recommended_jobs

    if __name__ == "__main__":
    recommend_make_jobs(avg_mem_per_job_mb=600) # 可根据工程大小调整


    💡 用法说明

  • 运行脚本:

    python3 recommend_make_jobs.py

  • 输出示例:

    🧮 系统总内存: 31.3 GB
    💻 CPU核心数: 16
    📦 假设每个编译任务使用约 600 MB 内存
    🛡️ 预留内存比例: 20%
    ✅ 推荐并行度: make -j12

  • 然后你可以直接:

    make -j12


  • ⚙️ 参数微调建议

    项目类型每任务内存建议 (MB)
    普通 C 工程 300–400
    C++ 模板较多 600–1000
    大型 C++ 工程(ROS, 内核) 1000–2000
    含大量链接操作 800+

    自动执行 make** 的版本(比如:检测完推荐值 → 自动执行 make -jN)?

    在检测完系统内存和 CPU 核心数后,自动计算最优 -j 值并执行 make -jN。
    同时还带上日志输出、错误处理与自定义参数,适合放进 CI/CD 或本地构建脚本。


    🐍 自动执行 make 的智能编译脚本

    import os
    import psutil
    import math
    import multiprocessing
    import subprocess
    import argparse
    import shutil
    import sys

    def recommend_make_jobs(avg_mem_per_job_mb=500, reserve_mem_ratio=0.2):
    """计算推荐的 make -j 值"""
    total_mem_mb = psutil.virtual_memory().total / (1024 ** 2)
    cpu_count = multiprocessing.cpu_count()
    usable_mem_mb = total_mem_mb * (1 reserve_mem_ratio)
    max_jobs_by_mem = usable_mem_mb // avg_mem_per_job_mb
    recommended_jobs = int(max(1, min(max_jobs_by_mem, cpu_count)))

    print(f"🧮 系统总内存: {total_mem_mb / 1024:.1f} GB")
    print(f"💻 CPU核心数: {cpu_count}")
    print(f"📦 假设每个任务占用约 {avg_mem_per_job_mb} MB")
    print(f"🛡️ 预留内存比例: {int(reserve_mem_ratio * 100)}%")
    print(f"✅ 推荐并行度: make -j{recommended_jobs}")
    return recommended_jobs

    def run_make(jobs, make_target=None, log_file="build.log"):
    """执行 make 编译并保存日志"""
    make_cmd = ["make", f"-j{jobs}"]
    if make_target:
    make_cmd.append(make_target)

    print("\\n🚀 正在执行:", " ".join(make_cmd))
    print(f"📝 日志输出: {os.path.abspath(log_file)}")

    with open(log_file, "w") as logf:
    process = subprocess.Popen(make_cmd, stdout=logf, stderr=subprocess.STDOUT)
    process.wait()

    if process.returncode == 0:
    print("✅ 编译成功!日志已保存。")
    else:
    print("❌ 编译失败!请查看日志文件。")

    def main():
    parser = argparse.ArgumentParser(description="智能计算 make 并行度并执行编译")
    parser.add_argument("–avg-mem", type=int, default=600, help="每个任务平均内存占用(MB)")
    parser.add_argument("–reserve", type=float, default=0.2, help="预留内存比例 (0.2=20%)")
    parser.add_argument("–target", type=str, default=None, help="make 目标 (如 all / clean / install)")
    parser.add_argument("–log", type=str, default="build.log", help="日志文件名")
    args = parser.parse_args()

    if shutil.which("make") is None:
    print("❌ 未找到 make 命令,请确认已安装 make。")
    sys.exit(1)

    jobs = recommend_make_jobs(args.avg_mem, args.reserve)
    run_make(jobs, args.target, args.log)

    if __name__ == "__main__":
    main()


    💡 使用示例

    1️⃣ 普通使用(自动检测并编译)

    python3 smart_make.py

    2️⃣ 指定目标(例如 make clean all)

    python3 smart_make.py –target all

    3️⃣ 调整内存假设(例如每个任务 800MB)

    python3 smart_make.py –avg-mem 800

    4️⃣ 预留更少内存(例如只保留 10%)

    python3 smart_make.py –reserve 0.1

    5️⃣ 指定日志文件名

    python3 smart_make.py –log output_build.log


    ⚙️ 输出示例

    🧮 系统总内存: 31.3 GB
    💻 CPU核心数: 16
    📦 假设每个任务占用约 600 MB
    🛡️ 预留内存比例: 20%
    ✅ 推荐并行度: make -j12

    🚀 正在执行: make -j12 all
    📝 日志输出: /home/user/build.log
    ✅ 编译成功!日志已保存。


    “内存监控”功能**,在编译过程中实时检测是否内存不足(自动暂停或警告)?

    那样就能避免 CI 机器因为 OOM 被系统杀掉。
    加上“实时内存监控”功能后,脚本就能成为一个智能自适应编译器:
    在编译过程中持续检测系统剩余内存,如果内存即将耗尽,它可以:

  • 提前警告(打印提示或写入日志)
  • 自动暂停 make 进程(防止 OOM)
  • 待内存恢复后继续 或直接中止,避免系统被强制杀掉
  • 下面是带 内存监控功能的增强版脚本👇


    🧠 智能内存感知版 smart_make_monitor.py

    import os
    import psutil
    import math
    import multiprocessing
    import subprocess
    import argparse
    import shutil
    import sys
    import threading
    import time
    import signal

    def recommend_make_jobs(avg_mem_per_job_mb=500, reserve_mem_ratio=0.2):
    total_mem_mb = psutil.virtual_memory().total / (1024 ** 2)
    cpu_count = multiprocessing.cpu_count()
    usable_mem_mb = total_mem_mb * (1 reserve_mem_ratio)
    max_jobs_by_mem = usable_mem_mb // avg_mem_per_job_mb
    recommended_jobs = int(max(1, min(max_jobs_by_mem, cpu_count)))

    print(f"🧮 系统总内存: {total_mem_mb / 1024:.1f} GB")
    print(f"💻 CPU核心数: {cpu_count}")
    print(f"📦 每个任务假设占用 {avg_mem_per_job_mb} MB")
    print(f"🛡️ 预留比例: {int(reserve_mem_ratio * 100)}%")
    print(f"✅ 推荐并行度: make -j{recommended_jobs}")
    return recommended_jobs

    def monitor_memory(process: subprocess.Popen, min_free_mem_ratio=0.1, check_interval=3):
    """
    后台线程监控系统内存,如果可用内存低于阈值则发出警告/中止
    """

    print(f"👀 启动内存监控线程 (阈值: {int(min_free_mem_ratio*100)}% 可用内存)")
    while process.poll() is None:
    mem = psutil.virtual_memory()
    free_ratio = mem.available / mem.total
    if free_ratio < min_free_mem_ratio:
    print(f"\\n⚠️ [警告] 系统可用内存仅剩 {free_ratio*100:.1f}%!可能发生 OOM!")
    print("🛑 建议:减少 -j 并行数 或 增加物理内存。")
    # 可选:强制终止 make 进程,避免 OOM
    process.send_signal(signal.SIGSTOP)
    print("⏸️ 已暂时暂停 make 进程以保护系统。按 Ctrl+C 结束或等待释放内存。")
    while psutil.virtual_memory().available / psutil.virtual_memory().total < (min_free_mem_ratio + 0.05):
    time.sleep(3)
    print("✅ 内存恢复正常,继续编译。")
    process.send_signal(signal.SIGCONT)
    time.sleep(check_interval)

    def run_make(jobs, make_target=None, log_file="build.log", min_free_mem_ratio=0.1):
    make_cmd = ["make", f"-j{jobs}"]
    if make_target:
    make_cmd.append(make_target)

    print("\\n🚀 执行命令:", " ".join(make_cmd))
    print(f"📝 日志输出到: {os.path.abspath(log_file)}")

    with open(log_file, "w") as logf:
    process = subprocess.Popen(make_cmd, stdout=logf, stderr=subprocess.STDOUT)

    # 启动内存监控线程
    monitor_thread = threading.Thread(target=monitor_memory, args=(process, min_free_mem_ratio))
    monitor_thread.daemon = True
    monitor_thread.start()

    process.wait()

    if process.returncode == 0:
    print("✅ 编译成功!日志已保存。")
    else:
    print("❌ 编译失败!请查看日志文件。")

    def main():
    parser = argparse.ArgumentParser(description="智能内存感知 make 执行器")
    parser.add_argument("–avg-mem", type=int, default=600, help="每任务平均内存占用(MB)")
    parser.add_argument("–reserve", type=float, default=0.2, help="预留系统内存比例 (0.2=20%)")
    parser.add_argument("–min-free", type=float, default=0.1, help="最低可用内存比例,低于此值将暂停编译 (0.1=10%)")
    parser.add_argument("–target", type=str, default=None, help="make 目标 (如 all / clean / install)")
    parser.add_argument("–log", type=str, default="build.log", help="日志文件名")
    args = parser.parse_args()

    if shutil.which("make") is None:
    print("❌ 未找到 make,请确认系统已安装。")
    sys.exit(1)

    jobs = recommend_make_jobs(args.avg_mem, args.reserve)
    run_make(jobs, args.target, args.log, args.min_free)

    if __name__ == "__main__":
    main()


    🧰 使用示例

    python3 smart_make_monitor.py –avg-mem 800 –reserve 0.2 –min-free 0.1 –target all

    📊 运行效果

    🧮 系统总内存: 31.3 GB
    💻 CPU核心数: 16
    📦 每个任务假设占用 800 MB
    🛡️ 预留比例: 20%
    ✅ 推荐并行度: make -j12

    🚀 执行命令: make -j12 all
    📝 日志输出到: /home/user/build.log
    👀 启动内存监控线程 (阈值: 10% 可用内存)

    ⚠️ [警告] 系统可用内存仅剩 7.8%!可能发生 OOM!
    ⏸️ 已暂时暂停 make 进程以保护系统。
    ✅ 内存恢复正常,继续编译。


    ✨ 特点

    ✅ 自动计算最优并行度
    ✅ 实时监控系统内存
    ✅ 低内存自动暂停、恢复
    ✅ 所有日志写入文件(适合 CI)
    ✅ 跨平台(Linux、macOS、WSL)


    赞(0)
    未经允许不得转载:171主机测评 » CMake / make 编译时“用到的内存”到底是什么,以及它和服务器物理内存之间的关系。
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址