Replace qrand() engine with C++11 <random> LCG and deprecate

Instead of trying to adapt to whatever the C library may have and using
QThreadLocalStorage, let's use a simple linear congruential generator
engine from <random>. We can't use a single instance because qsrand()
is documented to work per thread.

I thought of using QRandomEngine, but had to make the choice between
growing the QtCore code size and growing the per-thread data size. Code
is sharable and is actually smaller than the sizeof(QRandomEngine),
which is over 2500 bytes. sizeof(std::minstd_rand) is just
sizeof(uint_fast32_t).

Change-Id: I0a103569c81b4711a649fffd14ec8e641d02bf20
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Thiago Macieira 2017-10-11 17:42:15 +02:00
parent 09c1510060
commit e1ffa594a1

View File

@ -1262,20 +1262,56 @@ void QRandomGenerator::_fillRange(void *buffer, void *bufferEnd)
std::generate(begin, end, [this]() { return storage.engine()(); });
}
#if defined(Q_OS_ANDROID) && (__ANDROID_API__ < 21)
typedef QThreadStorage<QJNIObjectPrivate> AndroidRandomStorage;
Q_GLOBAL_STATIC(AndroidRandomStorage, randomTLS)
namespace {
struct QRandEngine
{
std::minstd_rand engine;
QRandEngine() : engine(1) {}
#elif defined(Q_OS_UNIX) && !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS - 0 > 0)
using SeedStorageType = QtPrivate::FunctionPointer<decltype(&srand)>::Arguments::Car;
int generate()
{
std::minstd_rand::result_type v = engine();
if (std::numeric_limits<int>::max() != RAND_MAX)
v %= uint(RAND_MAX) + 1;
typedef QThreadStorage<SeedStorageType *> SeedStorage;
Q_GLOBAL_STATIC(SeedStorage, randTLS) // Thread Local Storage for seed value
return int(v);
}
void seed(std::minstd_rand::result_type q)
{
engine.seed(q);
}
};
}
#if defined(QT_NO_THREAD) || defined(Q_OS_WIN)
// On Windows srand() and rand() already use Thread-Local-Storage
// to store the seed between calls
static inline QRandEngine *randTLS()
{
return nullptr;
}
#elif defined(Q_COMPILER_THREAD_LOCAL)
static inline QRandEngine *randTLS()
{
thread_local QRandEngine r;
return &r;
}
#else
Q_GLOBAL_STATIC(QThreadStorage<QRandEngine>, g_randTLS)
static inline QRandEngine *randTLS()
{
auto tls = g_randTLS();
if (!tls)
return nullptr;
return &tls->localData();
}
#endif
/*!
\relates <QtGlobal>
\deprecated
\since 4.2
Thread-safe version of the standard C++ \c srand() function.
@ -1287,49 +1323,23 @@ Q_GLOBAL_STATIC(SeedStorage, randTLS) // Thread Local Storage for seed value
if two threads call qsrand(1) and subsequently call qrand(), the threads will get
the same random number sequence.
\note This function is deprecated. In new applications, use
QRandomGenerator instead.
\sa qrand(), QRandomGenerator
*/
void qsrand(uint seed)
{
#if defined(Q_OS_ANDROID) && (__ANDROID_API__ < 21)
if (randomTLS->hasLocalData()) {
randomTLS->localData().callMethod<void>("setSeed", "(J)V", jlong(seed));
return;
}
QJNIObjectPrivate random("java/util/Random",
"(J)V",
jlong(seed));
if (!random.isValid()) {
auto prng = randTLS();
if (prng)
prng->seed(seed);
else
srand(seed);
return;
}
randomTLS->setLocalData(random);
#elif defined(Q_OS_UNIX) && !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS - 0 > 0)
SeedStorage *seedStorage = randTLS();
if (seedStorage) {
SeedStorageType *pseed = seedStorage->localData();
if (!pseed)
seedStorage->setLocalData(pseed = new SeedStorageType);
*pseed = seed;
} else {
//global static seed storage should always exist,
//except after being deleted by QGlobalStaticDeleter.
//But since it still can be called from destructor of another
//global static object, fallback to srand(seed)
srand(seed);
}
#else
// On Windows srand() and rand() already use Thread-Local-Storage
// to store the seed between calls
// this is also valid for QT_NO_THREAD
srand(seed);
#endif
}
/*!
\relates <QtGlobal>
\deprecated
\since 4.2
Thread-safe version of the standard C++ \c rand() function.
@ -1343,52 +1353,18 @@ void qsrand(uint seed)
step is skipped, then the sequence will be pre-seeded with a constant
value.
\sa qsrand(), QRandomGenerator
\note This function is deprecated. In new applications, use
QRandomGenerator instead.
\sa qrand(), QRandomGenerator
*/
int qrand()
{
#if defined(Q_OS_ANDROID) && (__ANDROID_API__ < 21)
AndroidRandomStorage *randomStorage = randomTLS();
if (!randomStorage)
auto prng = randTLS();
if (prng)
return prng->generate();
else
return rand();
if (randomStorage->hasLocalData()) {
return randomStorage->localData().callMethod<jint>("nextInt",
"(I)I",
RAND_MAX);
}
QJNIObjectPrivate random("java/util/Random",
"(J)V",
jlong(1));
if (!random.isValid())
return rand();
randomStorage->setLocalData(random);
return random.callMethod<jint>("nextInt", "(I)I", RAND_MAX);
#elif defined(Q_OS_UNIX) && !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS - 0 > 0)
SeedStorage *seedStorage = randTLS();
if (seedStorage) {
SeedStorageType *pseed = seedStorage->localData();
if (!pseed) {
seedStorage->setLocalData(pseed = new SeedStorageType);
*pseed = 1;
}
return rand_r(pseed);
} else {
//global static seed storage should always exist,
//except after being deleted by QGlobalStaticDeleter.
//But since it still can be called from destructor of another
//global static object, fallback to rand()
return rand();
}
#else
// On Windows srand() and rand() already use Thread-Local-Storage
// to store the seed between calls
// this is also valid for QT_NO_THREAD
return rand();
#endif
}
QT_END_NAMESPACE