Подтвердить что ты не робот

Есть ли какое-либо преимущество при использовании pow (x, 2) вместо x * x, с x double?

есть ли какое-то преимущество в использовании этого кода

double x;
double square = pow(x,2);

вместо этого?

double x;
double square = x*x;

Я предпочитаю x * x и глядя на мою реализацию (Microsoft), я не нахожу преимуществ в pow, потому что x * x проще, чем pow для конкретного квадратного случая.

Есть ли какой-нибудь конкретный случай, когда pow превосходит?

4b9b3361

Ответ 1

FWIW, с gcc-4.2 в MacOS X 10.6 и -O3 флаги компилятора,

x = x * x;

и

y = pow(y, 2);

приводит к тому же самому ассемблеру:

#include <cmath>

void test(double& x, double& y) {
        x = x * x;
        y = pow(y, 2);
}

Собран:

    pushq   %rbp
    movq    %rsp, %rbp
    movsd   (%rdi), %xmm0
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, (%rdi)
    movsd   (%rsi), %xmm0
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, (%rsi)
    leave
    ret

Итак, пока вы используете достойный компилятор, напишите, какой смысл имеет смысл для вашего приложения, но считайте, что pow(x, 2) никогда не может быть более оптимальным, чем простое умножение.

Ответ 2

std:: pow более выразителен, если вы имеете в виду x², xx более выразителен, если вы имеете в виду xx, особенно если вы просто кодируете, например. научная статья и читатели должны уметь понимать вашу реализацию в сравнении с бумагой. Разница тонкая, возможно, для x * x/x², но я думаю, что если вы используете именованные функции в целом, это увеличивает экспрессивность кода и читаемость.

В современных компиляторах, например, g++ 4.x, std:: pow (x, 2) будет встроен, если он даже не встроен в компилятор, а сила - уменьшена до x * x. Если не по умолчанию, и вы не заботитесь о соответствии плавающего типа IEEE, проверьте свое руководство по компиляции для быстрого математического переключателя (g++ == -ffast-math).


Sidenote: Было упомянуто, что включение math.h увеличивает размер программы. Мой ответ:

В С++ вы #include <cmath>, не math.h. Кроме того, если ваш компилятор не является старомодным, он увеличит размер ваших программ только тем, что вы используете (в общем случае), и если ваше внедрение std:: pow просто связано с соответствующими инструкциями x87 и современным g++ уменьшит x² с x * x, тогда нет соответствующего увеличения размера. Кроме того, размер программы никогда не должен когда-либо диктовать, насколько выразительно вы делаете свой код.

Дополнительным преимуществом cmath над math.h является то, что с cmath вы получаете перегрузку std:: pow для каждого типа с плавающей точкой, тогда как с math.h вы получаете pow, powf и т.д. в глобальном пространстве имен, поэтому cmath повышает адаптивность кода, особенно при написании шаблонов.

Как правило: Предпочитайте выразительный и понятный код с сомнительно обоснованной производительностью и аргументированным кодом размера двоичного кода.

См. также Knuth:

"Мы должны забыть о небольшой эффективности, скажем, около 97% времени: преждевременная оптимизация - корень всех злых"

и Джексон:

Первое правило оптимизации программы: не делайте этого. Второе правило оптимизации программы (только для экспертов!): Не делайте этого еще.

Ответ 3

Не только x*x clearer, это, безусловно, будет как минимум так же быстро, как pow(x,2).

Ответ 4

Этот вопрос затрагивает одну из ключевых недостатков большинства реализаций C и С++ в отношении научного программирования. Перейдя с Fortran на C около двадцати лет, а затем на С++, это остается одним из тех болевых пятен, которые изредка заставляют меня задаться вопросом, подходит ли этот переключатель.

Проблема в двух словах:

  • Самый простой способ реализации pow - Type pow(Type x; Type y) {return exp(y*log(x));}
  • Большинство компиляторов C и С++ делают простой выход.
  • Некоторые могут "поступать правильно", но только на высоких уровнях оптимизации.
  • По сравнению с x*x, простой выход с pow(x,2) чрезвычайно дорогостоящим образом вычисляет и теряет точность.

Сравнение с языками, предназначенными для научного программирования:

  • Вы не пишете pow(x,y). Эти языки имеют встроенный оператор возведения в степень. То, что C и С++ неуклонно отказываются выполнять оператор возведения, заставляет кровь многих программистов-программистов кипеть. Для некоторых несгибаемых программистов Fortran это само по себе является причиной никогда не переключаться на C.
  • Fortran (и другие языки) обязаны "делать правильную вещь" для всех малых целых степеней, где small - любое целое число от -12 до 12. (Компилятор несовместим, если он не может выполнить правильная вещь ".) Кроме того, они должны сделать это с отключением оптимизации.
  • Многие компиляторы Fortran также знают, как извлечь некоторые рациональные корни, не прибегая к простому пути.

Существует проблема с использованием высоких уровней оптимизации для "правильной работы". Я работал в нескольких организациях, которые запретили использование оптимизации в критическом для безопасности программном обеспечении. Воспоминания могут быть очень длинными (много десятилетий спустя) после потери 10 миллионов долларов здесь, 100 миллионов, из-за ошибок в некотором оптимизирующем компиляторе.

IMHO, никогда не следует использовать pow(x,2) в C или С++. Я не одинок в этом мнении. Программисты, которые используют pow(x,2), обычно получают большое количество раз во время просмотра кода.

Ответ 5

В С++ 11 есть один случай, когда есть преимущество использования x * x over std::pow(x,2), и этот случай должен использоваться в constexpr:

constexpr double  mySqr( double x )
{
      return x * x ;
}

Как мы можем видеть std:: pow не помечен как constexpr, и поэтому он непригоден для функции constexpr.

В противном случае с точки зрения производительности, помещая следующий код в godbolt, вы увидите следующие функции:

#include <cmath>

double  mySqr( double x )
{
      return x * x ;
}

double  mySqr2( double x )
{
      return std::pow( x, 2.0 );
}

создать идентичную сборку:

mySqr(double):
    mulsd   %xmm0, %xmm0    # x, D.4289
    ret
mySqr2(double):
    mulsd   %xmm0, %xmm0    # x, D.4292
    ret

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

Стоит отметить, что в настоящее время gcc рассматривает pow constexpr, также охватываемый здесь, но это не- Соответствующее расширение и не следует полагаться и, вероятно, изменится в последующих выпусках gcc.

Ответ 6

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

Ответ 7

ИМХО:

  • читаемость кода
  • Устойчивость кода - будет легче изменить на pow(x, 6), возможно, будет реализован какой-то механизм с плавающей запятой для конкретного процессора и т.д.
  • Производительность - если есть более умный и быстрый способ вычислить это (используя ассемблер или какой-то специальный трюк), pow сделает это. вы не будете..:)

Приветствия

Ответ 8

Я бы выбрал std::pow(x, 2), потому что это могло бы облегчить мой рефакторинг кода. И без каких-либо изменений код не будет иметь никакого значения.

Теперь два подхода не идентичны. Это мой тестовый код:

#include<cmath>

double square_explicit(double x) {
  asm("### Square Explicit");
  return x * x;
}

double square_library(double x) {
  asm("### Square Library");  
  return std::pow(x, 2);
}

Вызов asm("text"); просто записывает комментарии на вывод сборки, который я использую (GCC 4.8.1 на OS X 10.7.4):

g++ example.cpp -c -S -std=c++11 -O[0, 1, 2, or 3]

Вам не нужно -std=c++11, я всегда использую его.

Во-первых: при отладке (с нулевой оптимизацией) создаваемая сборка отличается; это соответствующая часть:

# 4 "square.cpp" 1
    ### Square Explicit
# 0 "" 2
    movq    -8(%rbp), %rax
    movd    %rax, %xmm1
    mulsd   -8(%rbp), %xmm1
    movd    %xmm1, %rax
    movd    %rax, %xmm0
    popq    %rbp
LCFI2:
    ret
LFE236:
    .section __TEXT,__textcoal_nt,coalesced,pure_instructions
    .globl __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_
    .weak_definition __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_
__ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_:
LFB238:
    pushq   %rbp
LCFI3:
    movq    %rsp, %rbp
LCFI4:
    subq    $16, %rsp
    movsd   %xmm0, -8(%rbp)
    movl    %edi, -12(%rbp)
    cvtsi2sd    -12(%rbp), %xmm2
    movd    %xmm2, %rax
    movq    -8(%rbp), %rdx
    movd    %rax, %xmm1
    movd    %rdx, %xmm0
    call    _pow
    movd    %xmm0, %rax
    movd    %rax, %xmm0
    leave
LCFI5:
    ret
LFE238:
    .text
    .globl __Z14square_libraryd
__Z14square_libraryd:
LFB237:
    pushq   %rbp
LCFI6:
    movq    %rsp, %rbp
LCFI7:
    subq    $16, %rsp
    movsd   %xmm0, -8(%rbp)
# 9 "square.cpp" 1
    ### Square Library
# 0 "" 2
    movq    -8(%rbp), %rax
    movl    $2, %edi
    movd    %rax, %xmm0
    call    __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_
    movd    %xmm0, %rax
    movd    %rax, %xmm0
    leave
LCFI8:
    ret

Но когда вы создаете оптимизированный код (даже на самом низком уровне оптимизации для GCC, что означает -O1), код просто идентичен:

# 4 "square.cpp" 1
    ### Square Explicit
# 0 "" 2
    mulsd   %xmm0, %xmm0
    ret
LFE236:
    .globl __Z14square_libraryd
__Z14square_libraryd:
LFB237:
# 9 "square.cpp" 1
    ### Square Library
# 0 "" 2
    mulsd   %xmm0, %xmm0
    ret

Итак, это действительно не имеет значения, если вы не заботитесь о скорости неоптимизированного кода.

Как я уже сказал: мне кажется, что std::pow(x, 2) более четко передает ваши намерения, но это вопрос предпочтения, а не производительность.

И оптимизация, похоже, сохраняется даже для более сложных выражений. Возьмем, например:

double explicit_harder(double x) {
  asm("### Explicit, harder");
  return x * x - std::sin(x) * std::sin(x) / (1 - std::tan(x) * std::tan(x));
}

double implicit_harder(double x) {
  asm("### Library, harder");
  return std::pow(x, 2) - std::pow(std::sin(x), 2) / (1 - std::pow(std::tan(x), 2));
}

Опять же, с -O1 (самая низкая оптимизация), сборка снова идентична:

# 14 "square.cpp" 1
    ### Explicit, harder
# 0 "" 2
    call    _sin
    movd    %xmm0, %rbp
    movd    %rbx, %xmm0
    call    _tan
    movd    %rbx, %xmm3
    mulsd   %xmm3, %xmm3
    movd    %rbp, %xmm1
    mulsd   %xmm1, %xmm1
    mulsd   %xmm0, %xmm0
    movsd   LC0(%rip), %xmm2
    subsd   %xmm0, %xmm2
    divsd   %xmm2, %xmm1
    subsd   %xmm1, %xmm3
    movapd  %xmm3, %xmm0
    addq    $8, %rsp
LCFI3:
    popq    %rbx
LCFI4:
    popq    %rbp
LCFI5:
    ret
LFE239:
    .globl __Z15implicit_harderd
__Z15implicit_harderd:
LFB240:
    pushq   %rbp
LCFI6:
    pushq   %rbx
LCFI7:
    subq    $8, %rsp
LCFI8:
    movd    %xmm0, %rbx
# 19 "square.cpp" 1
    ### Library, harder
# 0 "" 2
    call    _sin
    movd    %xmm0, %rbp
    movd    %rbx, %xmm0
    call    _tan
    movd    %rbx, %xmm3
    mulsd   %xmm3, %xmm3
    movd    %rbp, %xmm1
    mulsd   %xmm1, %xmm1
    mulsd   %xmm0, %xmm0
    movsd   LC0(%rip), %xmm2
    subsd   %xmm0, %xmm2
    divsd   %xmm2, %xmm1
    subsd   %xmm1, %xmm3
    movapd  %xmm3, %xmm0
    addq    $8, %rsp
LCFI9:
    popq    %rbx
LCFI10:
    popq    %rbp
LCFI11:
    ret

Наконец: подход x * x не требует include ing cmath, который сделает вашу компиляцию еще немного быстрее, если все остальное будет равно.