1 /* Ergo, version 3.8, a program for linear scaling electronic structure
2  * calculations.
3  * Copyright (C) 2019 Elias Rudberg, Emanuel H. Rubensson, Pawel Salek,
4  * and Anastasia Kruchinina.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Primary academic reference:
20  * Ergo: An open-source program for linear-scaling electronic structure
21  * calculations,
22  * Elias Rudberg, Emanuel H. Rubensson, Pawel Salek, and Anastasia
23  * Kruchinina,
24  * SoftwareX 7, 107 (2018),
25  * <http://dx.doi.org/10.1016/j.softx.2018.03.005>
26  *
27  * For further information about Ergo, see <http://www.ergoscf.org>.
28  */
29 
30 /** @file AllocatorManager.h
31 
32     \brief Code for AllocatorManager class used for memory
33     allocation/deallocation in matrix library. The AllocatorManager
34     keeps a list of Allocator objects, creating new Allocator objects
35     when necessary.
36 
37     @author Elias Rudberg
38 */
39 
40 #ifndef MAT_ALLOCATORMANAGER_HEADER
41 #define MAT_ALLOCATORMANAGER_HEADER
42 
43 #include <stdexcept>
44 #include <list>
45 #include <string>
46 #include <sstream>
47 #include <iomanip> /* For setprecision */
48 #include <iostream>
49 #include <cstdlib>
50 #include "Allocator.h"
51 
52 namespace mat {
53 
54 template<class Treal>
55 class AllocatorManager
56 {
57  public:
init(size_t noOfRealsPerBuffer_,size_t noOfBuffers_)58   void init(size_t noOfRealsPerBuffer_,
59 	    size_t noOfBuffers_) {
60     if(noOfRealsPerBuffer != 0) {
61       // This means that the AllocatorManager has already been initialized.
62       // We allow this if the parameters are the same.
63       if(noOfRealsPerBuffer_ != noOfRealsPerBuffer || noOfBuffers_ != noOfBuffers)
64 	throw std::runtime_error("Error in AllocatorManager: "
65 				 "attempt to re-initialize with different parameters.");
66     }
67     if(noOfRealsPerBuffer_ <= 0 || noOfBuffers_ <= 0)
68       throw std::runtime_error("Error in AllocatorManager: bad input to init().");
69     noOfRealsPerBuffer = noOfRealsPerBuffer_;
70     noOfBuffers = noOfBuffers_;
71   }
72   static AllocatorManager & instance();
alloc(size_t n)73   Treal* alloc(size_t n) {
74     if(n != noOfRealsPerBuffer)
75       return new Treal[n];
76     pthread_mutex_lock(&mutex);
77     // Go through list to see if there is any free space.
78     typename std::list< Allocator<Treal>* >::iterator it = list.begin();
79     while(it != list.end()) {
80       if(!(*it)->isFull()) {
81 	// OK, found allocator that is not full. Use it.
82 	Treal* ptr = (*it)->alloc();
83 	pthread_mutex_unlock(&mutex);
84 	return ptr;
85       }
86       it++;
87     }
88     // We did not find any non-full existing allocator. Need to add a new one.
89     /* ELIAS NOTE 2016-06-30: Important to catch exceptions here so
90        that we unlock the mutex in case allocation fails, otherwise
91        program may hang later when trying to lock the mutex e.g. to
92        delete something after catching the exception elsewhere in the
93        code. This happened with some test runs on Triolith and it was
94        very annoying, took a long time to figure out why the runs
95        hanged. */
96     Allocator<Treal>* newAllocator;
97     try {
98       newAllocator = new Allocator<Treal>(noOfRealsPerBuffer,
99 					  noOfBuffers);
100     }
101     catch (const std::bad_alloc & e) {
102       size_t noOfBytesPerAllocator = noOfBuffers * noOfRealsPerBuffer * sizeof(Treal);
103       size_t totNoOfBytesAllocated = list.size() * noOfBytesPerAllocator;
104       std::cerr << "Error in AllocatorManager::alloc(): std::bad_alloc exception caught. Usage before error: list.size() = " << list.size()
105 		<< " --> " << (double)totNoOfBytesAllocated/1000000000 << " GB used. peakListSize = " << peakListSize << std::endl;
106       pthread_mutex_unlock(&mutex);
107       throw e;
108     }
109     catch(...) {
110       std::cerr << "Error in AllocatorManager::alloc(): exception caught (but not std::bad_alloc!?!)." << std::endl;
111       pthread_mutex_unlock(&mutex);
112       throw std::runtime_error("Error in AllocatorManager::alloc(): exception caught (but not std::bad_alloc!?!).");
113     }
114     list.push_back(newAllocator);
115     if(list.size() > peakListSize)
116       peakListSize = list.size();
117     Treal* ptr = newAllocator->alloc();
118     pthread_mutex_unlock(&mutex);
119     return ptr;
120   }
free(Treal * ptr)121   void free(Treal* ptr) {
122     pthread_mutex_lock(&mutex);
123     // Go through list to see if this ptr belongs to any allocator.
124     typename std::list< Allocator<Treal>* >::iterator it = list.begin();
125     while(it != list.end()) {
126       if((*it)->ownsPtr(ptr)) {
127 	(*it)->free(ptr);
128 	// Now check if allocator is empty; in that case we want to remove it.
129 	if((*it)->isEmpty()) {
130 	  delete *it;
131 	  list.erase(it);
132 	}
133 	pthread_mutex_unlock(&mutex);
134 	return;
135       }
136       it++;
137     }
138     delete [] ptr;
139     pthread_mutex_unlock(&mutex);
140   }
getStatistics()141   std::string getStatistics() {
142 
143     if(noOfRealsPerBuffer == 0) return "AllocatorManager is not initialized.";
144 
145     size_t noOfBytesPerAllocator = noOfBuffers * noOfRealsPerBuffer * sizeof(Treal);
146     size_t totNoOfBytesAllocated = list.size() * noOfBytesPerAllocator;
147     size_t peakNoOfBytesAllocated = peakListSize * noOfBytesPerAllocator;
148     size_t totNoOfBytesUsed = 0;
149     // Go through list to compute totNoOfBytesUsed
150     typename std::list< Allocator<Treal>* >::iterator it = list.begin();
151     while(it != list.end()) {
152       totNoOfBytesUsed += (size_t)((*it)->getNoOfOccupiedSlots()) * noOfRealsPerBuffer * sizeof(Treal);
153       it++;
154     }
155     std::stringstream ss;
156     ss << "AllocatorManager statistics: ";
157     ss << std::setprecision(3)
158        << " noOfRealsPerBuffer: " << noOfRealsPerBuffer
159        << " noOfBuffers: " << noOfBuffers
160        << " list.size(): " << list.size()
161        << ". "
162        << "Allocated: "  << (double)totNoOfBytesAllocated  / 1e9 << " GB, "
163        << "Used: "       << (double)totNoOfBytesUsed       / 1e9 << " GB, "
164        << "Peak alloc: " << (double)peakNoOfBytesAllocated/ 1e9 << " GB.";
165     return ss.str();
166   }
167  private:
AllocatorManager()168  AllocatorManager() : noOfRealsPerBuffer(0), noOfBuffers(0), peakListSize(0) {
169     pthread_mutex_init(&mutex, NULL);
170   }
~AllocatorManager()171   ~AllocatorManager() {
172     if(!list.empty()) {
173       std::cerr << "Error in AllocatorManager destructor: not empty." << std::endl;
174       abort();
175     }
176     // Go through list to free any allocators that are left.
177     typename std::list< Allocator<Treal>* >::iterator it = list.begin();
178     while(it != list.end()) {
179       delete *it;
180       it++;
181     }
182   }
183   std::list< Allocator<Treal>* > list;
184   size_t noOfRealsPerBuffer;
185   size_t noOfBuffers;
186   pthread_mutex_t mutex;
187   size_t peakListSize;
188 }; // end class AllocatorManager
189 
190 } /* end namespace mat */
191 
192 #endif
193