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