Newer
Older
#ifndef MANTID_KERNEL_DISKBUFFER_ISAVEABLE_TEST_H_
#define MANTID_KERNEL_DISKBUFFER_ISAVEABLE_TEST_H_
#include "MantidKernel/DiskBuffer.h"
#include "MantidKernel/FreeBlock.h"
#include "MantidKernel/ISaveable.h"
#include "MantidKernel/CPUTimer.h"
#include "MantidKernel/MultiThreaded.h"
#include "MantidKernel/System.h"
#include "MantidKernel/Timer.h"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <cxxtest/TestSuite.h>
#include <iomanip>
#include <iostream>
using namespace Mantid;
using namespace Mantid::Kernel;
using Mantid::Kernel::CPUTimer;
//====================================================================================
class ISaveableTester : public ISaveable
{
ISaveableTester(size_t idIn) : ISaveable(),
{}
virtual ~ISaveableTester(){}
size_t getFileId()const{return id;}
//-----------------------------------------------------------------------------------------------
/// Save the data - to be overriden
virtual void save()const
{
// Fake writing to a file
std::ostringstream out;
streamMutex.lock();
fakeFile += out.str();
streamMutex.unlock();
}
/// Load the data - to be overriden
virtual void load()
{
this->setLoaded(true);
};
/// Method to flush the data to disk and ensure it is written.
virtual void flushData() const{};
/// remove objects data from memory
virtual void clearDataFromMemory()
{
this->setLoaded(false);
};
/** @return the amount of memory that the object takes as a whole.
For filebased objects it should be the amount the object occupies in memory plus the size it occupies in file if the object has not been fully loaded
or modified.
* If the object has never been loaded, this should be equal to number of data points in the file
*/
virtual uint64_t getTotalDataSize() const{return 1;}
/// the data size kept in memory
virtual size_t getDataMemorySize()const{return 1;};
static std::string fakeFile;
static Kernel::Mutex streamMutex;
};
// Declare the static members here.
std::string ISaveableTester::fakeFile = "";
Kernel::Mutex ISaveableTester::streamMutex;
//====================================================================================
class DiskBufferISaveableTest : public CxxTest::TestSuite
{
public:
std::vector<ISaveableTester*> data;
size_t num;
std::vector<ISaveableTester*> bigData;
void setUp()
{
// Create the ISaveables
num = 10;
data.clear();
for (size_t i=0; i<num; i++)
data.push_back( new ISaveableTester(i) );
BIG_NUM = 1000;
bigData.clear();
bigData.reserve(BIG_NUM);
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
bigData.push_back( new ISaveableTester(i) );
}
void tearDown()
{
for (size_t i=0; i<data.size(); i++)
{
delete data[i];
data[i]= NULL;
}
for (size_t i=0; i<bigData.size(); i++)
{
delete bigData[i];
bigData[i]=NULL;
}
ISaveableTester::fakeFile = "";
}
void testIsaveable()
{
ISaveableTester Sav(0);
TSM_ASSERT_EQUALS("Default data ID should be 0 ",0,Sav.getFileId());
TSM_ASSERT_EQUALS("Default file position is wrong ",std::numeric_limits<uint64_t>::max(),Sav.getFilePosition());
TSM_ASSERT_EQUALS("Default size should be 0 ",0,Sav.getFileSize());
ISaveableTester CopyTester(Sav);
TSM_ASSERT_EQUALS("Default data ID should be 0 ",0,CopyTester.getFileId());
TSM_ASSERT_EQUALS("Default file position is wrong ",std::numeric_limits<uint64_t>::max(),CopyTester.getFilePosition());
TSM_ASSERT_EQUALS("Default size should be 0 ",0,CopyTester.getFileSize());
}
//--------------------------------------------------------------------------------
/** Getting and setting the cache sizes */
void test_set_and_get_methods()
{
DiskBuffer dbuf(3);
TS_ASSERT_EQUALS( dbuf.getWriteBufferSize(), 3);
dbuf.setWriteBufferSize(11);
TS_ASSERT_EQUALS( dbuf.getWriteBufferSize(), 11);
}
//--------------------------------------------------------------------------------
/** Test calling toWrite() */
void test_basic()
{
// No MRU, 3 in the to-write cache
DiskBuffer dbuf(2);
TS_ASSERT_EQUALS( dbuf.getWriteBufferSize(), 2);
// Nothing in cache
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
dbuf.toWrite(data[0]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 1);
dbuf.toWrite(data[1]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 2);
dbuf.toWrite(data[2]);
// Write buffer now got flushed out
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
// The "file" was written out this way (the right order):
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "2,1,0,");
ISaveableTester::fakeFile ="";
// If you add the same one multiple times, it only is tracked once in the to-write buffer.
dbuf.toWrite(data[4]);
dbuf.toWrite(data[4]);
dbuf.toWrite(data[4]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 1);
}
//--------------------------------------------------------------------------------
/** Set a buffer size of 0 */
void test_basic_WriteBuffer()
{
// No write buffer
DiskBuffer dbuf(0);
TS_ASSERT_EQUALS( dbuf.getWriteBufferSize(), 0);
// Nothing in cache
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
dbuf.toWrite(data[0]);
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "0,");
dbuf.toWrite(data[1]);
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "0,1,");
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
dbuf.toWrite(data[2]);
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "0,1,2,");
dbuf.toWrite(data[3]);
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "0,1,2,3,");
dbuf.toWrite(data[4]);
// Everything get written immidiately;
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "0,1,2,3,4,");
ISaveableTester::fakeFile = "";
}
//--------------------------------------------------------------------------------
/// Empty out the cache with the flushCache() method
void test_flushCache()
{
DiskBuffer dbuf(10);
for (size_t i=0; i<6; i++)
dbuf.toWrite(data[i]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 6);
// Nothing written out yet
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "");
dbuf.flushCache();
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "5,4,3,2,1,0,");
// Nothing left in cache
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
}
//--------------------------------------------------------------------------------
/** Buffer allocates file positions, so sorts according to the alloction order by the DiskBuffer */
void test_writesOutDBOrder()
{
// Room for 3 in the to-write cache
DiskBuffer dbuf(3);
// These 3 will get written out
dbuf.toWrite(data[5]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[5]->getFilePosition());
dbuf.toWrite(data[1]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[1]->getFilePosition());
dbuf.toWrite(data[9]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[9]->getFilePosition());
dbuf.flushCache();
TSM_ASSERT_EQUALS("Is written to file at ",0,data[9]->getFilePosition());
TSM_ASSERT_EQUALS("Is written to file at ",1,data[1]->getFilePosition());
TSM_ASSERT_EQUALS("Is written to file at ",2,data[5]->getFilePosition());
// These 4 at the end will be in the cache
dbuf.toWrite(data[2]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[2]->getFilePosition());
dbuf.toWrite(data[3]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[3]->getFilePosition());
dbuf.toWrite(data[4]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[4]->getFilePosition());
dbuf.flushCache();
TSM_ASSERT_EQUALS("Is written to file at ",3,data[4]->getFilePosition());
TSM_ASSERT_EQUALS("Is written to file at ",4,data[3]->getFilePosition());
TSM_ASSERT_EQUALS("Is written to file at ",5,data[2]->getFilePosition());
dbuf.toWrite(data[6]);
TSM_ASSERT_EQUALS("Not yet written to file",std::numeric_limits<uint64_t>::max(),data[6]->getFilePosition());
// 1 left in the buffer
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 1);
}
//--------------------------------------------------------------------------------
/** Extreme case with nothing writable but exceeding the writable buffer */
void test_noWriteBuffer_nothingWritable()
{
// Room for 4 in the write buffer
DiskBuffer dbuf(4);
for (size_t i=0; i<9; i++)
{
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
dbuf.toWrite(data[i]);
}
// We ended up with too much in the buffer since nothing could be written.
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 9);
// Let's make it all writable
for (size_t i=0; i<9; i++)
data[i]->setBusy(false);
// Trigger a write
dbuf.toWrite(data[8]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
// And all of these get written out at once
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "8,7,6,5,4,3,2,1,0,");
}
//--------------------------------------------------------------------------------
/** If a block gets deleted it needs to be taken out of the caches */
void test_objectDeleted()
{
// Room for 6 in the to-write cache
DiskBuffer dbuf(6);
// Fill the buffer
for (size_t i=0; i<5; i++)
dbuf.toWrite(data[i]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 5);
// First let's get rid of something in to to-write buffer
dbuf.objectDeleted(data[1]);
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 4);
TSM_ASSERT_EQUALS( "The data have never been written", dbuf.getFreeSpaceMap().size(), 0);
dbuf.flushCache();
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "4,3,2,0,");
}
//--------------------------------------------------------------------------------
/** Any ISaveable that says it can't be written remains in the cache */
void test_skips_dataBusy_Blocks()
{
DiskBuffer dbuf(3);
dbuf.toWrite(data[0]);
dbuf.toWrite(data[1]); data[1]->setBusy(true); // Won't get written out
dbuf.toWrite(data[2]);
dbuf.flushCache();
// Item #1 was skipped and is still in the buffer!
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "2,0,");
//TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "0,2,");
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 1);
// But it'll get written out next time
ISaveableTester::fakeFile = "";
data[1]->setBusy(false);
dbuf.flushCache();
TS_ASSERT_EQUALS(ISaveableTester::fakeFile, "1,");
TS_ASSERT_EQUALS( dbuf.getWriteBufferUsed(), 0);
}
//--------------------------------------------------------------------------------
/** Accessing the map from multiple threads simultaneously does not segfault */
void test_thread_safety()
{
// Room for 3 in the to-write cache
DiskBuffer dbuf(3);
PARALLEL_FOR_NO_WSP_CHECK()
{
dbuf.toWrite(bigData[i]);
}
//std::cout << ISaveableTester::fakeFile << std::endl;
}
void test_addAndRemove()
{
std::vector<size_t> indexToRemove(DATA_SIZE);
std::vector<ISaveable *> objToAdd(DATA_SIZE);
if(iStep<1||DATA_SIZE>BIG_NUM)
{
TSM_ASSERT("Test has wrong setting",false);
return;
}
{
indexToRemove[i]=i*iStep;
objToAdd[i] = new ISaveableTester(size_t(BIG_NUM+i*iStep));
DiskBuffer dbuf(size_t(BIG_NUM+DATA_SIZE));
{
dbuf.toWrite(bigData[i]);
}
std::cout<<"\nFinished DiskBuffer insertion performance test, inserted "<<BIG_NUM <<" objects on 1 thread in "<< clock.elapsed()<<" sec\n";
{
dbuf.objectDeleted(bigData[indexToRemove[i]]);
dbuf.toWrite(objToAdd[i]);
dbuf.toWrite(bigData[indexToRemove[i]]);
}
std::cout<<"Finished DiskBuffer inserting/deleting performance test, 1 thread in "<< clock.elapsed()<<" sec\n";
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(),BIG_NUM+DATA_SIZE);
// cleanup memory
for(long i=0;i<DATA_SIZE;i++)
{
delete objToAdd[i];
}
}
void test_addAndRemoveMultithread()
{
std::vector<size_t> indexToRemove(DATA_SIZE);
std::vector<ISaveable *> objToAdd(DATA_SIZE);
if(iStep<1||DATA_SIZE>BIG_NUM)
{
TSM_ASSERT("Test has wrong setting",false);
return;
}
{
indexToRemove[i]=i*iStep;
objToAdd[i] = new ISaveableTester(BIG_NUM+i*iStep);
}
DiskBuffer dbuf(BIG_NUM+DATA_SIZE);
Kernel::Timer clock;
PARALLEL_FOR_NO_WSP_CHECK()
for(long i=0;i<BIG_NUM;i++)
{
dbuf.toWrite(bigData[i]);
}
std::cout<<"\nFinished DiskBuffer insertion performance test, inserted "<<BIG_NUM <<" objects on multithread in "<< clock.elapsed()<<" sec\n";
PARALLEL_FOR_NO_WSP_CHECK()
for(long i=0;i<DATA_SIZE;i++)
{
dbuf.objectDeleted(bigData[indexToRemove[i]]);
dbuf.toWrite(objToAdd[i]);
dbuf.toWrite(bigData[indexToRemove[i]]);
}
std::cout<<"Finished DiskBuffer inserting/deleting performance test, multithread in "<< clock.elapsed()<<" sec\n";
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(),BIG_NUM+DATA_SIZE);
// cleanup memory
for(long i=0;i<DATA_SIZE;i++)
{
delete objToAdd[i];
}
}
};
//====================================================================================
class DiskBufferISaveableTestPerformance : public CxxTest::TestSuite
{
public:
std::vector<ISaveableTester*> data;
size_t num;
// This pair of boilerplate methods prevent the suite being created statically
// This means the constructor isn't called when running other tests
static DiskBufferISaveableTestPerformance *createSuite() { return new DiskBufferISaveableTestPerformance(); }
static void destroySuite( DiskBufferISaveableTestPerformance *suite ) { delete suite; }
DiskBufferISaveableTestPerformance()
{
num = 100000;
data.clear();
for (size_t i=0; i<num; i++)
{
data.push_back( new ISaveableTester(i) );
data[i]->setBusy(true); // Items won't do any real saving
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
}
}
void setUp()
{
ISaveableTester::fakeFile = "";
}
void test_smallCache_writeBuffer()
{
CPUTimer tim;
DiskBuffer dbuf(3);
for (size_t i=0; i<data.size(); i++)
{
dbuf.toWrite(data[i]);
data[i]->setBusy(false);
}
std::cout<<" Elapsed : " << tim << " to load " << num << " into MRU." << std::endl;
}
//
void test_smallCache_no_writeBuffer()
{
CPUTimer tim;
DiskBuffer dbuf(0);
for (size_t i=0; i<data.size(); i++)
{
data[i]->setBusy(true); // Items won't do any real saving
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
}
for (int i=0; i<int(data.size()); i++)
{
dbuf.toWrite(data[i]);
data[i]->setBusy(false);
}
std::cout<<" Elapsed : " << tim << " to load " << num << " into MRU (no write cache)." << std::endl;
}
void test_largeCache_writeBuffer()
{
CPUTimer tim;
DiskBuffer dbuf(1000);
for (int i=0; i<int(data.size()); i++)
{
dbuf.toWrite(data[i]);
data[i]->setBusy(false);
}
std::cout << tim << " to load " << num << " into MRU." << std::endl;
}
void test_largeCache_noWriteBuffer()
{
CPUTimer tim;
DiskBuffer dbuf(0);
for (int i=0; i<int(data.size()); i++)
{
dbuf.toWrite(data[i]);
data[i]->setBusy(false);
}
std::cout<<" Elapsed : " << tim << " to load " << num << " into MRU (no write buffer)." << std::endl;
}
};
#endif /* MANTID_KERNEL_DISKBUFFERTEST_H_ */