Есть ли какое-то преимущество в использовании pow (x, 2) вместо x * x с x double? - PullRequest
44 голосов
/ 12 июня 2011

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

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

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

double x;
double square = x*x;

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

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

Ответы [ 8 ]

54 голосов
/ 12 июня 2011

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

x = x * x;

и

y = pow(y, 2);

приводят к такой же сборкеcode:

#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)никогда не может быть больше оптимальным, чем обычное умножение.

23 голосов
/ 12 июня 2011

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

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


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

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

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

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

См. Также Кнут:

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

и Джексон:

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

13 голосов
/ 12 июня 2011

Мало того, что x*x яснее, это будет, по крайней мере, так же быстро, как pow(x,2).

10 голосов
/ 12 июня 2011

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

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

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

По сравнению с языкаминацелены на научное программирование:

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

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

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

9 голосов
/ 07 августа 2014

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

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.

7 голосов
/ 12 июня 2011

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

6 голосов
/ 12 июня 2011

ИМХО:

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

Приветствия

1 голос
/ 14 октября 2013

Я бы, вероятно, выбрал 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, что сделало бы вашу компиляцию немного быстрее при прочих равных условиях.

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