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

Mac与Windows下预分配vs重新分配性能差异原因探究

内存分配方式的跨平台性能差异探究

问题背景

探究两种内存分配策略的耗时差异:

  • 预分配结构后,每次循环仅重置填充默认值
  • 每次循环重新分配完整结构

在Windows和WLS Ubuntu环境中,重新分配的耗时远高于预分配,但Mac平台上两者性能差距极小。疑问:Mac的编译器是否将重新分配优化为仅分配一次再填充?为何出现这种差异?

测试代码

#include <algorithm>
#include <random>
#include <thread>
#include <iostream>
#include <chrono>
#include <vector>
#include <numeric>

using namespace std;
template<typename T>
inline double getMs(T start, T end) {
    return double(
        std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
        .count()) /
        1000;
}

void pre_allocation(int size, int max_k) {
    vector<int> tau_star(size, 1440 * 3);
    tau_star.shrink_to_fit();

    vector<vector<int>> tau;
    tau.reserve(max_k);

    vector<vector<int>> pred_trip;
    pred_trip.reserve(max_k);

    vector<vector<int>> pred_stop;
    pred_stop.reserve(max_k);

    for (int k = 0; k < max_k; ++k) {
        pred_trip.emplace_back(size, -1);
        pred_stop.emplace_back(size, std::numeric_limits<size_t>::max());
        tau.emplace_back(size, 1440 * 3);
    }

    for (size_t i = 0; i < size; i++) {
        std::fill(tau_star.begin(), tau_star.end(), 1440 * 3);
        for (int k = 0; k < max_k; ++k) {
            std::fill(tau[k].begin(), tau[k].end(), 1440 * 3);
            std::fill(pred_trip[k].begin(), pred_trip[k].end(), -1);
            std::fill(pred_stop[k].begin(), pred_stop[k].end(), std::numeric_limits<size_t>::max());
        }
    }

}

void re_allocation(int size, int max_k) {
    for (size_t i = 0; i < size; i++) {
        vector<int> tau_star(size, 1440 * 3);
        tau_star.shrink_to_fit();

        vector<vector<int>> tau;
        tau.reserve(max_k);


        vector<vector<int>> pred_trip;
        pred_trip.reserve(max_k);

        vector<vector<int>> pred_stop;
        pred_stop.reserve(max_k);

        for (int k = 0; k < max_k; ++k) {
            pred_trip.emplace_back(size, -1);
            pred_stop.emplace_back(size, std::numeric_limits<size_t>::max());
            tau.emplace_back(size, 1440 * 3);
        }
    }
}


int main(int) {

    int size = 107333;
    int max_k = 3;

    auto start_pre_alloc = std::chrono::high_resolution_clock::now();
    pre_allocation(size, max_k);
    double elapsed_pre_alloc = getMs(start_pre_alloc, std::chrono::high_resolution_clock::now());
    
    auto start_re_alloc = std::chrono::high_resolution_clock::now();
    re_allocation(size, max_k);
    double elapsed_re_alloc = getMs(start_re_alloc, std::chrono::high_resolution_clock::now());

    printf("Time in pre-allocation: %.3f sec\n", elapsed_pre_alloc);
    printf("Time in RE-allocation: %.3f sec\n", elapsed_re_alloc);

    return 0;
}

测试结果

Windows

Time in pre-allocation: 11.617 sec
Time in RE-allocation: 53.679 sec

WLS Ubuntu

Time in pre-allocation: 15.749 sec
Time in RE-allocation: 81.905 sec

Mac

Time in pre-allocation: 9.396 sec
Time in RE-allocation: 12.408 sec

硬件与编译环境

Windows

  • CPU:11th Gen Intel(R) Core(TM) i7-11700KF @ 3.60GHz
  • RAM:16 GB DDR4
  • 编译环境:MS Visual Studio 2022

Mac

  • CPU:6-core Intel Core i9 @2.9 GHz
  • RAM:16GB 2400 MHz DDR4
  • 系统:macOS Big Sur Version 11.6.5
  • 编译环境:Apple Clang(通过g++命令调用),编译参数-O2 -ffast-math -DNDEBUG -Wall -std=c++17

WLS Ubuntu

  • 编译环境:GCC,编译参数-O2 -ffast-math -march=native -DNDEBUG -Wall -std=c++17 -DLINUX

编译使用的Makefile:

# Directory for my files
MYHOME          = ${PWD}
BIN             = ${MYHOME}/bin
LIB             = ${MYHOME}/lib
SRC             = ${MYHOME}

# For Linux:
# OPTFLAG = -O2 -ffast-math -march=native -DNDEBUG -Wall -std=c++17 -DLINUX -Wall

# For Mac:
OPTFLAG = -O2 -ffast-math -DNDEBUG -Wall -std=c++17 -DLINUX -Wall
LDFLAGS = -O2 -DNDEBUG -lm -pthread -std=c++17

COMPILER    = g++ ${OPTFLAG}
LINKER      = g++ ${LDFLAGS}


# Directory for output files
OUT_DIR=bin lib


# Command line tool
cli: ${OUT_DIR} ${SRC}/main.cpp
        ${COMPILER} -c ${SRC}/main.cpp -o ${LIB}/main.o
        ${LINKER} -o ${BIN}/main ${LIB}/main.o

原因分析

  1. 编译器优化策略差异
    Mac上的g++实际是Apple Clang的前端,在-O2优化级别下,它对循环内的重复内存分配做了激进优化:识别到每次循环创建的容器大小固定、生命周期严格限制在单次迭代内,因此会复用同一块内存区域(栈内存或堆内存缓存),避免每次都调用malloc/free这类开销较大的系统调用。

  2. 内存分配的隐式优化
    代码中re_allocation函数的容器仅做初始化填充,没有后续的读写操作(除了初始化),Clang的优化器可能进一步将堆分配转为栈分配(只要内存大小在栈允许范围内),或者利用操作系统的内存页缓存,大幅减少实际的内存申请/释放开销。

  3. 其他平台的保守优化
    Windows的MSVC和WLS的GCC在处理这类场景时优化策略相对保守,更严格遵循代码的字面逻辑,每次循环都会执行完整的内存申请、初始化、释放流程,导致耗时显著高于预分配策略。

若要验证这一点,可以尝试:

  • 在re_allocation中对容器元素做实际读写操作(比如修改后输出),观察优化是否被抑制
  • 添加编译参数-fno-elide-constructors关闭构造函数省略,或-O0关闭优化,看耗时是否上升

内容的提问来源于stack exchange,提问作者Claudio Tomasi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:37:07