Added new random pool library and FixedSizeData:CreatePseudoRandom() uses it. This generates pseudorandom data in another thread and keeps a pool instead of calling the pseudorandom algorithm each time. Since that algorithm needs to be mutexed, that caused quite a lot of locking before

This commit is contained in:
Sebastian Meßmer 2015-10-16 02:46:17 +02:00
parent 02d89fa721
commit c51e2c115a
11 changed files with 299 additions and 46 deletions

View File

@ -7,7 +7,7 @@
#include <string>
#include <cstring>
#include "../assert/assert.h"
#include "ThreadsafePseudoRandomPool.h"
#include "../random/RandomPool.h"
namespace cpputils {
@ -35,7 +35,6 @@ public:
private:
FixedSizeData() {}
static ThreadsafePseudoRandomPool &PseudoRandomPool();
unsigned char _data[BINARY_LENGTH];
};
@ -48,16 +47,10 @@ template<unsigned int SIZE> bool operator!=(const FixedSizeData<SIZE> &lhs, cons
template<unsigned int SIZE> constexpr unsigned int FixedSizeData<SIZE>::BINARY_LENGTH;
template<unsigned int SIZE> constexpr unsigned int FixedSizeData<SIZE>::STRING_LENGTH;
template<unsigned int SIZE>
ThreadsafePseudoRandomPool &FixedSizeData<SIZE>::PseudoRandomPool() {
static ThreadsafePseudoRandomPool singleton;
return singleton;
}
template<unsigned int SIZE>
FixedSizeData<SIZE> FixedSizeData<SIZE>::CreatePseudoRandom() {
FixedSizeData<SIZE> result;
PseudoRandomPool().GenerateBlock(result._data, BINARY_LENGTH);
RandomPool::get(result._data, BINARY_LENGTH);
return result;
}

View File

@ -1,37 +0,0 @@
#pragma once
#ifndef MESSMER_CPPUTILS_DATA_THREADSAFEPSEUDORANDOMPOOL_H
#define MESSMER_CPPUTILS_DATA_THREADSAFEPSEUDORANDOMPOOL_H
#include "../macros.h"
#include <mutex>
#include <cryptopp/cryptopp/osrng.h>
namespace cpputils {
//TODO Create more complete random library around CryptoPP (also offering OS_Random for example) and use it in FixedSizeDate::CreateRandom()/CreateOSRandom()
//TODO Store a static CryptoPP::AutoSeededRandomPool (or multiple ones) and make constructor of
// ThreadsafeRandomPool() be lightweight (i.e. not do seeding), so it can be called on each callsite.
// Alternatively, use a singleton factory.
//TODO Test
class ThreadsafePseudoRandomPool {
public:
ThreadsafePseudoRandomPool() { }
void GenerateBlock(byte *data, size_t size) {
// TODO Provide multiple randomPools for parallelity instead of locking the only available one
std::unique_lock <std::mutex> lock(_mutex);
_pool.GenerateBlock(data, size);
}
private:
//TODO Make seeding use blocking=true (aka /dev/random instead of /dev/urandom) or offer a configuration option?
CryptoPP::AutoSeededRandomPool _pool;
std::mutex _mutex;
DISALLOW_COPY_AND_ASSIGN(ThreadsafePseudoRandomPool);
};
}
#endif

35
random/LoopThread.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "LoopThread.h"
namespace cpputils {
LoopThread::LoopThread(): _thread() {}
LoopThread::~LoopThread() {
stop();
}
void LoopThread::start() {
_thread = boost::thread(std::bind(&LoopThread::main, this));
}
void LoopThread::stop() {
_thread.interrupt();
_thread.join();
}
void LoopThread::main() {
try {
while(true) {
loopIteration();
}
} catch (const boost::thread_interrupted &e) {
//Do nothing, exit thread.
} catch (const std::exception &e) {
//TODO Think about logging
std::cerr << "LoopThread crashed: " << e.what() << std::endl;
} catch (...) {
//TODO Think about logging
std::cerr << "LoopThread crashed" << std::endl;
}
}
}

25
random/LoopThread.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#ifndef MESSMER_CPPUTILS_RANDOM_LOOPTHREAD_H
#define MESSMER_CPPUTILS_RANDOM_LOOPTHREAD_H
#include <boost/thread.hpp>
namespace cpputils {
//TODO Test
//TODO Move out of "random" folder into own library folder
class LoopThread {
public:
LoopThread();
virtual ~LoopThread();
void start();
void stop();
virtual void loopIteration() = 0;
private:
void main();
boost::thread _thread;
};
}
#endif

View File

@ -0,0 +1 @@
#include "RandomDataBuffer.h"

50
random/RandomDataBuffer.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#ifndef MESSMER_CPPUTILS_RANDOM_RANDOMDATABUFFER_H
#define MESSMER_CPPUTILS_RANDOM_RANDOMDATABUFFER_H
#include "../data/Data.h"
#include "../assert/assert.h"
namespace cpputils {
//TODO Test
class RandomDataBuffer {
public:
RandomDataBuffer();
size_t size() const;
void get(void *target, size_t bytes);
void add(Data data);
private:
size_t _usedUntil;
Data _data;
DISALLOW_COPY_AND_ASSIGN(RandomDataBuffer);
};
inline RandomDataBuffer::RandomDataBuffer() : _data(0) {
}
inline size_t RandomDataBuffer::size() const {
return _data.size() - _usedUntil;
}
inline void RandomDataBuffer::get(void *target, size_t numBytes) {
ASSERT(size() >= numBytes, "Too many bytes requested. Buffer is smaller.");
std::memcpy(target, _data.dataOffset(_usedUntil), numBytes);
_usedUntil += numBytes;
}
inline void RandomDataBuffer::add(Data data) {
// Concatenate old and new random data
Data newdata(_data.size() + data.size());
std::memcpy(newdata.data(), _data.data(), _data.size());
std::memcpy(newdata.dataOffset(_data.size()), data.data(), data.size());
_data = std::move(newdata);
}
}
#endif

View File

@ -0,0 +1,26 @@
#include "RandomGeneratorThread.h"
namespace cpputils {
RandomGeneratorThread::RandomGeneratorThread(ThreadsafeRandomDataBuffer *buffer, size_t minSize, size_t maxSize)
: _randomGenerator(), _buffer(buffer), _minSize(minSize), _maxSize(maxSize) {
ASSERT(_maxSize >= _minSize, "Invalid parameters");
}
void RandomGeneratorThread::loopIteration() {
_buffer->waitUntilSizeIsLessThan(_minSize);
size_t neededRandomDataSize = _maxSize - _buffer->size();
ASSERT(_maxSize > _buffer->size(), "This could theoretically fail if another thread refilled the buffer. But we should be the only refilling thread.");
Data randomData = _generateRandomData(neededRandomDataSize);
_buffer->add(std::move(randomData));
}
Data RandomGeneratorThread::_generateRandomData(size_t size) {
std::cout << "Generate " << static_cast<double>(size)/1024 << " KB" << std::endl;
Data newRandom(size);
_randomGenerator.GenerateBlock(static_cast<byte*>(newRandom.data()), size);
std::cout << "Generate " << static_cast<double>(size)/1024 << " KB done" << std::endl;
return newRandom;
}
}

View File

@ -0,0 +1,28 @@
#pragma once
#ifndef MESSMER_CPPUTILS_RANDOM_RANDOMGENERATORTHREAD_H
#define MESSMER_CPPUTILS_RANDOM_RANDOMGENERATORTHREAD_H
#include "LoopThread.h"
#include "ThreadsafeRandomDataBuffer.h"
#include <cryptopp/cryptopp/osrng.h>
namespace cpputils {
//TODO Test
class RandomGeneratorThread: public LoopThread {
public:
RandomGeneratorThread(ThreadsafeRandomDataBuffer *buffer, size_t minSize, size_t maxSize);
void loopIteration() override;
private:
Data _generateRandomData(size_t size);
CryptoPP::AutoSeededRandomPool _randomGenerator;
ThreadsafeRandomDataBuffer *_buffer;
size_t _minSize;
size_t _maxSize;
DISALLOW_COPY_AND_ASSIGN(RandomGeneratorThread);
};
}
#endif

8
random/RandomPool.cpp Normal file
View File

@ -0,0 +1,8 @@
#include "RandomPool.h"
namespace cpputils {
constexpr size_t RandomPool::MIN_BUFFER_SIZE;
constexpr size_t RandomPool::MAX_BUFFER_SIZE;
std::mutex RandomPool::_mutex;
}

44
random/RandomPool.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#ifndef MESSMER_CPPUTILS_RANDOM_RANDOMPOOL_H
#define MESSMER_CPPUTILS_RANDOM_RANDOMPOOL_H
#include <boost/thread.hpp>
#include "ThreadsafeRandomDataBuffer.h"
#include "RandomGeneratorThread.h"
#include <mutex>
namespace cpputils {
//TODO Test
class RandomPool final {
public:
static void get(void *target, size_t bytes);
private:
static constexpr size_t MIN_BUFFER_SIZE = 1*1024*1024; // 1MB
static constexpr size_t MAX_BUFFER_SIZE = 2*1024*1024; // 2MB
RandomPool();
static RandomPool &singleton();
static std::mutex _mutex;
ThreadsafeRandomDataBuffer _buffer;
RandomGeneratorThread _refillThread;
DISALLOW_COPY_AND_ASSIGN(RandomPool);
};
inline RandomPool &RandomPool::singleton() {
std::unique_lock<std::mutex> lock(_mutex);
static RandomPool singleton;
return singleton;
}
inline void RandomPool::get(void *target, size_t bytes) {
singleton()._buffer.get(target, bytes);
}
inline RandomPool::RandomPool(): _buffer(), _refillThread(&_buffer, MIN_BUFFER_SIZE, MAX_BUFFER_SIZE) {
_refillThread.start();
}
}
#endif

View File

@ -0,0 +1,80 @@
#pragma once
#ifndef MESSMER_CPPUTILS_RANDOM_THREADSAFERANDOMDATABUFFER_H
#define MESSMER_CPPUTILS_RANDOM_THREADSAFERANDOMDATABUFFER_H
#include "../data/Data.h"
#include "../assert/assert.h"
#include "RandomDataBuffer.h"
#include <boost/thread.hpp>
namespace cpputils {
//TODO Test
class ThreadsafeRandomDataBuffer {
public:
ThreadsafeRandomDataBuffer();
size_t size() const;
void get(void *target, size_t numBytes);
void add(Data data);
void waitUntilSizeIsLessThan(size_t numBytes);
private:
size_t _get(void *target, size_t bytes);
RandomDataBuffer _buffer;
mutable boost::mutex _mutex;
boost::condition_variable _dataAddedCv;
// _dataGottenCv needs to be boost::condition_variable and not std::condition_variable, because the
// RandomGeneratorThread calling ThreadsafeRandomDataBuffer::waitUntilSizeIsLessThan() needs the waiting to be
// interruptible to stop the thread in RandomGeneratorThread::stop() or in the RandomGeneratorThread destructor.
boost::condition_variable _dataGottenCv;
DISALLOW_COPY_AND_ASSIGN(ThreadsafeRandomDataBuffer);
};
inline ThreadsafeRandomDataBuffer::ThreadsafeRandomDataBuffer() {
}
inline size_t ThreadsafeRandomDataBuffer::size() const {
boost::unique_lock<boost::mutex> lock(_mutex);
return _buffer.size();
}
inline void ThreadsafeRandomDataBuffer::get(void *target, size_t numBytes) {
size_t alreadyGotten = 0;
while (alreadyGotten < numBytes) {
size_t got = _get(static_cast<uint8_t*>(target)+alreadyGotten, numBytes);
alreadyGotten += got;
ASSERT(alreadyGotten <= numBytes, "Got too many bytes");
}
}
inline size_t ThreadsafeRandomDataBuffer::_get(void *target, size_t numBytes) {
boost::unique_lock<boost::mutex> lock(_mutex);
_dataAddedCv.wait(lock, [this, numBytes] {
return _buffer.size() > 0;
});
size_t gettableBytes = std::min(_buffer.size(), numBytes);
_buffer.get(target, gettableBytes);
_dataGottenCv.notify_all();
return gettableBytes;
}
inline void ThreadsafeRandomDataBuffer::add(Data data) {
boost::unique_lock<boost::mutex> lock(_mutex);
_buffer.add(std::move(data));
_dataAddedCv.notify_all();
}
inline void ThreadsafeRandomDataBuffer::waitUntilSizeIsLessThan(size_t numBytes) {
boost::unique_lock<boost::mutex> lock(_mutex);
_dataGottenCv.wait(lock, [this, numBytes] {
return _buffer.size() < numBytes;
});
}
}
#endif