Firstly, I've never contributed something via git-send-email before.
I hope I've gotten everything correct but apologies in advance if I've
made any mistakes in the process.  If either of these are accepted, I
don't have write access to commit them.

I have a use case where we generate C++ at runtime and compile it with
GCC, in an environment where memory is a significant constraint.  We
use a PCH to speed this up, but memory usage is still considerable,
even for trivial translation units.

I tracked this down to two main issues:

 - mapping the PCH saves little memory: the objects a translation unit
   writes to are scattered over the file, so most pages end up dirty
 - every translation unit redoes the template instantiations that the
   header's inline functions need, even if it uses nothing from the
   header

As far as I can tell, it would be possible to prevent GCC from writing
to the memory mapped from the PCH.  However, in practice this would be
a large piece of work: the writes come from many places, including
template instantiation, gimplification, the symbol table, type flags
and hash tables stored in the PCH.  Patch 1 takes a pragmatic approach
of laying out the PCH so that the objects that do get written are
spread across as few pages as possible.

Patch 2 addresses the second issue by adding -fpch-instantiate-templates,
which does the template instantiations when the PCH is created
(mirroring the option available in Clang).

I think both are fairly simple, and together they significantly reduce
the runtime and memory usage of translation units using a large PCH.

To reproduce:

  curl -LO https://cburr.web.cern.ch/gcc-pch-reproducer/JIT.h.xz && unxz 
JIT.h.xz
  curl -LO https://cburr.web.cern.ch/gcc-pch-reproducer/functors92.cpp
  curl -LO https://cburr.web.cern.ch/gcc-pch-reproducer/peakmem.sh && chmod +x 
peakmem.sh
  FLAGS="-std=c++23 -O3 -march=x86-64-v3 -fPIC -w"
  g++ $FLAGS -fpch-instantiate-templates -x c++-header JIT.h -o JIT.h.gch
  touch empty.cpp
  ./peakmem.sh g++ $FLAGS --param=ggc-min-expand=10 -include JIT.h -c empty.cpp
  ./peakmem.sh g++ $FLAGS --param=ggc-min-expand=10 -include JIT.h -c 
functors92.cpp

JIT.h is 10 MB of preprocessed C++23 (preprocessed with GCC 15,
minimally edited to build with trunk), functors92.cpp 92 generated
functions using it.  I use the peakmem.sh script to more easily show the
peak private memory of cc1plus.

Peak private memory and wall time:

                          empty TU          92 functions
  trunk               476 MB  12.5 s     728 MB  32.7 s
  patch 1             356 MB   7.8 s     606 MB  25.9 s
  patches 1+2         149 MB   1.9 s     395 MB  19.5 s

With -fpch-instantiate-templates, building the PCH takes 30 s and
1761 MB instead of 21 s and 1363 MB, and it grows from 500 MB to 653 MB.

The patches help other headers too.  For nlohmann/json 3.12.0
(-std=c++17 -O2), with Clang 23 for comparison ("use" is a 15-line
function that parses, modifies and dumps a JSON object):

                                    empty TU         use
  GCC trunk, no PCH            182 MB  1.7 s     276 MB  6.0 s
  Clang 23, no PCH              89 MB  2.7 s     114 MB  5.3 s
  GCC trunk                     75 MB  0.7 s     189 MB  5.2 s
  GCC patch 1                   63 MB  0.6 s     175 MB  5.0 s
  Clang 23                      50 MB  1.4 s      77 MB  3.7 s
  GCC patches 1+2, with option  11 MB  0.05 s    135 MB  4.4 s
  Clang 23, with option          8 MB  0.07 s     53 MB  2.8 s

LibreOffice saw a significant speed-up from the Clang option, see the
discussion that led to it being added:
https://lists.llvm.org/pipermail/cfe-dev/2019-May/062371.html

Clang can't compile JIT.h, since it was preprocessed with GCC, so to
compare with Clang I used an older and larger version of the same
header, compiled from its original sources:

                                    empty TU         92 functions
  GCC trunk                    856 MB  31 s     1252 MB  84 s
  Clang 23                     433 MB  17 s      713 MB  44 s
  GCC patches 1+2, with option 290 MB   7 s      781 MB  55 s
  Clang 23, with option         86 MB   3 s      396 MB  31 s

For reference, Clang's PCH is also much smaller:

                        older header    nlohmann/json
  GCC                      974 MB            92 MB
  Clang 23                 178 MB            14 MB
  GCC, with option        1256 MB           119 MB
  Clang 23, with option    211 MB            19 MB

Bootstrapped and regtested on x86_64-pc-linux-gnu (make -k check and
check-c++-all), no regressions.

Chris Burr (2):
  ggc: Lay out PCH objects in the order in which they are reached
  c++: Add -fpch-instantiate-templates

 gcc/c-family/c-pch.cc                         |  8 +++
 gcc/c-family/c.opt                            |  4 ++
 gcc/c-family/c.opt.urls                       |  3 +
 gcc/cp/decl2.cc                               |  5 ++
 gcc/doc/invoke.texi                           | 31 +++++++++-
 gcc/ggc-common.cc                             | 61 +++++++++++++++++--
 gcc/ggc.h                                     |  3 +
 gcc/testsuite/g++.dg/pch/pch-instantiate-1.C  |  7 +++
 gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs | 56 +++++++++++++++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-2.C  |  1 +
 gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs | 15 +++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-3.C  | 11 ++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs | 14 +++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-4.C  | 11 ++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs | 34 +++++++++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-5.C  | 20 ++++++
 gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs | 22 +++++++
 .../g++.dg/template/pch-instantiate-1.C       | 22 +++++++
 .../g++.dg/template/pch-instantiate-2.C       | 21 +++++++
 .../g++.dg/template/pch-instantiate-3.C       | 19 ++++++
 .../g++.dg/template/pch-instantiate-4.C       | 24 ++++++++
 21 files changed, 385 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-1.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-2.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-3.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-4.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-5.C
 create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs
 create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-1.C
 create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-2.C
 create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-3.C
 create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-4.C

-- 
2.47.1

Reply via email to