LINUX.ORG.RU

ГПСЧ кросс-платформенно

 ,


0

2

Нужно на C получить случайное число.

Если использовать rand(), предварительно использовав srand(time(NULL)) в течение одной секунды (а может и больше) в разных процессах «выпадают» абсолютно одинаковые значения.

Читать из файлов вроде /dev/urandom – не вариант. Софт потенциально должен работать на фороточках, хоть и сейчас пишется под UNIX-подобные ОС.

предварительно использовав srand(time(NULL))

Тут много решений этой проблемы, можно использовать не секунды, а миллисекунды, а можно плюсануть к зерну номер процесса или порядковый номер треда.

unDEFER ★★★★★
()
Ответ на: комментарий от zx_gamer

А нельзя сделать обёртку(например функцию myCoolRand())? То есть сделать препроцессорное условие, при компиляции для UNIX твоя программа будет использовать /dev/random, а при компиляции под Windows - местные аналоги.

Werenter ★★★
()

а как часто в указанных «разных процессах» вы дёргаете rand() ? и сколько всего этих процессов. И как они стартуют.

если процессы стартуют одновременно, в течении 1-й секунды то srand(time(NULL)) инициализует seed одинаково, и первое-же обращение к rand() выдаст одинаковые числа. ВЫХОД: srand() инициализуйте иначе. Например добавив pid() - как-то добейтесь инициализации гарантированно разными числами

если используете rand() из многих процессов то получить одинаковое случайное числа (но не одинаковые последовательности), вообще говоря не фантастика - диапазон всего 0..RAND_MAX. Если это критично, то ВЫХОД: увеличьте разрядность, например взяв два/три случайных числа в разные биты.

когда пишется мат.софт (например монте-карло), то стоит использовать аналоги rand() из соответсвующих мат.библиотек. Они «хорошие» - выдают требуемые распределения в требуемой разрядности и видах (long,double)

если вская защита, сети, шифрования - то используется криптостойкий rand() из их библиотек. openssl упомянули, я ещё использовал https://doc.libsodium.org/generating_random_data .

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)

предварительно использовав srand(time(NULL)) в течение одной секунды (а может и больше) в разных процессах

Вот тут прямо написано, что srand не гарантируется потокобезопасность. Более того, rand тоже не потокобезопасна.

С моей точки зрения, если нужно генерировать псевдослучайные числа быстро и во множестве потоков, то нужно делать что-то вроде потокобезопасной обёртки над srand и rand и использовать/отлаживать её.

Либо компилировать с условием и использовать специфичные инструменты ОС. Забавный факт: FreeBSD напрямую называет rand и srand плохим генератором случайных чисел :)

dsl
()

нарисовать функцию которая под линухой будет брать рандом из /dev/urandom, а под винде из каких-либо true виндовых средств али подключенных библиотек.
ну и микшировать результат при кампеляции ?? вродеб все классично до ужоса.

pfg ★★★★★
()
Ответ на: комментарий от dsl

rand - это «Линейный конгруэнтный метод», т.е. там всего пару арифметических операций.

В винде болт клали, и вызов в потоках что-то работает шустро, но что там выдаёт большой большой секрет :)

В Linux вызов rand из потоков может приводить к блокировкам. Которые существенно увеличат время работы программы, по сравнению с однопоточным вариантом.

AlexVR ★★★★★
()

C++11 ввёл отдельные объекты для ГПСЧ. их можно смело создать в своём потоке и инициализировать через std::random_device (только что-то у MinGW были проблемы с ним когда-то). Если уж так сильно нужен Си интерфейс, то и обернуть можно.

Плюс есть 100500 SSE/AVX реализаций ГПСЧ.

Итог:

  1. Не используй rand в потоках. Его назначение - «Hello world» для студентов. Тут или C++11 <random>, или библиотеки, или своя реализация.
  2. Инициализируй через /dev/urandom, winapi, std::random_device и т.п.
AlexVR ★★★★★
()
  • Подмешивай в srand() getpid()
  • Используй для винды свою имплементацию под #ifdef где читай местный urandom
  • Используй библиотеку которая абстрагирует тебе такие вещи
slovazap ★★★★★
()
Ответ на: комментарий от annulen

Так-то да. Но я увидал сообщение вспомнил что я что-то вроде тыкал, закрома открыл, грепнул и скопипастил минимальный пример для ТС. Сам я асм вставки никогда не использовал кроме как поиграться, ну у меня любой код это больше игрушечка чем что-то адекватное, но всё же. От асма держусь в сторонке.

Если бы у меня была проблема как у ТС, то я бы сделал просто задержку запуска процессов по типу доминошек. Дабы программный генератор был в более разных состояниях при каждом его дрыгании.

А вообще странно, какая разница что в разных процессах рандомы совпадают, если в пределах процесса они всё же рандомы. А если у него несколько процессов коллапсируют результат в какой то один, то пусть через любой механизм межпроцесорного взаимодействия из него же и берут централизованно рандом, но просто по очереди, тогда и никаких совпадений не будет.

Ну да ладна.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

А вообще странно, какая разница что в разных процессах рандомы совпадают, если в пределах процесса они всё же рандомы. А если у него несколько процессов коллапсируют результат в какой то один, то пусть через любой механизм межпроцесорного взаимодействия из него же и берут централизованно рандом, но просто по очереди, тогда и никаких совпадений не будет.

Согласен, если проблема в совпадении, то нужен единый source of truth, так как даже рандом криптографического качества в теории может выдать два одинаковых результата, пусть и с мизерным шансом.

annulen ★★★★★
()
Ответ на: комментарий от zx_gamer

Речь шла не о потоках, а о процессах.

Если будут новые мысли – пишите.

Тупо инкрементировать к значению random() адрес указателя на функцию main процесса, адрес которой уникален (или можно аллоцировать 1 байт и использовать значение указателя на него), так для каждого отдельного процесса будет гарантированное уникальное смещение и разные значения даже если рандом одинаковое выдаст.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char *argv[])
{
    srand(time(NULL));
    size_t v = random()+(size_t)main;
    return 0;
}

Просто как палка и элегантно :D хотя может и тупак

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

так ты определись, минимальный Си и ОС-специфичное или библиотеки, которые сделают это за тебя.

По сабжу:

#include <sys/random.h>

#include <stdio.h>
#include <stdlib.h>

int main()
{
    unsigned seed;
    getrandom(&seed, sizeof(seed), 0);
    srand(seed);
    printf("%d\n", rand());
    return 0;
}
AlexVR ★★★★★
()
Ответ на: комментарий от zx_gamer

Это точно не вызовет какого-нибудь UB?

Нет, единственное что может быть, это переполнение типа результата, поэтому нельзя использовать знаковый тип. В теории может случится что выдаст 0 или очень маленькое значение например 42.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

адрес указателя на функцию main процесса, адрес которой уникален (или можно аллоцировать 1 байт и использовать значение указателя на него)

epic fail

адреса идентичны.

MKuznetsov ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

уникальный main всегда (ну почти) под попой

Откуда? В системе без ASLR, насколько я понимаю, у всех процессов, запущенных из одного бинарника, будет один и тот же адрес main.

annulen ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Кстати, да. Только зачем плюсовать к случайному числу, когда можно к зерну? Но там конечно виртуальное адресное пространство у каждого процесса и как сказали выше может быть и одинаковое. Но можно брать не main указатель, а что-то другое.

unDEFER ★★★★★
()
Последнее исправление: unDEFER (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Понял. Если процессы разделены и не используют никакие механизмы IPC и вызывают каждый rand/srand из своего адресного пространства, то самое простое как уже подсказали тут это предоставить srand в виде зерна идентификатор процесса. Это будет по-прежнему плохо с точки зрения криптографии, но одинаковых чисел Вы получать не должны.

UDP: А насколько быстро Вы стартуете процессы? Может быть дело в гранулярности time() в секундах. Попробуйте использовать clock_gettime() вместо time() и если хотите, то примешивайте идентификатор процесса.

dsl
()
Последнее исправление: dsl (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

байтик алоцировать и его указатель использовать. Или комбинировать, благо сделать это надо лишь 1 раз.

с байтиком такая-же фигня должна быть. первые аллоцируемые блоки будут иметь идентичные адреса. Собственно это основная причина для randomize сегментов.

при запуске из shell адреса argv,*argv получаются разные (проверил в виндовс); но при execp из третьего процесса возможно опять совпадут.

только зачем так извращаться ? getpid() возвращает уникальное число, можно разумно комбинировать его с time() и получить гарантированно разные seed для процессов в группе.

PS/ для конкурса «самое плохое но рабочее решение»: использовать udp/tcp порт.

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

Да я уже читаю https://habr.com/ru/companies/pt/articles/349544/ там приколов много, включая кеш glibc 0_o.

первые аллоцируемые блоки будут иметь идентичные адреса

Тутава я всё же не вкуриваю. Адреса с кучи всегда же уникальные. Как они могут быть одинаковы? (для не друг за другом работающих и завершающихся процессов, а для двух работающих)

Я в процесса А дёрну маллок получу байты свои, в процессе B дёрну маллок получу там свои байты. Адреса в А и B в выделенной куче могут быть близкими очень, но блин не одинаковыми. Тогда бы всё развалилось нахрен. 🤔

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 3)
Ответ на: комментарий от LINUX-ORG-RU

У каждого процесса своё виртуальное адресное пространство, и куча своя собственная. Адреса могут использоваться одни и те же без ограничений.

annulen ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Мы же недавно обсуждали работу MMU в теме про сегфолты. Он как раз и делает так, что у каждого процесса свой набор адресов, а ядро его в нужные моменты перенастраивает.

annulen ★★★★★
()
Ответ на: комментарий от annulen

Да, но… Не буду размусоливать, видимо я когда то давно начитался чего-то не того так как у меня в это треде некий конфуз произошёл некоторые моменты у меня в понимании неверные и искажают всякое вытекающее из этого :(

Над дочитать то что я по ссылке привёл и попрыгать по википедиям по ключевым словам, а потом провести серию экспериментов. Время бы выделить на это. Хотя бы сутки чистого времени.

Ну, зато ТС (и не только) осведомлён о всяком. Тоже дело. Ну да ладно, вперёд к знаниям!

LINUX-ORG-RU ★★★★★
()