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>
using namespace Mantid;
using namespace Mantid::Kernel;
using Mantid::Kernel::CPUTimer;
//====================================================================================
class ISaveableTester : public ISaveable {
size_t id;
ISaveableTester(size_t idIn) : ISaveable(), id(idIn) {}
~ISaveableTester() override {}
size_t getFileId() const { return id; }
//-----------------------------------------------------------------------------------------------
/// Save the data - to be overriden
void save() const override {
// Fake writing to a file
std::ostringstream out;
streamMutex.lock();
fakeFile += out.str();
streamMutex.unlock();
this->m_wasSaved = true;
}
/// Load the data - to be overriden
void load() override { this->setLoaded(true); };
/// Method to flush the data to disk and ensure it is written.
void flushData() const override{};
/// remove objects data from memory
void clearDataFromMemory() override { 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
* If the object has never been loaded, this should be equal to number of
data points in the file
uint64_t getTotalDataSize() const override { return 1; }
size_t getDataMemorySize() const override { return 1; };
static std::mutex streamMutex;
};
// Declare the static members here.
std::string ISaveableTester::fakeFile = "";
std::mutex ISaveableTester::streamMutex;
//====================================================================================
class DiskBufferISaveableTest : public CxxTest::TestSuite {
std::vector<ISaveableTester *> data;
std::vector<ISaveableTester *> bigData;
// 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);
for (long i = 0; i < BIG_NUM; i++)
bigData.push_back(new ISaveableTester(i));
void tearDown() override {
for (size_t i = 0; i < data.size(); i++) {
for (size_t i = 0; i < bigData.size(); i++) {
}
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() {
TS_ASSERT_EQUALS(dbuf.getWriteBufferSize(), 3);
TS_ASSERT_EQUALS(dbuf.getWriteBufferSize(), 11);
}
//--------------------------------------------------------------------------------
/** Test calling toWrite() */
// No MRU, 3 in the to-write cache
DiskBuffer dbuf(2);
TS_ASSERT_EQUALS(dbuf.getWriteBufferSize(), 2);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 0);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 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,");
// 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 */
// No write buffer
DiskBuffer dbuf(0);
TS_ASSERT_EQUALS(dbuf.getWriteBufferSize(), 0);
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
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());
TSM_ASSERT_EQUALS("Not yet written to file",
std::numeric_limits<uint64_t>::max(),
data[1]->getFilePosition());
TSM_ASSERT_EQUALS("Not yet written to file",
std::numeric_limits<uint64_t>::max(),
data[9]->getFilePosition());
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());
TSM_ASSERT_EQUALS("Not yet written to file",
std::numeric_limits<uint64_t>::max(),
data[3]->getFilePosition());
TSM_ASSERT_EQUALS("Not yet written to file",
std::numeric_limits<uint64_t>::max(),
data[4]->getFilePosition());
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());
TSM_ASSERT_EQUALS("Not yet written to file",
std::numeric_limits<uint64_t>::max(),
data[6]->getFilePosition());
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++) {
dbuf.toWrite(data[i]);
}
// We ended up with too much in the buffer since nothing could be written.
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 9);
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 */
// Room for 6 in the to-write cache
DiskBuffer dbuf(6);
// Fill the buffer
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);
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()
for (long i = 0; i < int(BIG_NUM); i++) {
dbuf.toWrite(bigData[i]);
}
// std::cout << ISaveableTester::fakeFile << '\n';
void test_addAndRemove() {
long DATA_SIZE(500);
std::vector<size_t> indexToRemove(DATA_SIZE);
std::vector<ISaveable *> objToAdd(DATA_SIZE);
long iStep = BIG_NUM / DATA_SIZE;
if (iStep < 1 || DATA_SIZE > BIG_NUM) {
TSM_ASSERT("Test has wrong setting", false);
return;
}
for (long i = 0; i < DATA_SIZE; i++) {
indexToRemove[i] = i * iStep;
objToAdd[i] = new ISaveableTester(size_t(BIG_NUM + i * iStep));
}
DiskBuffer dbuf(size_t(BIG_NUM + DATA_SIZE));
Kernel::Timer clock;
for (long i = 0; i < BIG_NUM; i++) {
dbuf.toWrite(bigData[i]);
}
std::cout << "\nFinished DiskBuffer insertion performance test, inserted "
<< BIG_NUM << " objects on 1 thread in " << clock.elapsed()
<< " sec\n";
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, 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];
}
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
void test_addAndRemoveMultithread() {
long DATA_SIZE(500);
std::vector<size_t> indexToRemove(DATA_SIZE);
std::vector<ISaveable *> objToAdd(DATA_SIZE);
long iStep = BIG_NUM / DATA_SIZE;
if (iStep < 1 || DATA_SIZE > BIG_NUM) {
TSM_ASSERT("Test has wrong setting", false);
return;
}
for (long i = 0; i < DATA_SIZE; i++) {
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 {
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
void setUp() override { 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.\n";
//
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
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).\n";
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.\n";
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).\n";