programing

무엇이 더 효율적인가?가루를 이용해 네모나게 만들거나, 아니면 그냥 곱해서 만들거나?

prostudy 2022. 5. 14. 11:15
반응형

무엇이 더 효율적인가?가루를 이용해 네모나게 만들거나, 아니면 그냥 곱해서 만들거나?

C에서 이 두 가지 방법 중 어떤 것이 더 효율적인가?다음에 대해 알아보십시오.

pow(x,3)

x*x*x // etc?

UPDATE 2021

벤치마크 코드를 다음과 같이 수정했다.

  • std:부스트 대신 타이밍 측정에 사용되는 동기
  • C++11<random>대신에 사용하다rand()
  • 호스트 아웃될 수 있는 작업을 반복하지 마십시오.기본 파라미터는 끊임없이 변화한다.

GCC 10 -O2로 다음 결과를 얻는다(초).

exp     c++ pow     c pow       x*x*x...
2       0.204243    1.39962     0.0902527   
3       1.36162     1.38291     0.107679    
4       1.37717     1.38197     0.106103    
5       1.3815      1.39139     0.117097

GCC 10 -O3은 GCC 10 -O2와 거의 동일하다.

GCC 10 -O2 -fast-math의 경우:

exp     c++ pow     c pow       x*x*x...
2       0.203625    1.4056      0.0913414   
3       0.11094     1.39938     0.108027    
4       0.201593    1.38618     0.101585    
5       0.102141    1.38212     0.10662

GCC 10 -O3 -fast-math의 경우:

exp     c++ pow     c pow       x*x*x...
2       0.0451995   1.175       0.0450497   
3       0.0470842   1.20226     0.051399    
4       0.0475239   1.18033     0.0473844   
5       0.0522424   1.16817     0.0522291

Clang 12 -O2 사용 시:

exp     c++ pow     c pow       x*x*x...
2       0.106242    0.105435    0.105533    
3       1.45909     1.4425      0.102235    
4       1.45629     1.44262     0.108861    
5       1.45837     1.44483     0.1116

클랑 12 -O3은 클랑 12 -O2와 거의 동일하다.

클랑 12 -O2 -fast-math를 사용하는 경우:

exp     c++ pow     c pow       x*x*x...
2       0.0233731   0.0232457   0.0231076   
3       0.0271074   0.0266663   0.0278415   
4       0.026897    0.0270698   0.0268115   
5       0.0312481   0.0296402   0.029811    

클랑 12 -O3 -fast-math는 클랑 12 -O2 -fast-math와 거의 동일하다.

Machine은 Linux 5.4.0-73-generic x86_64의 Intel Core i7-7700K이다.

결론:

  • GCC 10(no -fast-math),x*x*x...항상 빠르다
  • 하는 경우 GCC 10 -O2 -fast-mathmath를 사용하는 경우std::pow만큼 빠르다x*x*x...홀수 지수인 경우
  • 사용하는 경우 GCC 10 -O3 -fast-mathmatrix 사타하하면std::pow만큼 빠르다x*x*x...모든 테스트 사례에 적용되며 -O2보다 약 2배 빠르다.
  • GCC 10으로 C의pow(double, double)언제나 훨씬 느리다.
  • ), 클랑 12 (-fast-math 없음),x*x*x...2보다 큰 지수의 경우 더 빠름
  • 클랑 12 -fast-math를 사용하면 모든 방법이 유사한 결과를 산출한다.
  • 클랑 12번과 함께,pow(double, double)만큼 빠르다std::pow 지수를 지만 수식
  • 컴파일러를 능가하지 않고 벤치마크를 작성하는 것은 어렵다.

나는 결국 내 기계에 GCC의 최신 버전을 설치하고 그렇게 하면 결과를 업데이트 할 것이다.

업데이트된 벤치마크 코드:

#include <cmath>
#include <chrono>
#include <iostream>
#include <random>

using Moment = std::chrono::high_resolution_clock::time_point;
using FloatSecs = std::chrono::duration<double>;

inline Moment now()
{
    return std::chrono::high_resolution_clock::now();
}

#define TEST(num, expression) \
double test##num(double b, long loops) \
{ \
    double x = 0.0; \
\
    auto startTime = now(); \
    for (long i=0; i<loops; ++i) \
    { \
        x += expression; \
        b += 1.0; \
    } \
    auto elapsed = now() - startTime; \
    auto seconds = std::chrono::duration_cast<FloatSecs>(elapsed); \
    std::cout << seconds.count() << "\t"; \
    return x; \
}

TEST(2, b*b)
TEST(3, b*b*b)
TEST(4, b*b*b*b)
TEST(5, b*b*b*b*b)

template <int exponent>
double testCppPow(double base, long loops)
{
    double x = 0.0;

    auto startTime = now();
    for (long i=0; i<loops; ++i)
    {
        x += std::pow(base, exponent);
        base += 1.0;
    }
    auto elapsed = now() - startTime;

    auto seconds = std::chrono::duration_cast<FloatSecs>(elapsed); \
    std::cout << seconds.count() << "\t"; \

    return x;
}

double testCPow(double base, double exponent, long loops)
{
    double x = 0.0;

    auto startTime = now();
    for (long i=0; i<loops; ++i)
    {
        x += ::pow(base, exponent);
        base += 1.0;
    }
    auto elapsed = now() - startTime;

    auto seconds = std::chrono::duration_cast<FloatSecs>(elapsed); \
    std::cout << seconds.count() << "\t"; \

    return x;
}

int main()
{
    using std::cout;
    long loops = 100000000l;
    double x = 0;
    std::random_device rd;
    std::default_random_engine re(rd());
    std::uniform_real_distribution<double> dist(1.1, 1.2);
    cout << "exp\tc++ pow\tc pow\tx*x*x...";

    cout << "\n2\t";
    double b = dist(re);
    x += testCppPow<2>(b, loops);
    x += testCPow(b, 2.0, loops);
    x += test2(b, loops);

    cout << "\n3\t";
    b = dist(re);
    x += testCppPow<3>(b, loops);
    x += testCPow(b, 3.0, loops);
    x += test3(b, loops);

    cout << "\n4\t";
    b = dist(re);
    x += testCppPow<4>(b, loops);
    x += testCPow(b, 4.0, loops);
    x += test4(b, loops);

    cout << "\n5\t";
    b = dist(re);
    x += testCppPow<5>(b, loops);
    x += testCPow(b, 5.0, loops);
    x += test5(b, loops);

    std::cout << "\n" << x << "\n";
}

구답, 2010

사이의 성능 차이를 테스트했다.x*x*...pow(x,i)소규모로i다음 코드 사용:

#include <cstdlib>
#include <cmath>
#include <boost/date_time/posix_time/posix_time.hpp>

inline boost::posix_time::ptime now()
{
    return boost::posix_time::microsec_clock::local_time();
}

#define TEST(num, expression) \
double test##num(double b, long loops) \
{ \
    double x = 0.0; \
\
    boost::posix_time::ptime startTime = now(); \
    for (long i=0; i<loops; ++i) \
    { \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
    } \
    boost::posix_time::time_duration elapsed = now() - startTime; \
\
    std::cout << elapsed << " "; \
\
    return x; \
}

TEST(1, b)
TEST(2, b*b)
TEST(3, b*b*b)
TEST(4, b*b*b*b)
TEST(5, b*b*b*b*b)

template <int exponent>
double testpow(double base, long loops)
{
    double x = 0.0;

    boost::posix_time::ptime startTime = now();
    for (long i=0; i<loops; ++i)
    {
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
    }
    boost::posix_time::time_duration elapsed = now() - startTime;

    std::cout << elapsed << " ";

    return x;
}

int main()
{
    using std::cout;
    long loops = 100000000l;
    double x = 0.0;
    cout << "1 ";
    x += testpow<1>(rand(), loops);
    x += test1(rand(), loops);

    cout << "\n2 ";
    x += testpow<2>(rand(), loops);
    x += test2(rand(), loops);

    cout << "\n3 ";
    x += testpow<3>(rand(), loops);
    x += test3(rand(), loops);

    cout << "\n4 ";
    x += testpow<4>(rand(), loops);
    x += test4(rand(), loops);

    cout << "\n5 ";
    x += testpow<5>(rand(), loops);
    x += test5(rand(), loops);
    cout << "\n" << x << "\n";
}

결과:

1 00:00:01.126008 00:00:01.128338 
2 00:00:01.125832 00:00:01.127227 
3 00:00:01.125563 00:00:01.126590 
4 00:00:01.126289 00:00:01.126086 
5 00:00:01.126570 00:00:01.125930 
2.45829e+54

컴파일러가 최적화되지 않도록 모든 계산 결과를 축적한다는 점에 유의하십시오.

만약 내가 그것을 사용한다면std::pow(double, double) 및 버전loops = 1000000l, 알겠다:

1 00:00:00.011339 00:00:00.011262 
2 00:00:00.011259 00:00:00.011254 
3 00:00:00.975658 00:00:00.011254 
4 00:00:00.976427 00:00:00.011254 
5 00:00:00.973029 00:00:00.011254 
2.45829e+52

이것은 우분투 9.10 64비트를 운영하는 인텔 코어 듀오에 있다.-o2 최적화와 함께 gcc 4.4.1을 사용하여 컴파일.

그래서 C에서는 그렇다.x*x*x보다 빠를 것이다pow(x, 3)왜냐하면 없기 때문이다.pow(double, int)과하하다 C++에서는 대략 똑같을 것이다.(내 테스트의 방법론이 정확하다고 가정한다.)


이는 An Markm의 논평에 대한 반응이다.

a라고 해도using namespace std두 번째 매개 변수가 다음과 같은 경우에 지시문이 발행되었다.pow이다int, 그리고 그 다음std::pow(double, int)로부터 과부하하다.<cmath>대신 불릴 것이다::pow(double, double)로부터<math.h>.

이 테스트 코드는 다음과 같은 동작을 확인한다.

#include <iostream>

namespace foo
{

    double bar(double x, int i)
    {
        std::cout << "foo::bar\n";
        return x*i;
    }


}

double bar(double x, double y)
{
    std::cout << "::bar\n";
    return x*y;
}

using namespace foo;

int main()
{
    double a = bar(1.2, 3); // Prints "foo::bar"
    std::cout << a << "\n";
    return 0;
}

x*x또는x*x*x보다 빠를 것이다pow, 그 이후pow일반적인 경우를 다루어야 하는 반면,x*x구체적다. 할 수 또한, 함수 호출 등을 자유롭게 할 수 있다.

하지만, 만약 여러분이 이렇게 마이크로 최적화를 찾는다면, 여러분은 프로파일러를 구하고 진지한 프로파일링을 할 필요가 있다.압도적인 가능성은 당신이 그 둘 사이의 어떤 차이도 결코 알아차리지 못할 것이다.

성능 문제도 궁금했고, @EmileCormier의 대답을 바탕으로 컴파일러에 의해 이것이 최적화되기를 바라고 있었다.그러나, 나는 그가 보여준 시험 코드가 컴파일러가 여전히 std::pow() 호출을 최적화할 수 있을지 걱정되었다. 매번 같은 값을 호출에 사용했기 때문에 컴파일러가 결과를 저장하고 루프에 다시 사용할 수 있기 때문이다. 이것은 모든 경우에 거의 동일한 런 타임을 설명할 것이다.그래서 나도 한번 조사해 보았다.

다음은 내가 사용한 코드(test_pow.cpp):

#include <iostream>                                                                                                                                                                                                                       
#include <cmath>
#include <chrono>

class Timer {
  public:
    explicit Timer () : from (std::chrono::high_resolution_clock::now()) { }

    void start () {
      from = std::chrono::high_resolution_clock::now();
    }

    double elapsed() const {
      return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - from).count() * 1.0e-6;
    }

  private:
    std::chrono::high_resolution_clock::time_point from;
};

int main (int argc, char* argv[])
{
  double total;
  Timer timer;



  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += std::pow (i,2);
  std::cout << "std::pow(i,2): " << timer.elapsed() << "s (result = " << total << ")\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += i*i;
  std::cout << "i*i: " << timer.elapsed() << "s (result = " << total << ")\n";

  std::cout << "\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += std::pow (i,3);
  std::cout << "std::pow(i,3): " << timer.elapsed() << "s (result = " << total << ")\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += i*i*i;
  std::cout << "i*i*i: " << timer.elapsed() << "s (result = " << total << ")\n";


  return 0;
}

이것은 다음을 사용하여 컴파일되었다.

g++ -std=c++11 [-O2] test_pow.cpp -o test_pow

기본적으로 차이점은 std::pow()가 루프 카운터라는 주장이다.우려했던 대로 실적 차이가 확연하다.-O2 플래그가 없으면 내 시스템(Arch Linux 64비트, g++ 4.9.1, Intel i7-4930)의 결과는 다음과 같았다.

std::pow(i,2): 0.001105s (result = 3.33333e+07)
i*i: 0.000352s (result = 3.33333e+07)

std::pow(i,3): 0.006034s (result = 2.5e+07)
i*i*i: 0.000328s (result = 2.5e+07)

최적화를 통해 결과는 다음과 같았다.

std::pow(i,2): 0.000155s (result = 3.33333e+07)
i*i: 0.000106s (result = 3.33333e+07)

std::pow(i,3): 0.006066s (result = 2.5e+07)
i*i*i: 9.7e-05s (result = 2.5e+07)

따라서 컴파일러는 최소한 std::파우(x,2)의 경우를 최적화하려고 시도하지만 std::파우(x,3)의 경우는 최적화하지 않는다(std:::파우(x,2)의 경우보다 최대 40배가 더 걸린다).모든 경우에서 수동 확장이 더 잘 수행되었지만, 특히 전력 3 케이스(60배 더 빠름)에서는 더 잘 수행되었다.이것은 만약 고정 루프에서 2보다 큰 정수 파워로 std::pow()를 실행한다면 분명히 기억할 가치가 있다.

가장 효율적인 방법은 승수의 기하급수적인 성장을 고려하는 것이다.이 코드에서 p^q:

template <typename T>
T expt(T p, unsigned q){
    T r =1;
    while (q != 0) {
        if (q % 2 == 1) {    // if q is odd
            r *= p;
            q--;
        }
        p *= p;
        q /= 2;
    }
    return r;
}

그건 잘못된 질문이야.올바른 질문은 다음과 같다: "내 코드의 인간 독자들에게 이해하기 쉬운 것은 무엇인가?"

속도가 중요한 경우(더 느리게) 묻지 말고 측정하십시오. (그리고 그 전에 이를 최적화하는 것이 실제로 눈에 띄는 차이를 만들 것인지 측정하십시오.)그때까지 코드를 작성하면 읽기 쉽도록 한다.

편집
이 점을 분명히 하기 위해(이미 그렇게 했어야 했지만):획기적인 속도 향상은 대개 더 나은 알고리즘 사용, 데이터위치 개선, 동적 메모리 사용 감소, 사전 컴퓨팅 결과 등과 같은 것들로부터 온다.그것들은 거의 마이크로 최적화 단일 기능 호출에서 오는 경우가 없으며, 그들이 하는 곳에서는 아주 적은 에서 그렇게 하는데, 그것은 신중한 (그리고 시간이 많이 걸리는) 프로파일링에 의해서만 발견될 수 있을 뿐이며, 아주 비직관적인 일(삽입과 같은)을 함으로써 속도를 높일 수 있는 경우가 더 많다.noop한 플랫폼에 대한 최적화는 때때로 다른 플랫폼에 대한 비관적(그래서 우리가 당신의 환경을 완전히 알지 못하기 때문에 질문하는 대신 측정해야 하는 것이다)이다.

다시 한 번 밑줄을 그어 봅시다.그런 것들이 중요한 몇 안 되는 어플리케이션에서도, 그것들이 사용되는 대부분의 장소에서는 중요하지 않으며, 코드를 보면 그것들이 중요한 장소를 찾을 가능성이 매우 낮다.그렇지 않으면 코드를 최적화하는 것은 시간 낭비일 뿐이기 때문에 우선스팟을 식별해야 한다.

단일 연산(일부 가치의 제곱 계산과 같은)이 애플리케이션 실행 시간10%(IME는 상당히 드물다)를 차지하고, 최적화가 해당 작업에 필요한 시간50%(IME는 훨씬 더 드물다)를 절약하더라도 애플리케이션 실행 시간이 5%만 덜 걸리도록 한 것이다.
사용자들은 그것을 알아차리기 위해 스톱워치가 필요할 것이다. (대부분의 경우 20% 이하의 속도 상승은 대부분의 사용자들이 눈치채지 못할 것이다.그리고 그것은 당신이 찾아야 할 4개의 지점이다.)

지수가 일정하고 작으면 이를 밖으로 확장하여 승수 수를 최소화한다.(예를 들어,x^4최적이 아니다x*x*x*x그렇지만y*y어디에y=x*x. 그리고x^5이다y*y*x어디에y=x*x 등) 최적화된 만 하면 된다.상수 정수 지수의 경우 최적화된 양식을 이미 작성하십시오. 작은 지수를 사용하면 코드가 프로파일링되었는지 여부에 관계없이 표준 최적화 작업을 수행해야 한다.최적화된 형태는 매우 큰 비율의 사례에서 더 빠를 것이며, 기본적으로 항상 그럴 가치가 있다.

C를 사용하면, (Visual C+++flushane)std::pow(float,int)연산 순서가 지수의 비트 패턴과 관련되는 최적화를 수행한다.하지만 컴파일러가 루프를 풀어줄 것이라고 장담할 수는 없으니, 그래도 손으로 할 만한 가치가 있다.)

[편집] BTWpow프로파일러 결과에 따라 증가하는 경향이 있다.절대적으로 필요하지 않고(즉, 지수가 크거나 상수가 아니라면) 성과에 대해 전혀 염려하고 있다면 최적 코드를 작성하고 프로파일러가 (놀라울 정도로) 시간을 낭비하고 있다고 말할 때까지 기다리는 것이 최선이다.(대안은 전화를 거는 것이다.pow그리고 프로파일러에게 시간을 낭비하고 있다고 말하게 하고, 지능적으로 이 단계를 중단시키는 것이다.)

나도 비슷한 문제로 바빴는데, 그 결과에 상당히 어리둥절하다.n-bees 상황에서 뉴턴 인력의 x⁻³/²를 계산하고 있었다(거리 벡터 d에 위치한 질량 M의 다른 신체에서 가속을 거쳤다).a = M G d*(d²)⁻³/²(여기서 d²는 d의 점(scalar) 제품 그 자체로) , 그리고 나는 계산을 생각했다.M*G*pow(d2, -1.5)보다 간단할 것이다M*G/d2/sqrt(d2)

요령은 작은 시스템에서는 사실이지만 시스템의 크기가 커짐에 따라M*G/d2/sqrt(d2)더 효율적이 되고 나는 왜 시스템의 크기가 이 결과에 영향을 미치는지 이해할 수 없다. 왜냐하면 다른 데이터에 대한 작업을 반복하는 것은 그렇지 않기 때문이다.마치 시스템이 성장함에 따라 가능한 최적화가 있었던 것처럼, 그러나 다음으로는 불가능한 것이다.pow

여기에 이미지 설명을 입력하십시오.

참조URL: https://stackoverflow.com/questions/2940367/what-is-more-efficient-using-pow-to-square-or-just-multiply-it-with-itself

반응형