Потокобезопасная инициализация указателя только один раз - PullRequest
3 голосов
/ 13 февраля 2020

Я пишу библиотечную функцию, скажем, count_char(const char *str, int len, char ch), которая обнаруживает поддерживаемые расширения SIMD процессора, на котором он работает, и отправляет вызов, скажем, в версию, оптимизированную для AVX2 или SSE4.2. Поскольку я хотел бы избежать штрафа за выполнение пары cpuid инструкций для каждого вызова, я пытаюсь сделать это только один раз при первом вызове функции (которая может вызываться разными потоками одновременно).

На земле C ++ я бы просто сделал что-то вроде

int count_char(const char *str, int len, char ch) {
    static const auto fun_ptr = select_simd_function();
    return (*fun_ptr)(str, len, ch);
}

и полагался на семантику C ++ static, чтобы гарантировать, что он вызывается ровно один раз без каких-либо условий гонки. Но какой лучший способ сделать это в чистом виде C?

Вот что я придумал:

  1. Использование атомов c переменных (которые также присутствуют в C) - довольно подвержен ошибкам и немного сложнее в обслуживании.
  2. Использование pthread_once - не уверен в том, какие накладные расходы у него есть, плюс это может вызвать головную боль при Windows.
  3. принуждение пользователя библиотеки вызывать другую библиотечную функцию для инициализации указателя - короче говоря, в моем случае это не сработает, поскольку на самом деле это C бит библиотеки для другого языка.
  4. выравнивание указатель на 8 байтов и полагается на то, что x86-доступ к файлу размером атома c - непереносим для других архитектур (позже я буду реализовывать некоторые версии PowerP C или ARM-специфицированные c SIMD, скажем), технически UB (по крайней мере в C ++).
  5. Использование локального потокового хранилища и маркировка fun_ptr как thread_local, а затем что-то вроде
    static thread_local fun_ptr_t fun_ptr = NULL;
    if (!fun_ptr) {
        fun_ptr = select_simd_function();
    }
    return (*fun_ptr)(str, len, ch);

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

Лично для меня (5) пока победитель, за которым внимательно следят by (1) (я бы, вероятно, даже go с (1), если бы это была не чья-то основополагающая библиотека, и я не хотел бы смущать себя ошибочной реализацией).

Так Какой будет лучший вариант? Я что-то пропустил?

1 Ответ

5 голосов
/ 13 февраля 2020

Если вы можете использовать C11, это будет работать (при условии, что ваша реализация поддерживает потоки - это дополнительная функция ):

#include <threads.h>

static fun_ptr_t fun_ptr = NULL;

static void init_fun_ptr( void )
{
    fun_ptr = select_simd_function();
}

fun_ptr_t get_simd_function( void )
{
    static once_flag flag = ONCE_FLAG_INIT;

    call_once( &flag, init_fun_ptr);

    return ( fun_ptr );
}

Конечно, вы упомянули Windows. Я сомневаюсь, что MSV C поддерживает это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...