[gold][patch] Control caching of memory mapped regions

Cary Coutant ccoutant@google.com
Wed Mar 10 11:12:00 GMT 2010


When linking a huge binary, gold uses a lot of memory because it keeps
the input files mapped throughout the link. This can slow down the
link considerably and sometimes the linker runs out of memory. The
attached patch adds an option that can reduce gold's memory usage
considerably by letting it unmap the memory used by input files
between linker passes.

I had a bit of a dilemma with the option name. Ideally, I wanted to
use --[no-]keep-files-mapped, but that option already exists, to
control whether the linker maps files as a whole instead of piecemeal.
I chose to rename that option to --[no-]map-whole-files, which is
closer to its actual meaning, and adopt --keep-files-mapped for the
new control. The defaults are the same as before: --map-whole-files is
the default for 64-bit linkers, --no-map-whole-files is the default
for 32-bit linkers, and --keep-files-mapped is the default for either
case.

For mapping whole files, I changed it to create the initial whole file
mapping at the first request for any view, rather than as soon as the
file is opened. I also keep the whole file view on the list of views
for the file, making it eligible for removal.

I found a couple of gotchas in Add_symbols::run() and
Scan_relocs::run() -- gold was calling release() on the File object
before deleting the sd_ and rd_ objects, both of which contain several
File_view objects obtained from get_lasting_view(). With those
File_view objects still alive, release() was not actually clearing the
views.

Linking a 400MB binary, according to gold --stats output, with the
64-bit default --map-whole-files, the --no-keep-files-mapped option
reduced the total number of bytes mmaped at one time from 1011 MB to
only 32 MB; link time went down marginally from 19.6s to 19.3s (wall
time). According to ps, the memory high-water mark was reduced from
478,297 pages to 236,276 pages. Experiments with other combinations of
the two options showed that --no-keep-files-mapped
--no-map-whole-files increased link time slightly, while
--keep-files-mapped --no-map-whole-files increased link time quite a
bit (to 37s).

All tests pass on x86_64, debug and opt. OK to commit?

-cary

        * fileread.cc (File_read::~File_read): Don't delete whole_file_view_.
        (File_read::open[1]): Remove initial mapping of whole_file_view_.
        (File_read::open[2]): Add whole_file_view_ to list of views.
        (File_read::make_view): Remove test of whole_file_view_.
        (File_read::find_or_make_view): Create whole_file_view_ if
        necessary.
        (File_read::clear_views): Replace bool parameter with enum;
        adjust all callers.  Don't delete views with unowned data;
        do delete cached views and views from archives if
        --no-keep-files-mapped is set.  Set whole_file_view_ to NULL
        if clearing the corresponding view.
        * fileread.h (File_read::Clear_views_mode): New enum.
        (File_read::View::is_data_owned): New method.
        (File_read::clear_views): Replace bool parameter
        with enum; adjust all callers.
        * options.h (General_options): Change keep_files_mapped option;
        add map_whole_files.
        * readsyms.cc (Add_symbols::run): Delete sd_ object before
        releasing the file.
        * reloc.cc (Scan_relocs::run): Delete rd_ object before releasing
        the file.
-------------- next part --------------
Index: fileread.cc
===================================================================
RCS file: /cvs/src/src/gold/fileread.cc,v
retrieving revision 1.61
diff -u -p -r1.61 fileread.cc
--- fileread.cc	24 Feb 2010 20:50:59 -0000	1.61
+++ fileread.cc	10 Mar 2010 06:54:35 -0000
@@ -115,9 +115,7 @@ File_read::~File_read()
       this->is_descriptor_opened_ = false;
     }
   this->name_.clear();
-  this->clear_views(true);
-  if (this->whole_file_view_)
-    delete this->whole_file_view_;
+  this->clear_views(CLEAR_VIEWS_ALL);
 }
 
 // Open the file.
@@ -144,23 +142,6 @@ File_read::open(const Task* task, const 
       this->size_ = s.st_size;
       gold_debug(DEBUG_FILES, "Attempt to open %s succeeded",
                  this->name_.c_str());
-
-      // Options may not yet be ready e.g. when reading a version
-      // script.  We then default to --no-keep-files-mapped.
-      if (parameters->options_valid()
-	  && parameters->options().keep_files_mapped())
-        {
-          const unsigned char* contents = static_cast<const unsigned char*>(
-              ::mmap(NULL, this->size_, PROT_READ, MAP_PRIVATE,
-                     this->descriptor_, 0));
-          if (contents == MAP_FAILED)
-            gold_fatal(_("%s: mmap failed: %s"), this->filename().c_str(),
-                       strerror(errno));
-          this->whole_file_view_ = new View(0, this->size_, contents, 0, false,
-                                            View::DATA_MMAPPED);
-          this->mapped_bytes_ += this->size_;
-        }
-
       this->token_.add_writer(task);
     }
 
@@ -180,6 +161,7 @@ File_read::open(const Task* task, const 
   this->name_ = name;
   this->whole_file_view_ = new View(0, size, contents, 0, false,
                                     View::DATA_NOT_OWNED);
+  this->add_view(this->whole_file_view_);
   this->size_ = size;
   this->token_.add_writer(task);
   return true;
@@ -226,7 +208,7 @@ File_read::release()
   // for releasing the descriptor.
   if (this->object_count_ <= 1)
     {
-      this->clear_views(false);
+      this->clear_views(CLEAR_VIEWS_NORMAL);
       if (this->is_descriptor_opened_)
 	{
 	  release_descriptor(this->descriptor_, false);
@@ -428,7 +410,7 @@ File_read::make_view(off_t start, sectio
     }
 
   File_read::View* v;
-  if (this->whole_file_view_ != NULL || byteshift != 0)
+  if (byteshift != 0)
     {
       unsigned char* p = new unsigned char[psize + byteshift];
       memset(p, 0, byteshift);
@@ -484,6 +466,15 @@ File_read::find_or_make_view(off_t offse
 	byteshift = (target_size / 8) - byteshift;
     }
 
+  // If --map-whole-files is set, make sure we have a
+  // whole file view.  Options may not yet be ready, e.g.,
+  // when reading a version script.  We then default to
+  // --no-keep-files-mapped.
+  if (this->whole_file_view_ == NULL
+      && parameters->options_valid()
+      && parameters->options().map_whole_files())
+    this->whole_file_view_ = this->make_view(0, this->size_, 0, cache);
+
   // Try to find a View with the required BYTESHIFT.
   File_read::View* vshifted;
   File_read::View* v = this->find_view(offset + start, size,
@@ -691,25 +682,31 @@ File_read::clear_view_cache_marks()
 // the next object.
 
 void
-File_read::clear_views(bool destroying)
+File_read::clear_views(Clear_views_mode mode)
 {
+  bool keep_files_mapped = (parameters->options_valid()
+			    && parameters->options().keep_files_mapped());
   Views::iterator p = this->views_.begin();
   while (p != this->views_.end())
     {
       bool should_delete;
-      if (p->second->is_locked())
+      if (p->second->is_locked() || !p->second->is_data_owned())
 	should_delete = false;
-      else if (destroying)
+      else if (mode == CLEAR_VIEWS_ALL)
 	should_delete = true;
-      else if (p->second->should_cache())
+      else if (p->second->should_cache() && keep_files_mapped)
 	should_delete = false;
-      else if (this->object_count_ > 1 && p->second->accessed())
+      else if (this->object_count_ > 1
+      	       && p->second->accessed()
+      	       && mode != CLEAR_VIEWS_ARCHIVE)
 	should_delete = false;
       else
 	should_delete = true;
 
       if (should_delete)
 	{
+	  if (p->second == this->whole_file_view_)
+	    this->whole_file_view_ = NULL;
 	  delete p->second;
 
 	  // map::erase invalidates only the iterator to the deleted
@@ -720,7 +717,6 @@ File_read::clear_views(bool destroying)
 	}
       else
 	{
-	  gold_assert(!destroying);
 	  p->second->clear_accessed();
 	  ++p;
 	}
@@ -736,7 +732,7 @@ File_read::clear_views(bool destroying)
 	}
       else
 	{
-	  gold_assert(!destroying);
+	  gold_assert(mode != CLEAR_VIEWS_ALL);
 	  ++q;
 	}
     }
Index: fileread.h
===================================================================
RCS file: /cvs/src/src/gold/fileread.h,v
retrieving revision 1.42
diff -u -p -r1.42 fileread.h
--- fileread.h	24 Feb 2010 20:50:59 -0000	1.42
+++ fileread.h	10 Mar 2010 06:54:36 -0000
@@ -170,7 +170,7 @@ class File_read
   // interface, and it would be nice to have something more automatic.
   void
   clear_uncached_views()
-  { this->clear_views(false); }
+  { this->clear_views(CLEAR_VIEWS_ARCHIVE); }
 
   // A struct used to do a multiple read.
   struct Read_multiple_entry
@@ -214,6 +214,17 @@ class File_read
   get_mtime();
 
  private:
+  // Control for what views to clear.
+  enum Clear_views_mode
+  {
+    // Clear uncached views not used by an archive.
+    CLEAR_VIEWS_NORMAL,
+    // Clear all uncached views (including in an archive).
+    CLEAR_VIEWS_ARCHIVE,
+    // Clear all views (i.e., we're destroying the file).
+    CLEAR_VIEWS_ALL
+  };
+
   // This class may not be copied.
   File_read(const File_read&);
   File_read& operator=(const File_read&);
@@ -303,6 +314,10 @@ class File_read
     accessed() const
     { return this->accessed_; }
 
+    bool
+    is_data_owned() const
+    { return this->data_ownership_ != DATA_NOT_OWNED; }
+
    private:
     View(const View&);
     View& operator=(const View&);
@@ -366,7 +381,7 @@ class File_read
 
   // Clear the file views.
   void
-  clear_views(bool);
+  clear_views(Clear_views_mode);
 
   // The size of a file page for buffering data.
   static const off_t page_size = 8192;
Index: options.h
===================================================================
RCS file: /cvs/src/src/gold/options.h,v
retrieving revision 1.141
diff -u -p -r1.141 options.h
--- options.h	6 Mar 2010 02:34:13 -0000	1.141
+++ options.h	10 Mar 2010 06:54:36 -0000
@@ -774,11 +774,14 @@ class General_options
   DEFINE_special(just_symbols, options::TWO_DASHES, '\0',
                  N_("Read only symbol values from FILE"), N_("FILE"));
 
-  DEFINE_bool(keep_files_mapped, options::TWO_DASHES, '\0',
+  DEFINE_bool(map_whole_files, options::TWO_DASHES, '\0',
 	      sizeof(void*) >= 8,
               N_("Map whole files to memory (default on 64-bit hosts)"),
               N_("Map relevant file parts to memory (default on 32-bit "
                  "hosts)"));
+  DEFINE_bool(keep_files_mapped, options::TWO_DASHES, '\0', true,
+              N_("Keep files mapped across passes (default)"),
+              N_("Release mapped files after each pass"));
 
   DEFINE_special(library, options::TWO_DASHES, 'l',
                  N_("Search for library LIBNAME"), N_("LIBNAME"));
Index: readsyms.cc
===================================================================
RCS file: /cvs/src/src/gold/readsyms.cc,v
retrieving revision 1.40
diff -u -p -r1.40 readsyms.cc
--- readsyms.cc	12 Feb 2010 03:23:26 -0000	1.40
+++ readsyms.cc	10 Mar 2010 06:54:36 -0000
@@ -470,6 +470,8 @@ Add_symbols::run(Workqueue*)
 
   if (!this->input_objects_->add_object(this->object_))
     {
+      delete this->sd_;
+      this->sd_ = NULL;
       this->object_->release();
       delete this->object_;
     }
@@ -477,10 +479,10 @@ Add_symbols::run(Workqueue*)
     {
       this->object_->layout(this->symtab_, this->layout_, this->sd_);
       this->object_->add_symbols(this->symtab_, this->sd_, this->layout_);
+      delete this->sd_;
+      this->sd_ = NULL;
       this->object_->release();
     }
-  delete this->sd_;
-  this->sd_ = NULL;
 }
 
 // Class Start_group.
Index: reloc.cc
===================================================================
RCS file: /cvs/src/src/gold/reloc.cc,v
retrieving revision 1.54
diff -u -p -r1.54 reloc.cc
--- reloc.cc	12 Feb 2010 04:33:53 -0000	1.54
+++ reloc.cc	10 Mar 2010 06:54:36 -0000
@@ -179,9 +179,9 @@ void
 Scan_relocs::run(Workqueue*)
 {
   this->object_->scan_relocs(this->symtab_, this->layout_, this->rd_);
-  this->object_->release();
   delete this->rd_;
   this->rd_ = NULL;
+  this->object_->release();
 }
 
 // Return a debugging name for the task.


More information about the Binutils mailing list