วิธีใดในสองวิธีนี้ใน 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
โปรดทราบว่าฉันรวบรวมผลลัพธ์ของการคำนวณทุกขั้นตอนเพื่อให้แน่ใจว่าคอมไพเลอร์ไม่ได้ปรับให้เหมาะสม
หากฉันใช้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
นี่คือ Intel Core Duo ที่ใช้ Ubuntu 9.10 64 บิต รวบรวมโดยใช้ gcc 4.4.1 พร้อมการเพิ่มประสิทธิภาพ -o2
ดังนั้นใน C ใช่x*x*x
จะเร็วกว่าpow(x, 3)
เพราะไม่มีการpow(double, int)
โอเวอร์โหลด ในภาษา C ++ จะเหมือนกันโดยประมาณ (สมมติว่าวิธีการในการทดสอบของฉันถูกต้อง)
นี่เป็นการตอบสนองต่อความคิดเห็นของ 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 * ลูปครั้ง (สำหรับตัวแทน> 2) -fno-math-errno
เว้นแต่คุณจะใช้ จากนั้นก็สามารถดึงพาวโทรออกจากลูปได้อย่างที่คิด ฉันเดาว่าเนื่องจาก errno เป็นโกลบอลความปลอดภัยของเธรดจึงต้องการให้การเรียกพาวตั้งค่า errno หลาย ๆ ครั้ง ... exp = 1 และ exp = 2 นั้นเร็วเพราะการเรียกพาวถูกยกออกจากลูปด้วยเพียง-O3
.. ( มี - ffast-mathมันทำผลรวม-of-8 นอกลูปด้วย)
pow
สายเรียกเข้าออกจากวงดังนั้นจึงมีข้อบกพร่องใหญ่ที่นั่น นอกจากนี้ดูเหมือนว่าคุณกำลังทดสอบเวลาแฝงของการเพิ่ม FP เป็นส่วนใหญ่เนื่องจากการทดสอบทั้งหมดทำงานในเวลาเดียวกัน คุณคาดว่าtest5
จะช้ากว่าtest1
แต่ก็ไม่ใช่ การใช้ตัวสะสมหลายตัวจะแยกห่วงโซ่การพึ่งพาและซ่อนเวลาแฝง
pow
กับค่าที่เปลี่ยนแปลงตลอดเวลา (เพื่อป้องกันไม่ให้นิพจน์ธารซ้ำถูกยกออก)
นั่นเป็นคำถามที่ผิด คำถามที่ถูกต้องคือ "ข้อใดเข้าใจง่ายกว่าสำหรับผู้อ่านโค้ดของฉันที่เป็นมนุษย์"
ถ้าเรื่องความเร็ว (ทีหลัง) อย่าถาม แต่วัด (และก่อนหน้านั้นให้วัดว่าการเพิ่มประสิทธิภาพนี้จะสร้างความแตกต่างที่เห็นได้ชัดเจนหรือไม่) ก่อนหน้านั้นให้เขียนโค้ดเพื่อให้อ่านง่ายที่สุด
แก้ไข
เพียงเพื่อให้ชัดเจนนี้ (แม้ว่ามันอยู่แล้วควรจะได้รับ): speedups ก้าวหน้ามักจะมาจากสิ่งที่ต้องการโดยใช้ขั้นตอนวิธีการที่ดีขึ้น ,ปรับปรุงท้องที่ของข้อมูล ,การลดการใช้หน่วยความจำแบบไดนามิก ,ผล pre-คอมพิวเตอร์ฯลฯพวกเขาไม่ค่อยเคยมาจาก การเรียกใช้ฟังก์ชันเดี่ยวที่เพิ่มประสิทธิภาพระดับไมโครและที่ไหนพวกเขาทำได้ในสถานที่น้อยมากซึ่งจะพบได้จากการทำโปรไฟล์อย่างระมัดระวัง (และใช้เวลานาน)บ่อยกว่าที่พวกเขาไม่สามารถเร่งความเร็วได้ด้วยการทำแบบไม่ใช้งานง่าย สิ่งต่างๆ (เช่นการแทรก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-bit, 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) กรณี) ในทุกกรณีการขยายด้วยตนเองทำได้ดีกว่า - แต่โดยเฉพาะอย่างยิ่งสำหรับเคส power 3 (เร็วกว่า 60 เท่า) นี่เป็นสิ่งที่ควรคำนึงถึงอย่างแน่นอนหากใช้ std :: pow () ที่มีอำนาจจำนวนเต็มมากกว่า 2 ในวงที่แน่น
วิธีที่มีประสิทธิภาพที่สุดคือการพิจารณาการเติบโตของการคูณ ตรวจสอบรหัสนี้สำหรับ 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
มีแนวโน้มที่น่าแปลกใจในการครอบตัดในผลลัพธ์ของโปรไฟล์ หากคุณไม่จำเป็นต้องใช้มัน (เช่นเลขชี้กำลังมีขนาดใหญ่หรือไม่คงที่) และคุณกังวลเกี่ยวกับประสิทธิภาพดังนั้นควรเขียนโค้ดที่เหมาะสมที่สุดออกมาและรอให้ผู้สร้างโปรไฟล์แจ้งให้คุณทราบ (น่าแปลกใจ ) เสียเวลาก่อนที่จะคิดต่อไป (อีกทางเลือกหนึ่งคือโทรหาpow
และให้โปรไฟล์บอกคุณว่ามันเสียเวลา (ไม่แปลกใจเลย) - คุณกำลังตัดขั้นตอนนี้ออกไปโดยทำอย่างชาญฉลาด)
ฉันยุ่งอยู่กับปัญหาที่คล้ายกันและฉันค่อนข้างงงกับผลลัพธ์ ฉันกำลังคำนวณx⁻³ / ²สำหรับความโน้มถ่วงของนิวตันในสถานการณ์ n-body (การเร่งความเร็วเกิดขึ้นจากมวลกายอื่น M ซึ่งอยู่ที่เวกเตอร์ระยะทาง d): 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
จุดหนึ่งหรือลอย?