Mantid
Loading...
Searching...
No Matches
DiskBuffer.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
9#include <sstream>
10#include <utility>
11
12using namespace Mantid::Kernel;
13
14namespace Mantid::Kernel {
15
16//----------------------------------------------------------------------------------------------
20 : m_writeBufferSize(50), m_writeBufferUsed(0), m_nObjectsToWrite(0), m_free(), m_free_bySize(m_free.get<1>()),
21 m_fileLength(0) {
22 m_free.clear();
23}
24
25//----------------------------------------------------------------------------------------------
31DiskBuffer::DiskBuffer(uint64_t m_writeBufferSize)
32 : m_writeBufferSize(m_writeBufferSize), m_writeBufferUsed(0), m_nObjectsToWrite(0), m_free(),
33 m_free_bySize(m_free.get<1>()), m_fileLength(0) {
34 m_free.clear();
35}
36
37//---------------------------------------------------------------------------------------------
47 if (item == nullptr)
48 return;
49 // if (!m_useWriteBuffer) return;
50
51 if (item->getBufPostion()) // already in the buffer and probably have changed
52 // its size in memory
53 {
54 // forget old memory size
55 std::unique_lock<std::mutex> uniqueLock(m_mutex);
57 // add new size
58 size_t newMemorySize = item->getDataMemorySize();
59 m_writeBufferUsed += newMemorySize;
60 uniqueLock.unlock();
61 item->setBufferSize(newMemorySize);
62 } else {
63 std::lock_guard<std::mutex> lock(m_mutex);
64 m_toWriteBuffer.push_front(item);
67 }
68
69 // Should we now write out the old data?
72}
73
74//---------------------------------------------------------------------------------------------
83 if (item == nullptr)
84 return;
85 // have it ever been in the buffer?
86 std::unique_lock<std::mutex> uniqueLock(m_mutex);
87 auto opt2it = item->getBufPostion();
88 if (opt2it) {
90 m_toWriteBuffer.erase(*opt2it);
91 } else {
92 return;
93 }
94
95 // indicate to the object that it is not stored in memory any more
96 item->clearBufferState();
97 uniqueLock.unlock();
98
99 // Mark the amount of space used on disk as free
100 if (item->wasSaved())
101 this->freeBlock(item->getFilePosition(), item->getFileSize());
102}
103
104//---------------------------------------------------------------------------------------------
109
110 std::lock_guard<std::mutex> _lock(m_mutex);
111 // Holder for any objects that you were NOT able to write.
112 std::list<ISaveable *> couldNotWrite;
113 size_t objectsNotWritten(0);
114 size_t memoryNotWritten(0);
115
116 // Iterate through the list
117 auto it = m_toWriteBuffer.begin();
118 auto it_end = m_toWriteBuffer.end();
119
120 ISaveable *obj = nullptr;
121
122 for (; it != it_end; ++it) {
123 obj = *it;
124 if (!obj->isBusy()) {
125 uint64_t NumObjEvents = obj->getTotalDataSize();
126 uint64_t fileIndexStart;
127 if (!obj->wasSaved()) {
128 fileIndexStart = this->allocate(NumObjEvents);
129 // Write to the disk; this will call the object specific save function;
130 // Prevent simultaneous file access (e.g. write while loading)
131 obj->saveAt(fileIndexStart, NumObjEvents);
132 } else {
133 uint64_t NumFileEvents = obj->getFileSize();
134 if (NumObjEvents != NumFileEvents) {
135 // Event list changed size. The MRU can tell us where it best fits
136 // now.
137 fileIndexStart = this->relocate(obj->getFilePosition(), NumFileEvents, NumObjEvents);
138 // Write to the disk; this will call the object specific save
139 // function;
140 obj->saveAt(fileIndexStart, NumObjEvents);
141 } else // despite object size have not been changed, it can be modified
142 // other way. In this case, the method which changed the data
143 // should set dataChanged ID
144 {
145 if (obj->isDataChanged()) {
146 fileIndexStart = obj->getFilePosition();
147 // Write to the disk; this will call the object specific save
148 // function;
149 obj->saveAt(fileIndexStart, NumObjEvents);
150 // this is questionable operation, which adjust file size in case
151 // when the file postions were allocated externaly
152 if (fileIndexStart + NumObjEvents > m_fileLength)
153 m_fileLength = fileIndexStart + NumObjEvents;
154 } else // just clean the object up -- it just occupies memory
155 obj->clearDataFromMemory();
156 }
157 }
158 // tell the object that it has been removed from the buffer
159 obj->clearBufferState();
160 } else // object busy
161 {
162 // The object is busy, can't write. Save it for later
163 couldNotWrite.emplace_back(obj);
164 // When a prefix or postfix operator is applied to a function argument,
165 // the value of the argument is
166 // NOT GUARANTEED to be incremented or decremented before it is passed to
167 // the function.
168 memoryNotWritten += obj->setBufferPosition(--couldNotWrite.end());
169 objectsNotWritten++;
170 }
171 }
172
173 // use last object to clear NeXus buffer and actually write data to HDD
174 if (obj) {
175 // NXS needs to flush the writes to file by closing and re-opening the data
176 // block.
177 // For speed, it is best to do this only once per write dump, using last
178 // object saved
179 obj->flushData();
180 }
181
182 // Exchange with the new map you built out of the not-written blocks.
183 m_toWriteBuffer.swap(couldNotWrite);
184 m_writeBufferUsed = memoryNotWritten;
185 m_nObjectsToWrite = objectsNotWritten;
186}
187
188//---------------------------------------------------------------------------------------------
192 // Now write everything out.
194}
195
196//---------------------------------------------------------------------------------------------
204void DiskBuffer::freeBlock(uint64_t const pos, uint64_t const size) {
205 if (size == 0 || size == std::numeric_limits<uint64_t>::max())
206 return;
207 std::lock_guard<std::mutex> lock(m_freeMutex);
208
209 // Make the block
210 FreeBlock newBlock(pos, size);
211 // Insert it
212 std::pair<freeSpace_t::iterator, bool> p = m_free.insert(newBlock);
213
214 // Failed insert? Should not happen since the map is NOT unique
215 // Or, if the map has only 1 item then it cannot do any merging. This solves a
216 // hanging bug in MacOS. Refs #3652
217 if (!p.second || m_free.size() <= 1) {
218 return;
219 }
220
221 // This is where we inserted
222 freeSpace_t::iterator it = p.first;
223 if (it != m_free.begin()) {
224 freeSpace_t::iterator it_before = it;
225 --it_before;
226 // There is a block before
227 FreeBlock block_before = *it_before;
228 if (FreeBlock::merge(block_before, newBlock)) {
229 // Change the map by replacing the old "before" block with the new merged
230 // one
231 m_free.replace(it_before, block_before);
232 // Remove the block we just inserted
233 m_free.erase(it);
234 // For cases where the new block was between two blocks.
235 newBlock = block_before;
236 it = it_before;
237 }
238 }
239 // Get an iterator to the block AFTER this one
240 freeSpace_t::iterator it_after = it;
241 ++it_after;
242 // There is a block after
243 if (it_after != m_free.end()) {
244 FreeBlock block_after = *it_after;
245 if (FreeBlock::merge(newBlock, block_after)) {
246 // Change the map by replacing the old "new" block with the new merged one
247 m_free.replace(it, newBlock);
248 // Remove the block that was after this one
249 m_free.erase(it_after);
250 }
251 }
252}
253
254//---------------------------------------------------------------------------------------------
260 std::lock_guard<std::mutex> lock(m_freeMutex);
261
262 freeSpace_t::iterator it = m_free.begin();
263 FreeBlock thisBlock;
264 thisBlock = *it;
265
266 while (it != m_free.end()) {
267 // Get iterator to the block after "it".
268 freeSpace_t::iterator it_after = it;
269 ++it_after;
270
271 if (FreeBlock::merge(thisBlock, *it_after)) {
272 // Change the map by replacing the old "before" block with the new merged
273 // one
274 m_free.replace(it, thisBlock);
275 // Remove the block that was merged out
276 m_free.erase(it_after);
277 // And stay at this iterator to
278 } else {
279 // Move on to the next block
280 ++it;
281 thisBlock = *it;
282 }
283 }
284}
285
286//---------------------------------------------------------------------------------------------
293uint64_t DiskBuffer::allocate(uint64_t const newSize) {
294 std::unique_lock<std::mutex> uniqueLock(m_freeMutex);
295
296 // Now, find the first available block of sufficient size.
297 freeSpace_bySize_t::iterator it;
298 bool putAtFileEnd = true;
299 if (m_free.size() > 0) {
300 // Unless there is nothing in the free space map
301 it = m_free_bySize.lower_bound(newSize);
302 putAtFileEnd = (it == m_free_bySize.end());
303 }
304
305 if (putAtFileEnd) {
306 // No block found
307 // Go to the end of the file.
308 uint64_t retVal = m_fileLength;
309 // And we assume the file will grow by this much.
310 m_fileLength += newSize;
311 // Will place the new block at the end of the file
312 return retVal;
313 } else {
314 // std::cout << "Block found for allocate " << newSize << '\n';
315 uint64_t foundPos = it->getFilePosition();
316 uint64_t foundSize = it->getSize();
317 // Remove the free block you found - it is no longer free
318 m_free_bySize.erase(it);
319 uniqueLock.unlock();
320 // Block was too large - free the bit of space after it.
321 if (foundSize > newSize) {
322 this->freeBlock(foundPos + newSize, foundSize - newSize);
323 }
324 return foundPos;
325 }
326}
327
328//---------------------------------------------------------------------------------------------
340uint64_t DiskBuffer::relocate(uint64_t const oldPos, uint64_t const oldSize, const uint64_t newSize) {
341 // std::cout << "Relocating " << oldPos << ", " << oldSize << ", " << newSize
342 // << '\n';
343 // First, release the space in the old block.
344 this->freeBlock(oldPos, oldSize);
345 return this->allocate(newSize);
346}
347
348//---------------------------------------------------------------------------------------------
351void DiskBuffer::getFreeSpaceVector(std::vector<uint64_t> &free) const {
352 free.reserve(m_free.size() * 2);
353 freeSpace_bySize_t::const_iterator it = m_free_bySize.begin();
354 freeSpace_bySize_t::const_iterator it_end = m_free_bySize.end();
355 for (; it != it_end; ++it) {
356 free.emplace_back(it->getFilePosition());
357 free.emplace_back(it->getSize());
358 }
359}
360
363void DiskBuffer::setFreeSpaceVector(std::vector<uint64_t> const &free) {
364 m_free.clear();
365
366 if (free.size() % 2 != 0)
367 throw std::length_error("Free vector size is not a factor of 2.");
368
369 for (auto it = free.begin(); it != free.end(); it += 2) {
370 auto it_next = std::next(it);
371
372 if (*it == 0 && *it_next == 0) {
373 continue; // Not really a free space block!
374 }
375
376 FreeBlock newBlock(*it, *it_next);
377 m_free.insert(newBlock);
378 }
379}
380
382std::string DiskBuffer::getMemoryStr() const {
383 std::ostringstream mess;
384 mess << "Buffer: " << m_writeBufferUsed << " in " << m_nObjectsToWrite << " objects. ";
385 return mess.str();
386}
387
388} // namespace Mantid::Kernel
double obj
the value of the quadratic function
std::mutex m_freeMutex
Mutex for modifying the free space list.
Definition DiskBuffer.h:145
void freeBlock(uint64_t const pos, uint64_t const size)
This method is called by this->relocate when object that has shrunk and so has left a bit of free spa...
uint64_t relocate(uint64_t const oldPos, uint64_t const oldSize, const uint64_t newSize)
This method is called by an ISaveable object that has outgrown its space allocated on file and needs ...
std::list< ISaveable * > m_toWriteBuffer
A forward list for the buffer of "toWrite" objects.
Definition DiskBuffer.h:131
uint64_t allocate(uint64_t const newSize)
Allocate a block of the given size in a free spot in the file, or at the end of the file if there is ...
void toWrite(ISaveable *item)
Call this method when an object is ready to be written out to disk.
uint64_t m_fileLength
Length of the file. This is where new blocks that don't fit get placed.
Definition DiskBuffer.h:149
size_t m_writeBufferUsed
Total amount of memory in the "toWrite" buffer.
Definition DiskBuffer.h:127
void defragFreeBlocks()
Method that defrags free blocks by combining adjacent ones together NOTE: This is not necessary to ru...
void flushCache()
Flush out all the data in the memory; and writes out everything in the to-write cache.
size_t m_nObjectsToWrite
number of objects stored in to write buffer list
Definition DiskBuffer.h:129
void getFreeSpaceVector(std::vector< uint64_t > &free) const
Returns a vector with two entries per free block: position and size.
void setFreeSpaceVector(std::vector< uint64_t > const &free)
Sets the free space map.
void objectDeleted(ISaveable *item)
Call this method when an object that might be in the cache is getting deleted.
freeSpace_bySize_t & m_free_bySize
Index into m_free, but indexed by block size.
Definition DiskBuffer.h:142
size_t m_writeBufferSize
Do we use the write buffer? Always now.
Definition DiskBuffer.h:124
void writeOldObjects()
Method to write out the old objects that have been stored in the "toWrite" buffer.
std::string getMemoryStr() const
std::mutex m_mutex
Mutex for modifying the toWrite buffer.
Definition DiskBuffer.h:134
freeSpace_t m_free
Map of the free blocks in the file.
Definition DiskBuffer.h:139
FreeBlock: a simple class that holds the position and size of block of free space in a file.
Definition FreeBlock.h:22
static bool merge(FreeBlock &first, const FreeBlock &second)
Attempt to merge an adjacent block into this one.
Definition FreeBlock.h:49
An interface for objects that can be cached or saved to disk.
Definition ISaveable.h:28
void clearBufferState()
clears the state of the object, and indicate that it is not stored in buffer any more
Definition ISaveable.cpp:78
void setBufferSize(size_t newSize)
Definition ISaveable.h:166
virtual uint64_t getFilePosition() const
Definition ISaveable.h:36
size_t setBufferPosition(std::list< ISaveable * >::iterator bufPosition)
sets the iterator pointing to the location of this object in the memory buffer to write later
Definition ISaveable.cpp:68
uint64_t getFileSize() const
Return the number of units this block occipies on file.
Definition ISaveable.h:38
std::optional< std::list< ISaveable * >::iterator > & getBufPostion()
returns the iterator pointing to the position of this object within the memory to-write buffer
Definition ISaveable.h:162
virtual uint64_t getTotalDataSize() const =0
size_t getBufferSize() const
return the amount of memory, this object had when it was stored in buffer last time;
Definition ISaveable.h:165
virtual size_t getDataMemorySize() const =0
the data size kept in memory