How to use multiple cores with cmake + visual studio

For reference: https://gitlab.kitware.com/cmake/cmake/-/issues/20564

I am running cmake with cmake --build build --verbose -- /p:CL_MPCount=8 and it still uses only 1 core. Here is my output from the above command.

D:\github\nf2>cmake --build build --verbose -- /p:CL_MPCount=8
Change Dir: 'D:/github/nf2/build'

Run Build Command(s): "C:/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/amd64/MSBuild.exe" ALL_BUILD.vcxproj /p:Configuration=Debug /p:Platform=win32 /p:VisualStudioVersion=17.0 /v:n /p:CL_MPCount=8
MSBuild version 17.13.19+0d9f5a35a for .NET Framework
Build started 2025-04-30 2:08:04 PM.

Project "D:\github\nf2\build\ALL_BUILD.vcxproj" on node 1 (default targets).
Project "D:\github\nf2\build\ALL_BUILD.vcxproj" (1) is building "D:\github\nf2\build\ZERO_CHECK.vcxproj" (2) on no
de 1 (default targets).
PrepareForBuild:
  Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https 
  ://aka.ms/cpp/structured-output for more details.
InitializeBuildStatus:
  Creating "win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.        
  Touching "win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild".
PreBuildEvent:
  Checking File Globs
  setlocal
  "C:\Program Files\CMake\bin\cmake.exe" -P D:/github/nf2/build/CMakeFiles/VerifyGlobs.cmake
  if %errorlevel% neq 0 goto :cmEnd
  :cmEnd
  endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone
  :cmErrorLevel
  exit /b %1
  :cmDone
  if %errorlevel% neq 0 goto :VCEnd
  :VCEnd
CustomBuild:
  All outputs are up-to-date.
FinalizeBuildStatus:
  Deleting file "win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild".
  Touching "win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\ZERO_CHECK.lastbuildstate".
Done Building Project "D:\github\nf2\build\ZERO_CHECK.vcxproj" (default targets).

Project "D:\github\nf2\build\ALL_BUILD.vcxproj" (1) is building "D:\github\nf2\build\nf2.vcxproj" (3) on node 1 (d
efault targets).
PrepareForBuild:
  Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https 
  ://aka.ms/cpp/structured-output for more details.
InitializeBuildStatus:
  Creating "nf2.dir\Debug\nf2.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
  Touching "nf2.dir\Debug\nf2.tlog\unsuccessfulbuild".
CustomBuild:
  All outputs are up-to-date.
ClCompile:
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\bin\HostX86\x86\CL.exe /c /ID:
  \github\nf2\nf2 /I"D:\github\nf2\SDL3-devel-3.2.10-VC\SDL3-3.2.10\include" /I"D:\github\nf2\SDL3_image-devel-3.2 
  .4-VC\SDL3_image-3.2.4\include" /I"D:\github\nf2\SDL3_ttf-devel-3.2.2-VC\SDL3_ttf-3.2.2\include" /Zi /nologo /W1 
   /WX- /diagnostics:column /Od /Ob0 /Oy- /D _MBCS /D WIN32 /D _WINDOWS /D "CMAKE_INTDIR=\"Debug\"" /EHsc /RTC1 /M 
  Dd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /GR /std:c++20 /Fo"nf2.dir\Debug\\" /Fd"nf2.dir\Debug\vc1 
  43.pdb" /external:W1 /Gd /TP /analyze- /errorReport:queue D:\github\nf2\nf2\main.cpp D:\github\nf2\nf2\src\Game. 
  cpp D:\github\nf2\nf2\src\Interactions.cpp D:\github\nf2\nf2\src\MapRenderer.cpp D:\github\nf2\nf2\src\SaveLoad. 
  cpp D:\github\nf2\nf2\src\states\MainMenuState.cpp D:\github\nf2\nf2\src\states\PlayGameState.cpp D:\github\nf2\ 
  nf2\src\states\PlayUI.cpp D:\github\nf2\nf2\src\ui\Text.cpp D:\github\nf2\nf2\src\ui\UICheckBox.cpp D:\github\nf 
  2\nf2\src\ui\UIElement.cpp D:\github\nf2\nf2\src\ui\UIInput.cpp D:\github\nf2\nf2\src\ui\UIManager.cpp D:\github 
  \nf2\nf2\src\ui\UIRenderer.cpp D:\github\nf2\nf2\src\ui\UISlider.cpp
  main.cpp
 ... removed 25 some files
  UISlider.cpp
  Generating Code...
Link:
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\bin\HostX86\x86\link.exe /ERRO
  RREPORT:QUEUE /OUT:"D:\github\nf2\Debug\nf2.exe" /INCREMENTAL /ILK:"nf2.dir\Debug\nf2.ilk" /NOLOGO /LIBPATH:"D:/ 
  github/nf2/SDL3-devel-3.2.10-VC/SDL3-3.2.10/lib/x86" /LIBPATH:"D:/github/nf2/SDL3-devel-3.2.10-VC/SDL3-3.2.10/li 
  b/x86/Debug" /LIBPATH:"D:/github/nf2/SDL3_image-devel-3.2.4-VC/SDL3_image-3.2.4/lib/x86" /LIBPATH:"D:/github/nf2 
  /SDL3_image-devel-3.2.4-VC/SDL3_image-3.2.4/lib/x86/Debug" /LIBPATH:"D:/github/nf2/SDL3_ttf-devel-3.2.2-VC/SDL3_ 
  ttf-3.2.2/lib/x86" /LIBPATH:"D:/github/nf2/SDL3_ttf-devel-3.2.2-VC/SDL3_ttf-3.2.2/lib/x86/Debug" SDL3.lib SDL3_i 
  mage.lib SDL3_ttf.lib user32.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.l 
  ib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embe 
  d /DEBUG /PDB:"D:/github/nf2/Debug/nf2.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"D:/githu 
  b/nf2/build/Debug/nf2.lib" /MACHINE:X86 /SAFESEH  /machine:X86 nf2.dir\Debug\main.obj
...
  nf2.dir\Debug\StringUtil.obj
  nf2.vcxproj -> D:\github\nf2\Debug\nf2.exe
FinalizeBuildStatus:
  Deleting file "nf2.dir\Debug\nf2.tlog\unsuccessfulbuild".
  Touching "nf2.dir\Debug\nf2.tlog\nf2.lastbuildstate".
Done Building Project "D:\github\nf2\build\nf2.vcxproj" (default targets).

PrepareForBuild:
  Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https 
  ://aka.ms/cpp/structured-output for more details.
InitializeBuildStatus:
  Creating "win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
  Touching "win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild".
CustomBuild:
  All outputs are up-to-date.
FinalizeBuildStatus:
  Deleting file "win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild".
  Touching "win32\Debug\ALL_BUILD\ALL_BUILD.tlog\ALL_BUILD.lastbuildstate".
Done Building Project "D:\github\nf2\build\ALL_BUILD.vcxproj" (default targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:27.08

Things I have noticed:

  • /p:CL_MPCount=8 is passed to MSBuild.exe command.
  • I don’t see /MP flag on the CL.exe command so it doesn’t compile using multicore?

Let me know if you need more information or CMakeLists.txt etc.

Thanks.

I can’t explain why you don’t see /MP on the compiler command line, but I’ll offer a different avenue for you to explore. You may want to consider using the Multi-ToolTask scheduler. It provides parallelism similar in character to Ninja. It doesn’t have the trade-off between parallelism between targets versus within a target. There’s a couple of ways you can enable the Multi-ToolTask scheduler. One way would be to add a block like the following immediately after the project() command in your top level CMakeLists.txt:

if(NOT CMAKE_VS_GLOBALS MATCHES "(^|;)UseMultiToolTask=")
    list(APPEND CMAKE_VS_GLOBALS UseMultiToolTask=true)
endif()

if(NOT CMAKE_VS_GLOBALS MATCHES "(^|;)EnforceProcessCountAcrossBuilds=")
    list(APPEND CMAKE_VS_GLOBALS EnforceProcessCountAcrossBuilds=true)
endif()

Then add the /m option to msbuild or cmake --build.

I’d encourage you to read up on the Multi-ToolTask scheduler and how it provides improved parallelism. This blog article briefly introduces it. I think there’s a better blog article out there somewhere that goes into more detail, but I wasn’t able to find it just now.

I have not found a way to reliably get cmake to convey VS arbitrary parameters – instead we use cmake to copy a Directory.Build.props in the path tree of the projects with contents like this

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <!-- To enable project specific directory for temporary files. -->
    <UseProjectTMPDirectory>true</UseProjectTMPDirectory>

    <!-- To enable MSBuild Resource Manager in VS 2019 16.11 (on by default in VS 2022) -->
    <UseMSBuildResourceManager>false</UseMSBuildResourceManager>

    <!-- To allow executing more build operations in parallel-->
    <BuildPassReferences>true</BuildPassReferences>
  </PropertyGroup>

  <!-- Not compatible with SN-DBS -->
  <PropertyGroup>
    <!-- To enable MultiToolTask (MTT) mode. -->
    <UseMultiToolTask Condition="'$(UseMultiToolTask)' == ''">true</UseMultiToolTask>
    <EnforceProcessCountAcrossBuilds Condition="'$(UseMultiToolTask)' == 'true'">true</EnforceProcessCountAcrossBuilds>
    <!-- To enable experimental MTT ClServer mode, available in VS 2022. -->
    <!-- <EnableClServerMode>false</EnableClServerMode> -->
  </PropertyGroup>

  <ItemDefinitionGroup>
    <!--  Enable parallel file compilation (cl.exe /MP) -->
    <ClCompile>
      <MultiProcessorCompilation>true</MultiProcessorCompilation> 
    </ClCompile>
    <!--  Enable parallel execution of a custom build tool-->
    <CustomBuild>
      <BuildInParallel>true</BuildInParallel>
    </CustomBuild>
  </ItemDefinitionGroup>

  <PropertyGroup Condition="'$(NUMBER_OF_PROCESSORS)' != ''">
    <!-- https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/ -->
    <!-- https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions -->
    <CoresToBuildWith>$(NUMBER_OF_PROCESSORS)</CoresToBuildWith>

    <!-- Maximum parallel file builds per project -->
    <CL_MPCount Condition="'$(CL_MPCount)' == ''">$(CoresToBuildWith)</CL_MPCount>
    <MultiProcMaxCount Condition="'$(MultiProcMaxCount)' == ''">$(CoresToBuildWith)</MultiProcMaxCount>
  </PropertyGroup>
</Project>