Есть ли быстрая функция стандартной библиотеки C или C++ для обратного квадратного корня с двойной точностью?

я ловлю себя на том, что печатаю

double foo=1.0/sqrt(...);

много, и я слышал, что современные процессоры имеют встроенные коды операции обратного квадратного корня.

Существует ли функция обратного квадратного корня из стандартной библиотеки C или C++, которая

  1. использует двойную точность с плавающей запятой?
  2. так же точно, как 1.0/sqrt(...)?
  3. так же быстро или быстрее, чем результат 1.0/sqrt(...)?

person Dan    schedule 16.10.2012    source источник
comment
@Pherric Oxide: это был обратный квадрат, а не обратный квадратный корень.   -  person Dan    schedule 17.10.2012
comment
#define INSQRT(x) (1.0/sqrt(x))   -  person Aniket Inge    schedule 17.10.2012
comment
Должен ли он работать на C или C++ или C и C++?   -  person Ryan    schedule 17.10.2012
comment
Есть ли способ изменить математику, чтобы выполнять промежуточную работу в квадратах, а затем извлекать минимальное количество квадратных корней в конце?   -  person paddy    schedule 17.10.2012
comment
Встроенная инструкция обратного квадратного корня, о которой вы слышали, является приближением, а не таким точным, как sqrt. См. tommesani.com/SSEReciprocal.html.   -  person Mark Ransom    schedule 17.10.2012
comment
Возможно, не так быстро, как в Quake III Arena.   -  person Roman R.    schedule 17.10.2012
comment
@Mark Ransom: Это в основном ответ, который я искал.   -  person Dan    schedule 17.10.2012
comment
bugs.llvm.org/show_bug.cgi?id=20900   -  person v.oddou    schedule 06.09.2018


Ответы (7)


Нет. Нет. Не в С++. Неа.

person Lightness Races in Orbit    schedule 16.10.2012

Вы можете использовать эту функцию для более быстрого вычисления обратного квадратного корня
В Википедии есть статья о том, как это работает: https://en.wikipedia.org/wiki/Fast_inverse_square_root
Также существует версия этого алгоритма на языке C.

float invSqrt( float number ){
    union {
        float f;
        uint32_t i;
    } conv;

    float x2;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    conv.f  = number;
    conv.i  = 0x5f3759df - ( conv.i >> 1 );
    conv.f  = conv.f * ( threehalfs - ( x2 * conv.f * conv.f ) );
    return conv.f;
}
person rafaraj    schedule 22.12.2018
comment
Чтение об этом дало мне идею для вопроса. В статье о быстром обратном квадратном корне говорилось, что некоторые аппаратные средства имеют инструкции по обратному квадратному корню из-за того, как много они встречаются в графическом коде. Я не мог использовать этот алгоритм в то время, потому что мне нужна была полная двойная точность, но я поддерживаю всех, кто читает этот ответ, кто не слышал об этом :). - person Dan; 22.12.2018

Я не знаю стандартизированного C API для этого, но это не означает, что вы не можете использовать быстрые инструкции обратного sqrt, если вы готовы написать зависящие от платформы встроенные функции.

Возьмем, к примеру, 64-разрядную версию x86 с AVX, где вы можете использовать _mm256_rsqrt_ps() для аппроксимации обратной величины квадратного корня. Или, точнее: 8 квадратных корней за один раз с использованием SIMD.

#include <immintrin.h>

...

float inputs[8] = { ... } __attribute__ ((aligned (32)));
__m256 input = _mm256_load_ps(inputs);
__m256 invroot = _mm256_rsqrt_ps(input);

Точно так же вы можете использовать встроенный vrsqrteq_f32 на ARM с NEON. В этом случае SIMD имеет ширину 4, поэтому он будет вычислять четыре обратных квадратных корня за один раз.

#include <arm_neon.h>

...

float32x4_t sqrt_reciprocal = vrsqrteq_f32(x);

Даже если вам нужно только одно значение корня на пакет, это все равно быстрее, чем полный квадратный корень. Просто установите вход во все или в одну дорожку регистра SIMD. Таким образом, вам не придется проходить через вашу память с операцией загрузки. На x86 это делается через _mm256_set1_ps(x).

person Bram    schedule 07.06.2020

Нарушение ограничений 1. и 2. (и это тоже не стандартно), но это все же может помочь кому-то просмотреть...

Я использовал ASMJIT для своевременной компиляции именно той операции сборки, которую вы ищете: RSQRTSS ( одинарная точность, хорошо, но она должна быть аналогична двойной).

Мой код таков (см. также мой ответ в другом сообщении):

   typedef float(*JITFunc)();

   JITFunc func;
   asmjit::JitRuntime jit_runtime;
   asmjit::CodeHolder code;
   code.init(jit_runtime.getCodeInfo());

   asmjit::X86Compiler cc(&code);
   cc.addFunc(asmjit::FuncSignature0<float>());

   float value = 2.71; // Some example value.
   asmjit::X86Xmm x = cc.newXmm();
   uint32_t *i = reinterpret_cast<uint32_t*>(&value);
   cc.mov(asmjit::x86::eax, i[0]);
   cc.movd(x, asmjit::x86::eax);

   cc.rsqrtss(x, x);   // THE asm function.

   cc.ret(x);

   cc.endFunc();
   cc.finalize();

   jit_runtime.add(&func, &code);

   // Now, func() can be used as the result to rsqrt(value).

Если вы выполняете часть компиляции JIT только один раз, вызывая ее позже с другими значениями, это должно быть быстрее (хотя и немного менее точно, но это присуще встроенным операциям, о которых вы говорите), чем 1.0/sqrt(...).

person Duke    schedule 30.10.2019

Если вы не боитесь использовать собственные функции, попробуйте следующее:

template <typename T>
T invsqrt(T x)
{
    return 1.0 / std::sqrt(x);
}

Он должен быть таким же быстрым, как оригинальный 1.0 / std::sqrt(x) в любом современном оптимизированном компиляторе. Кроме того, его можно использовать с двойниками или поплавками.

person Ryan    schedule 16.10.2012
comment
нарушает правило № 3 в вопросе! - person Aniket Inge; 17.10.2012
comment
Извините, как я понимаю, это должно быть так же быстро. - person Ryan; 17.10.2012
comment
Прочитайте stackoverflow.com/questions/2442358/ чтобы увидеть, почему функции шаблона должны работать медленнее, чем код без шаблонов. - person Ryan; 17.10.2012
comment
Кроме того, если вы включите -ffast-math в gcc, он будет использовать приближение к обратному квадратному корню. Это гарантирует, что он будет таким же быстрым / быстрее, чем обычный квадратный корень. - person Azmisov; 17.09.2016

Если вы обнаружите, что пишете одно и то же снова и снова, вы должны подумать про себя «функция!»:

double invsqrt(const double x)
{
    return 1.0 / std::sqrt(x);
}

Теперь код более самодокументируемый: людям не нужно выводить 1.0 / std::sqrt(x) обратный квадратный корень, они читают его. Кроме того, теперь вы можете подключить любую реализацию, которую хотите, и каждый сайт вызова автоматически использует обновленное определение.

Чтобы ответить на ваш вопрос, нет, для него нет функции C (++), но теперь, когда вы ее создали, если вы обнаружите, что ваша производительность слишком низкая, вы можете заменить свое собственное определение.

person GManNickG    schedule 16.10.2012
comment
нарушает правило № 3 в вопросе - person Aniket Inge; 17.10.2012
comment
зачем полагаться на компилятор, когда можно использовать препроцессор? Я все еще думаю, что не заслужил голосование -ve :-( - person Aniket Inge; 17.10.2012
comment
@PrototypeStark: Потому что это не так просто, как «или-или». Один проверяет тип, отлаживает, масштабирует, перегружает, оценивает свой аргумент как выражение один раз и т. д. (все функции функции), другой - нет. И это единственный минус, это не конец света; Я понимаю, что неприятно не получить причину от самого человека, но так оно и есть. - person GManNickG; 17.10.2012
comment
он просто проголосовал против и ушел, да, это расстраивает. Хотя я тоже нахожу это забавным. - person Aniket Inge; 17.10.2012
comment
Я бы сказал, что 1.0/sqrt(x) легче читать как обратный квадратный корень, чем invsqrt(x), поскольку первое использует менее двусмысленное математическое обозначение, а не аббревиатуру. - person Dan; 17.10.2012
comment
@Dan: Это нормально, конечно, вам решать, что вам легко читать. Но со временем, я думаю, вы обнаружите, что в принципе гораздо лучше скрывать детали, в том числе то, что составляет обратный квадратный корень. - person GManNickG; 17.10.2012
comment
Однако обратный квадратный корень просто означает обратную величину квадратного корня. Это не деталь, это буквально то, что означает название функции. - person Dan; 17.10.2012
comment
@Dan: Вы путаете реализацию со спецификацией. Вы правы, обратный квадратный корень — это величина, обратная квадратному корню, но как из этого получить 1.0 / sqrt(x)? Это, конечно, несложно, но суть не в этом: это все равно разделение от спецификации к реализации. Скрыть реализацию, сохранить спецификацию; это облегчает рассуждения и поддержку вашей программы. Подумайте, как легко было бы оптимизировать каждое вычисление обратного квадратного корня во всей вашей программе, просто изменив реализацию и сохранив спецификацию. - person GManNickG; 17.10.2012
comment
@GManNickG: Хотя обычно я первым соглашаюсь с этой логикой, существуют ограничения. Вы бы не написали функцию multiplyByTwo — вы бы написали *2. Лично я бы сказал, что пример с обратным квадратным корнем находится прямо на границе. - person Lightness Races in Orbit; 17.10.2012

почему бы не попробовать это? #define INSQRT(x) (1.0/sqrt(x))

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

person Aniket Inge    schedule 16.10.2012
comment
Я не минусовал, но здесь нет смысла использовать макрос, когда подойдет функция. (Вы даже сами сказали: сделать так, чтобы это ощущалось как функция? Просто на самом деле сделать функцию.) - person GManNickG; 17.10.2012
comment
@GManNickG Причина, по которой я не преобразовал его в функцию, заключается в том, что в вопросе четко указано: так же быстро или быстрее, чем результат 1.0/sqrt(...). Превращение его в функцию добавит дополнительные накладные расходы, делая оператор 1.0/sqrt(...) МЕДЛЕННЫМ. - person Aniket Inge; 17.10.2012
comment
Ни на одном компиляторе последнего десятилетия. - person GManNickG; 17.10.2012
comment
@PrototypeStark: предоставьте контрольные показатели, подтверждающие ваше утверждение о том, что использование реальной функции будет медленнее. Макросов можно безопасно избегать при отсутствии доказательств того, что они необходимы для соответствия какому-либо критерию. Тем не менее, я всегда ношу с собой #define isNaN(x) ((x)!=(x)); иногда просто приятно быть таким плохим. - person Lightness Races in Orbit; 17.10.2012