Как заставить независимо скомпилированные пакеты cython использовать общий генератор случайных чисел?

У меня есть экспериментальный язык программирования, где программы компилируются в c. Я написал оболочку cython, которая оборачивает скомпилированный код c и позволяет вызывать его из python. Это позволяет использовать скомпилированные программы в качестве быстрых низкоуровневых функций внутри Python. Часто бывает так, что мы хотим использовать несколько таких программ в одной и той же программе Python. Тогда конвейер для создания и импорта каждой программы:

  1. Скомпилируйте программу в c с помощью компилятора.
  2. Скомпилируйте код c в общий объект .so с помощью gcc.
  3. Создайте оболочку .pyx, которая может получить доступ к функциям c, которые мы хотим использовать, из python.
  4. Скомпилируйте оболочку .pyx с помощью cythonize, чтобы сгенерировать файл .so.
  5. Импортируйте общий объект .so, используя функцию импорта Python.

На практике шаги 1-4 фактически объединяются в один внешний вызов для выполнения с использованием sys, при этом сгенерированный Makefile выполняет каждый из 4 шагов. Это позволяет нам вызывать make через внешний вызов с помощью sys, а затем импортировать скомпилированную программу, даже не выходя из python.

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

rand()

функция. Когда скомпилированная программа-оболочка импортируется в python, выполняется вызов импорта для сгенерированного общего объекта .so, который создается с помощью cythonize. До сих пор я пытался позвонить

srand(<long int>time(NULL)

из файла .pyx, который упаковывает каждую скомпилированную программу. Насколько я могу судить, каждый импортированный .so будет эффективно использовать свой собственный генератор случайных чисел. Но из документов мне совсем не ясно, так ли это.

В конечном счете, я хочу, чтобы разные .so использовали один и тот же генератор случайных чисел, но я понятия не имею, как это сделать. Мы будем очень признательны за любые рекомендации. Большая часть кода слишком длинная, чтобы включать его сюда, но если вы хотите увидеть какие-либо фрагменты (например, «как вы делаете x-компонент?»), я с радостью вам помогу.

Даже если все, что вы можете предложить, — это объяснение того, как вызовы rand() будут взаимодействовать между различными общими объектами, сгенерированными с помощью cythonize, этого может быть достаточно, чтобы выработать решение.

Заранее спасибо!


person Tim Atkinson    schedule 07.08.2019    source источник
comment
Проверить это можно через nm my_extension.so | grep rand, если у rand@@GLIB_XXX есть атрибут U (скорее всего), то его будет обеспечивать libc.so и состояние разделяется между всеми модулями.   -  person ead    schedule 08.08.2019


Ответы (1)


Я не уверен, что в спецификации C четко определено, распределяется ли случайное начальное число между файлами .so или отдельными (при этом я не читал стандарт C, поэтому здесь я немного предполагаю). Поэтому поведение, которое вы видите, может зависеть от платформы, на которой вы работаете.

Проще всего было бы написать небольшой модуль Cython, единственной целью которого является обработка генерации случайных чисел:

# cy_rand.pxd
cpdef void srand(unsigned int)
cpdef int rand()

# cy_rand.pyx
from libc cimport stdlib

cpdef void srand(unsigned int seed):
    stdlib.srand(seed)

cpdef int rand():
    return stdlib.rand()

Я сделал функции cpdef такими, чтобы их можно было вызывать и из Python. Если вас не волнует возможность сделать это, просто сделайте их cdef.

Вам нужно скомпилировать этот модуль обычным способом. В других ваших модулях вы можете просто сделать:

cimport cy_rand

cy_rand.srand(1) # some seed
rand_val = cy_rand.rand()

Таким образом, вы знаете, что случайные числа генерируются только в одном файле .so. Это добавляет небольшой уровень косвенности, поэтому будет немного медленнее, чем прямой вызов. Поэтому может быть хорошей идеей добавить вспомогательные функции для массовой генерации случайных чисел (для скорости).

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

person DavidW    schedule 07.08.2019
comment
Я думаю, что стандарту не решать, находится ли rand в общей библиотеке или нет: когда расширение связано, оно может быть связано с общей версией libc или со статической версией (что довольно необычно, но возможно). - person ead; 08.08.2019
comment
Это очень ценный совет, так как позже можно было бы легко заменить пару rand/srand с помощью генератора случайных чисел С++ 11 или создать самодельный генератор. - person ead; 08.08.2019