2012-01-15 3 views
393

Я искал эффективный подход для вычисления b (скажем, a = 2 и b = 50). Чтобы начать работу, я решил взглянуть на реализацию функции Math.Pow(). Но в .NET Reflector, все я нашел это:Как внедряется Math.Pow() в .NET Framework?

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical] 
public static extern double Pow(double x, double y); 

Что некоторые из ресурсов, где я могу видеть, как то, что происходит внутри, когда я называю Math.Pow() функцию?

+15

Как и в случае с FYI, если вы запутались во всём 'InternalCall' с модификатором' extern' (поскольку они _seem_ будут конфликтующими), см. [Вопрос (и результирующие ответы)] (http: /stackoverflow.com/questions/1211462/c-sharp-internal-static-extern-with-internalcall-attribute-internal-or-externa), который я опубликовал об этом самом же. – CraigTP

+6

Для операции '2^x', если' x' является целым числом, результатом является операция сдвига. Поэтому, возможно, вы могли бы построить результат, используя мантиссу «2» и показатель «x». – ja72

+0

@SurajJain ваш комментарий на самом деле вопрос, который вам нужно отправить отдельно. – ja72

ответ

811

MethodImplOptions.InternalCall

Это означает, что метод на самом деле реализуется в CLR, написанный на C++. Компилятор «точно в срок» обращается к таблице с внутренне реализованными методами и напрямую компилирует вызов функции C++.

Для просмотра кода необходим исходный код для CLR. Вы можете получить это от SSCLI20 distribution. Он был написан вокруг временного интервала .NET 2.0, я нашел реализации на низком уровне, например Math.Pow(), чтобы быть в значительной степени точным для более поздних версий CLR.

Таблица поиска находится в каталоге clr/src/vm/ecall.cpp. Раздел, который релевантен Math.Pow() выглядит следующим образом:

FCFuncStart(gMathFuncs) 
    FCIntrinsic("Sin", COMDouble::Sin, CORINFO_INTRINSIC_Sin) 
    FCIntrinsic("Cos", COMDouble::Cos, CORINFO_INTRINSIC_Cos) 
    FCIntrinsic("Sqrt", COMDouble::Sqrt, CORINFO_INTRINSIC_Sqrt) 
    FCIntrinsic("Round", COMDouble::Round, CORINFO_INTRINSIC_Round) 
    FCIntrinsicSig("Abs", &gsig_SM_Flt_RetFlt, COMDouble::AbsFlt, CORINFO_INTRINSIC_Abs) 
    FCIntrinsicSig("Abs", &gsig_SM_Dbl_RetDbl, COMDouble::AbsDbl, CORINFO_INTRINSIC_Abs) 
    FCFuncElement("Exp", COMDouble::Exp) 
    FCFuncElement("Pow", COMDouble::Pow) 
    // etc.. 
FCFuncEnd() 

Поиск "COMDouble" принимает вас CLR/SRC/classlibnative/плавать/comfloat.cpp. Я пощажу вам код, просто взгляните на себя. Он в основном проверяет угловые случаи, затем называет версию CRT pow().

Единственная другая деталь реализации, которая интересна, это макрос FCIntrinsic в таблице. Это намек на то, что джиттер может реализовать функцию как внутреннюю. Другими словами, замените вызов функции инструкцией машинного кода с плавающей запятой. Что не относится к Pow(), для него нет инструкции FPU. Но, конечно же, для других простых операций. Примечательно, что это может сделать математику с плавающей запятой в C# существенно быстрее, чем тот же код в C++, по этой причине проверьте this answer.

Кстати, исходный код для CRT также доступен, если у вас есть полная версия каталога Visual Studio vc/crt/src. Вы попадете в стену на pow(), хотя Microsoft купила этот код у Intel. Лучше работать, чем инженеры Intel, маловероятно. Хотя идентичность моей средней школы книга была в два раза быстрее, когда я попробовал:

public static double FasterPow(double x, double y) { 
    return Math.Exp(y * Math.Log(x)); 
} 

Но не истинный заменитель, поскольку он накапливает ошибку от 3 операций с плавающей точкой и не иметь дело с проблемами доменных извращенцев, что Pow () имеет. Как 0^0 и -Infinity подняты до любой степени.

+403

Отличный ответ, StackOverflow требует больше такого рода вещей, а не «Зачем вам это знать?» что происходит слишком часто. –

+1

Согласен с Томом В. Фантастический ответ. Я узнал больше, чем ожидал. Интересно, что я достиг 1000 очков, отметив ваш пост «Ответ». Еще раз спасибо. –

+0

@ Я не уверен, что смогу следить за последним абзацем: если я смотрю на 'crt/src/math.h', я действительно нахожу' _Pow_int' (~ строка 493 на моей установке vs2010), которая, по крайней мере, кажется me используется для всех вызовов pow. И это похоже на очевидный сдвиг и многократное внедрение. Я что-то упустил? (Я предполагаю, что оптимизированная реализация intel будет использовать какой-то SSE-колдовство? Кажется, мы могли бы немного распараллелить его, но не уверены, что это будет улучшение) – Voo

62

Если freely available C version of pow - это любое указание, оно не выглядит так, как вы ожидали бы. Было бы не очень полезно найти версию .NET, потому что проблема, которую вы решаете (т. Е. С целыми числами), упрощает порядки величин и может быть решена в нескольких строках кода C# with the exponentiation by squaring algorithm.

+0

Спасибо за ваш ответ. Первая ссылка удивила меня, поскольку я не ожидал такой масштабной технической реализации функции Pow(). Хотя ответ Hans Passant подтверждает, что он тоже в мире .Net тоже. Я думаю, что я могу решить эту проблему, используя некоторые из методов, перечисленных в ссылке алгоритма squaring. Еще раз спасибо. –

+2

Я не считаю, что этот код эффективен. 30 локальных переменных просто должны отражать все регистры. Я только предполагаю, что это ARM-версия, но на x86 30 локальных переменных в методе потрясающе. –

96

Hans Passant's answer Отлично, но я хотел бы добавить, что если b является целым числом, то a^b может быть вычислено очень эффективно с двоичным разложением.Вот модифицированная версия от Восторга Генри Уоррена Хакера:

public static int iexp(int a, uint b) { 
    int y = 1; 

    while(true) { 
     if ((b & 1) != 0) y = a*y; 
     b = b >> 1; 
     if (b == 0) return y; 
     a *= a; 
    }  
} 

Он отмечает, что эта операция является оптимальной (делает минимальное количество арифметических или логические операции) для всех б < 15. Также нет никакого Известного решения общая задача нахождения оптимальной последовательности факторов для вычисления a^b для любого b, кроме расширенного поиска. Это проблема NP-Hard. Таким образом, в основном это означает, что двоичное разложение так же хорошо, как и получается.

+11

Этот алгоритм ([квадрат и умнож.] (Http://en.wikipedia.org/wiki/Exponentiation_by_squaring)) также применяется, если 'a '- число с плавающей запятой. – CodesInChaos

+12

На практике можно сделать немного лучше, чем собственный квадрат и умножить. Например, для подготовки таблиц поиска для небольших экспонентов, чтобы вы могли квадратироваться несколько раз и только затем умножать или строить оптимизированные цепочки сложения целых чисел для фиксированных показателей. Такая проблема является неотъемлемой частью важных криптографических алгоритмов, поэтому там была довольно много работы по ее оптимизации. Твердость NP - это всего лишь * наихудшая асимптотика *, мы часто можем создавать оптимальные или почти оптимальные решения для случаев возникающей на практике проблемы. – CodesInChaos