이 두 가지 방법 중 C에서 더 효율적인 방법은 무엇입니까? 그리고 어떨까요 :
pow(x,3)
대
x*x*x // etc?
이 두 가지 방법 중 C에서 더 효율적인 방법은 무엇입니까? 그리고 어떨까요 :
pow(x,3)
대
x*x*x // etc?
답변:
이 코드를 사용하여 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
컴파일러가 최적화하지 않도록 모든 pow 계산의 결과를 누적합니다.
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
이것은 Ubuntu 9.10 64 비트를 실행하는 Intel Core Duo에 있습니다. -o2 최적화와 함께 gcc 4.4.1을 사용하여 컴파일되었습니다.
따라서 C에서는 과부하 가 없기 때문에 yes가보다 x*x*x
빠릅니다 . C ++에서는 거의 동일합니다. (내 테스트의 방법론이 정확하다고 가정합니다.)pow(x, 3)
pow(double, int)
이것은 An Markm의 의견에 대한 응답입니다.
경우에도 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;
}
std::pow
8 * loops 시간 (지수> 2의 경우)을 호출 합니다 -fno-math-errno
. 그런 다음 내가 생각했던 것처럼 루프에서 pow call을 당길 수 있습니다. 나는 errno를 글로벌, 스레드 안전이기 때문에 추측은 가능성이 errno를 여러 번 ... 특급 = 1을 설정하고 펑 호출이와 단지 루프 밖으로 게양되어 있기 때문에 특급 = 2 빠르다에이 펑를 호출해야합니다 -O3
(.. 와 - ffast - 수학 , 너무, 합계-의-8 루프 외부 않습니다).
pow
루프 밖으로 끌어 올려 진 호출 과 인라인되기 때문에 깨져서 거기에 큰 결함이 있습니다. 또한 모든 테스트가 동일한 시간에 실행되기 때문에 대부분 FP 추가 대기 시간을 테스트하는 것처럼 보입니다. test5
보다 느릴 것으로 예상 test1
되지만 그렇지 않습니다. 여러 누산기를 사용하면 종속성 체인이 분할되고 대기 시간이 숨겨집니다.
pow
끊임없이 변화하는 값 을 적용 하여 벤치 마크를 수정 해 보겠습니다 (반복되는 pow 표현이 튀어 나오지 않도록 방지).
그것은 잘못된 종류의 질문입니다. 올바른 질문은 "내 코드를 읽는 사람이 이해하기 더 쉬운 것은 무엇입니까?"입니다.
속도가 중요하다면 (나중에) 묻지 말고 측정하십시오. (그리고 그 전에 이것을 최적화하는 것이 실제로 눈에 띄는 차이를 만드는지 측정하십시오.) 그때까지는 가장 읽기 쉽도록 코드를 작성하십시오.
이를 명확히하기 위해 편집하십시오 (이미 그래야 했음에도 불구하고) : 획기적인 속도 향상은 일반적으로 더 나은 알고리즘 사용 , 데이터 지역성 개선 , 동적 메모리 사용 감소 , 사전 계산 결과 등과 같은 것에서 비롯됩니다. 단일 함수 호출을 마이크로 최적화 하고 수행하는 경우 매우 적은 수의 장소 에서 수행합니다. 이는 신중 하고 시간이 많이 걸리는 프로파일 링을 통해서만 찾을수 있습니다. 매우 직관적이지 않은 작업을 수행하면 속도를 높일 수 있습니다. 물건 (삽입과 같은noop
한 플랫폼에 대한 최적화가 다른 플랫폼에 대한 비관적 일 때도 있습니다 (이는 환경을 완전히 알지 못하기 때문에 질문하는 대신 측정해야하는 이유입니다).
다시 한 번 강조하겠습니다. 그런 것들이 중요한 몇 안되는 애플리케이션에서도 사용되는 대부분의 장소에서는 중요하지 않으며 코드를 보면 중요한 위치를 찾을 가능성 이 거의 없습니다. 코드를 최적화 하는 것은 시간 낭비 일 뿐이므로 먼저 핫스팟 을 식별 해야합니다 .
단일 작업 (예 : 일부 값의 제곱 계산)이 애플리케이션 실행 시간의 10 %를 차지 하더라도 (IME는 매우 드뭅니다), 최적화하더라도 해당 작업에 필요한 시간의 50 %를 절약 합니다 (IME가 훨씬 더 드물지만) 애플리케이션에 소요 되는 시간이 5 % 줄었습니다 .
사용자는이를 알아 채기 위해 스톱워치가 필요합니다. (난 아무것도에서 20 %의 속도 향상은 대부분의 사용자에게 주목 간다 대부분의 경우 같아요. 그리고 그것은 당신이 찾아야 사 개 등의 명소입니다.)
x*x
나 x*x*x
보다 더 빨리 될 것입니다 pow
때문에, pow
반면, 일반적인 경우와 반드시 계약 x*x
에만 적용됩니다. 또한 함수 호출 등을 제거 할 수 있습니다.
그러나 이와 같은 마이크로 최적화를 발견하면 프로파일 러를 확보하고 심각한 프로파일 링을 수행해야합니다. 압도적 인 확률은 둘 사이의 차이점을 결코 눈치 채지 못할 것입니다.
x*x*x
이중 테스트를 std::pow(double base, int exponent)
했는데 통계적으로 의미있는 성능 차이를 볼 수 없습니다.
나는 또한 성능 문제에 대해 궁금해했으며 @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 :: pow (x, 2) 케이스를 최적화하려고 시도하지만 std :: pow (x, 3) 케이스는 그렇지 않은 것 같습니다 (std :: pow보다 40 배 더 오래 걸립니다). (x, 2) 케이스). 모든 경우에 수동 확장이 더 나은 성능을 보였지만 특히 파워 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;
}
지수가 일정하고 작 으면 확장하여 곱셈 수를 최소화합니다. (예를 들어, x^4
최적 아니다 x*x*x*x
하지만 y*y
여기서 y=x*x
그리고. x^5
인 y*y*x
곳 y=x*x
등.). 일정한 정수 지수를 들어, 단지 이미 최적화 된 양식을 작성; 지수가 작은 경우 이는 코드가 프로파일 링되었는지 여부에 관계없이 수행되어야하는 표준 최적화입니다. 최적화 된 양식은 기본적으로 항상 수행 할 가치가있는 많은 경우에서 더 빠릅니다.
(Visual C ++를 사용하는 경우 std::pow(float,int)
내가 언급 한 최적화를 수행하여 연산 순서가 지수의 비트 패턴과 관련되어 있습니다.하지만 컴파일러가 루프를 풀어줄 것이라는 보장은 없지만 그래도 할 가치가 있습니다. 손으로.)
BTW pow
는 프로파일 러 결과에 (un) 놀라운 경향이 있습니다. 절대적으로 필요하지 않고 (예 : 지수가 크거나 상수가 아님) 성능에 대해 전혀 걱정하지 않는 경우 최적의 코드를 작성하고 프로파일 러가이를 알려줄 때까지 기다리는 것이 가장 좋습니다 (놀랍게도 ) 더 생각하기 전에 시간 낭비. (대안은 전화를 걸어 pow
프로파일 러가 (당연히) 시간을 낭비하고 있다고 알려주도록하는 것입니다. 지능적으로 수행하여이 단계를 단축하고 있습니다.)
나는 비슷한 문제로 바빴고 결과에 상당히 당황합니다. 나는 n 체 상황에서 뉴턴 중력에 대해 x⁻³ / ²를 계산했습니다 (거리 벡터 d에 위치한 질량 M의 다른 물체에서 발생하는 가속도) : a = M G d*(d²)⁻³/²
(여기서 d²는 d 자체의 도트 (스칼라) 곱), 계산 M*G*pow(d2, -1.5)
이 더 간단 하다고 생각했습니다.M*G/d2/sqrt(d2)
트릭은 작은 시스템의 경우에는 사실이지만 시스템의 크기 M*G/d2/sqrt(d2)
가 커짐에 따라 효율성이 높아지고 다른 데이터에 대한 작업을 반복하는 것은 그렇지 않기 때문에 시스템의 크기가이 결과에 영향을 미치는 이유를 이해할 수 없습니다. 시스템이 성장함에 따라 가능한 최적화가있는 것처럼 보이지만pow
x
정수 또는 부동 소수점은?