File: AllocatorManager.h

package info (click to toggle)
ergo 3.8.2-1.1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 17,568 kB
  • sloc: cpp: 94,763; ansic: 17,785; sh: 10,701; makefile: 1,403; yacc: 127; lex: 116; awk: 23
file content (192 lines) | stat: -rw-r--r-- 7,006 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* Ergo, version 3.8.2, a program for linear scaling electronic structure
 * calculations.
 * Copyright (C) 2023 Elias Rudberg, Emanuel H. Rubensson, Pawel Salek,
 * and Anastasia Kruchinina.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Primary academic reference:
 * Ergo: An open-source program for linear-scaling electronic structure
 * calculations,
 * Elias Rudberg, Emanuel H. Rubensson, Pawel Salek, and Anastasia
 * Kruchinina,
 * SoftwareX 7, 107 (2018),
 * <http://dx.doi.org/10.1016/j.softx.2018.03.005>
 * 
 * For further information about Ergo, see <http://www.ergoscf.org>.
 */

/** @file AllocatorManager.h

    \brief Code for AllocatorManager class used for memory
    allocation/deallocation in matrix library. The AllocatorManager
    keeps a list of Allocator objects, creating new Allocator objects
    when necessary.

    @author Elias Rudberg
*/

#ifndef MAT_ALLOCATORMANAGER_HEADER
#define MAT_ALLOCATORMANAGER_HEADER

#include <stdexcept>
#include <list>
#include <string>
#include <sstream>
#include <iomanip> /* For setprecision */
#include <iostream>
#include <cstdlib>
#include "Allocator.h"

namespace mat {

template<class Treal>
class AllocatorManager
{
 public:
  void init(size_t noOfRealsPerBuffer_,
	    size_t noOfBuffers_) {
    if(noOfRealsPerBuffer != 0) {
      // This means that the AllocatorManager has already been initialized.
      // We allow this if the parameters are the same.
      if(noOfRealsPerBuffer_ != noOfRealsPerBuffer || noOfBuffers_ != noOfBuffers)
	throw std::runtime_error("Error in AllocatorManager: "
				 "attempt to re-initialize with different parameters.");
    }
    if(noOfRealsPerBuffer_ <= 0 || noOfBuffers_ <= 0)
      throw std::runtime_error("Error in AllocatorManager: bad input to init().");
    noOfRealsPerBuffer = noOfRealsPerBuffer_;
    noOfBuffers = noOfBuffers_;
  }
  static AllocatorManager & instance();
  Treal* alloc(size_t n) {
    if(n != noOfRealsPerBuffer)
      return new Treal[n];
    pthread_mutex_lock(&mutex);
    // Go through list to see if there is any free space.
    typename std::list< Allocator<Treal>* >::iterator it = list.begin();
    while(it != list.end()) {
      if(!(*it)->isFull()) {
	// OK, found allocator that is not full. Use it.
	Treal* ptr = (*it)->alloc();
	pthread_mutex_unlock(&mutex);
	return ptr;
      }
      it++;
    }
    // We did not find any non-full existing allocator. Need to add a new one.
    /* ELIAS NOTE 2016-06-30: Important to catch exceptions here so
       that we unlock the mutex in case allocation fails, otherwise
       program may hang later when trying to lock the mutex e.g. to
       delete something after catching the exception elsewhere in the
       code. This happened with some test runs on Triolith and it was
       very annoying, took a long time to figure out why the runs
       hanged. */
    Allocator<Treal>* newAllocator;
    try {
      newAllocator = new Allocator<Treal>(noOfRealsPerBuffer,
					  noOfBuffers);
    }
    catch (const std::bad_alloc & e) {
      size_t noOfBytesPerAllocator = noOfBuffers * noOfRealsPerBuffer * sizeof(Treal);
      size_t totNoOfBytesAllocated = list.size() * noOfBytesPerAllocator;
      std::cerr << "Error in AllocatorManager::alloc(): std::bad_alloc exception caught. Usage before error: list.size() = " << list.size()
		<< " --> " << (double)totNoOfBytesAllocated/1000000000 << " GB used. peakListSize = " << peakListSize << std::endl;
      pthread_mutex_unlock(&mutex);
      throw e;
    }
    catch(...) {
      std::cerr << "Error in AllocatorManager::alloc(): exception caught (but not std::bad_alloc!?!)." << std::endl;
      pthread_mutex_unlock(&mutex);
      throw std::runtime_error("Error in AllocatorManager::alloc(): exception caught (but not std::bad_alloc!?!).");
    }
    list.push_back(newAllocator);
    if(list.size() > peakListSize)
      peakListSize = list.size();
    Treal* ptr = newAllocator->alloc();
    pthread_mutex_unlock(&mutex);
    return ptr;
  }
  void free(Treal* ptr) {
    pthread_mutex_lock(&mutex);
    // Go through list to see if this ptr belongs to any allocator.
    typename std::list< Allocator<Treal>* >::iterator it = list.begin();
    while(it != list.end()) {
      if((*it)->ownsPtr(ptr)) {
	(*it)->free(ptr);
	// Now check if allocator is empty; in that case we want to remove it.
	if((*it)->isEmpty()) {
	  delete *it;
	  list.erase(it);
	}
	pthread_mutex_unlock(&mutex);
	return;
      }
      it++;
    }
    delete [] ptr;
    pthread_mutex_unlock(&mutex);
  }
  std::string getStatistics() {

    if(noOfRealsPerBuffer == 0) return "AllocatorManager is not initialized.";

    size_t noOfBytesPerAllocator = noOfBuffers * noOfRealsPerBuffer * sizeof(Treal);
    size_t totNoOfBytesAllocated = list.size() * noOfBytesPerAllocator;
    size_t peakNoOfBytesAllocated = peakListSize * noOfBytesPerAllocator;
    size_t totNoOfBytesUsed = 0;
    // Go through list to compute totNoOfBytesUsed
    typename std::list< Allocator<Treal>* >::iterator it = list.begin();
    while(it != list.end()) {
      totNoOfBytesUsed += (size_t)((*it)->getNoOfOccupiedSlots()) * noOfRealsPerBuffer * sizeof(Treal);
      it++;
    }
    std::stringstream ss;
    ss << "AllocatorManager statistics: ";
    ss << std::setprecision(3)
       << " noOfRealsPerBuffer: " << noOfRealsPerBuffer 
       << " noOfBuffers: " << noOfBuffers 
       << " list.size(): " << list.size() 
       << ". "
       << "Allocated: "  << (double)totNoOfBytesAllocated  / 1e9 << " GB, "
       << "Used: "       << (double)totNoOfBytesUsed       / 1e9 << " GB, "
       << "Peak alloc: " << (double)peakNoOfBytesAllocated/ 1e9 << " GB.";
    return ss.str();
  }
 private:
 AllocatorManager() : noOfRealsPerBuffer(0), noOfBuffers(0), peakListSize(0) {
    pthread_mutex_init(&mutex, NULL);
  }
  ~AllocatorManager() {
    if(!list.empty()) {
      std::cerr << "Error in AllocatorManager destructor: not empty." << std::endl;
      abort();
    }
    // Go through list to free any allocators that are left.
    typename std::list< Allocator<Treal>* >::iterator it = list.begin();
    while(it != list.end()) {
      delete *it;
      it++;
    }
  }
  std::list< Allocator<Treal>* > list;
  size_t noOfRealsPerBuffer;
  size_t noOfBuffers;
  pthread_mutex_t mutex;
  size_t peakListSize;
}; // end class AllocatorManager

} /* end namespace mat */

#endif