Все, что я сказал до сих пор в своем другом ответе, остается верным в целом, поскольку ваш вопрос был о том, что «может» ... однако теперь, когда я увидел ваш реальный код, моя первая ставка была бы на то, что вы используете Функция random () замедляет все. Почему?
Понимаете, random хранит в памяти глобальную переменную, в которой хранится последнее вычисленное случайное значение. Каждый раз, когда вы вызываете random () (и вы вызываете его дважды в одной функции), он считывает значение этой глобальной переменной, выполняет вычисление (которое не так быстро; только random () является медленной функцией) и записывает результат туда, прежде чем вернуть его. Эта глобальная переменная не для каждого потока, она является общей для всех потоков. Поэтому то, что я написал относительно отравления кэша, применимо здесь все время (даже если вы избегаете этого для массива, разделив массивы на потоки; это было очень умно с вашей стороны!). Это значение постоянно аннулируется в кэше любого из ядер и должно быть извлечено из памяти. Однако, если у вас есть только один поток, ничего подобного не происходит, эта переменная никогда не покидает кеш после первоначального чтения, поскольку к ней постоянно обращаются снова и снова и снова.
Кроме того, чтобы сделать ситуацию еще хуже, у glibc есть поточно-ориентированная версия random () - я только что проверил это, посмотрев на источник. Хотя на практике это кажется хорошей идеей, это означает, что каждый вызов random () будет вызывать блокировку мьютекса, доступ к памяти и разблокировку мьютекса. Таким образом, два потока, вызывающие случайное совпадение в один и тот же момент, приведут к блокировке одного потока на пару циклов ЦП. Однако это зависит от реализации, так как AFAIK не требует, чтобы random () был потокобезопасным. Большинство стандартных функций lib не обязательно должны быть поточно-ориентированными, поскольку стандарт C даже не знает о концепции потоков. Когда они не вызывают его в один и тот же момент, мьютекс не будет влиять на скорость (так как даже однопоточное приложение должно блокировать / разблокировать мьютекс), но затем отравление кеша снова будет применяться.
Вы можете предварительно построить массив со случайными числами для каждого потока, содержащий столько случайных чисел, сколько нужно каждому потоку. Создайте его в главном потоке перед порождением потоков и добавьте ссылку на него в указатель структуры, который вы передаете каждому потоку. Тогда получите случайные числа оттуда.
Или просто внедрите свой собственный генератор случайных чисел, если вам не нужны «лучшие» случайные числа на планете, которые работают с памятью на поток для хранения своего состояния - это может быть даже быстрее, чем встроенная система. в генераторе.
Если у вас работает решение только для Linux, вы можете использовать random_r . Позволяет передавать состояние при каждом вызове. Просто используйте уникальный объект состояния для каждого потока. Однако эта функция является расширением glibc, и, скорее всего, она не поддерживается другими платформами (ни частью стандартов C, ни стандартов POSIX AFAIK - эта функция не существует, например, в Mac OS X, она может не существовать в Solaris или FreeBSD).
Создание собственного генератора случайных чисел на самом деле не так сложно. Если вам нужны реальные случайные числа, вы не должны использовать random (). Функция Random создает только псевдослучайные числа (числа, которые выглядят случайными, но являются предсказуемыми, если вы знаете внутреннее состояние генератора). Вот код для одного, который производит хорошие случайные числа uint32:
static uint32_t getRandom(uint32_t * m_z, uint32_t * m_w)
{
*m_z = 36969 * (*m_z & 65535) + (*m_z >> 16);
*m_w = 18000 * (*m_w & 65535) + (*m_w >> 16);
return (*m_z << 16) + *m_w;
}
Важно как-то правильно "затравить" m_z и m_w, иначе результаты не будут случайными вообще. Само начальное значение уже должно быть случайным, но здесь вы можете использовать системный генератор случайных чисел.
uint32_t m_z = random();
uint32_t m_w = random();
uint32_t nextRandom;
for (...) {
nextRandom = getRandom(&m_z, &m_w);
// ...
}
Таким образом, каждый поток должен вызывать random () дважды, а затем использовать собственный генератор. Кстати, если вам нужны двойные рандомы (от 0 до 1), вышеприведенную функцию можно легко обернуть для этого:
static double getRandomDouble(uint32_t * m_z, uint32_t * m_w)
{
// The magic number below is 1/(2^32 + 2).
// The result is strictly between 0 and 1.
return (getRandom(m_z, m_w) + 1) * 2.328306435454494e-10;
}
Попробуйте внести это изменение в свой код и дайте мне знать, каковы результаты теста: -)