ฟังก์ชันเวอร์ชันอินไลน์จะส่งคืนค่าที่แตกต่างจากเวอร์ชันที่ไม่ใช่แบบอินไลน์


85

ฟังก์ชันเดียวกันสองเวอร์ชันจะแตกต่างกันได้อย่างไรในเวอร์ชันหนึ่งที่เป็นแบบอินไลน์และฟังก์ชันอื่นจะไม่คืนค่าที่ต่างกัน นี่คือโค้ดบางส่วนที่ฉันเขียนในวันนี้และฉันไม่แน่ใจว่ามันทำงานอย่างไร

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

ฉันคาดหวังว่าเอาต์พุตทั้งหมดจะเท่ากับ1แต่จริงๆแล้วเอาต์พุตนี้ (g ++ 8.3.1 ไม่มีแฟล็ก):

1
0
1

แทน

1
1
1

แก้ไข: clang ++ 7.0.0 แสดงผลลัพธ์นี้:

0
0
0

และ g ++ - เร็วสิ่งนี้:

1
1
1

3
คุณช่วยระบุคอมไพเลอร์ตัวเลือกคอมไพเลอร์ที่คุณใช้และเครื่องอะไรได้บ้าง? ใช้งานได้ดีสำหรับฉันบน GCC 7.1 บน Windows
Diodacus

31
เป็น==ค่าทศนิยมที่คาดเดาไม่ได้เสมอไปใช่หรือไม่?
500 - ข้อผิดพลาดภายในเซิร์ฟเวอร์

3
ที่เกี่ยวข้องstackoverflow.com/questions/588004/…
maximum_prime_is_463035818

2
คุณได้ตั้งค่า-Ofastตัวเลือกซึ่งอนุญาตให้เพิ่มประสิทธิภาพดังกล่าวหรือไม่?
cmdLP

4
ผลตอบแทนที่คอมไพเลอร์สำหรับcbrt(27.0)ค่าของในขณะที่ผลตอบแทนห้องสมุดมาตรฐาน0x0000000000000840 0x0100000000000840คู่ผสมต่างกันในเลข 16 หลังลูกน้ำ ระบบของฉัน: archlinux4.20 x64 gcc8.2.1 glibc2.28 ตรวจสอบกับนี้ สงสัยว่า gcc หรือ glibc ถูกต้อง
KamilCuk

คำตอบ:


73

คำอธิบาย

คอมไพเลอร์บางตัว (โดยเฉพาะ GCC) ใช้ความแม่นยำสูงกว่าเมื่อประเมินนิพจน์ในเวลาคอมไพล์ หากนิพจน์ขึ้นอยู่กับอินพุตและตัวอักษรคงที่เท่านั้นอาจมีการประเมินในเวลาคอมไพล์แม้ว่านิพจน์จะไม่ได้กำหนดให้กับตัวแปร constexpr สิ่งนี้จะเกิดขึ้นหรือไม่ขึ้นอยู่กับ:

  • ความซับซ้อนของนิพจน์
  • ขีด จำกัด ที่คอมไพลเลอร์ใช้เป็นจุดตัดเมื่อพยายามดำเนินการประเมินเวลาคอมไพล์
  • ฮิวริสติกอื่น ๆ ที่ใช้ในกรณีพิเศษ (เช่นเมื่อเสียงดังขึ้นลูป)

หากนิพจน์ถูกจัดเตรียมไว้อย่างชัดเจนเช่นเดียวกับในกรณีแรกจะมีความซับซ้อนต่ำกว่าและคอมไพเลอร์มีแนวโน้มที่จะประเมินในเวลาคอมไพล์

ในทำนองเดียวกันถ้าฟังก์ชันถูกทำเครื่องหมายแบบอินไลน์คอมไพลเลอร์มีแนวโน้มที่จะประเมินค่าในเวลาคอมไพล์เนื่องจากฟังก์ชันแบบอินไลน์จะเพิ่มขีด จำกัด ที่สามารถเกิดการประเมินได้

ระดับการเพิ่มประสิทธิภาพที่สูงขึ้นยังเพิ่มเกณฑ์นี้เช่นเดียวกับในตัวอย่าง -Ofast โดยที่นิพจน์ทั้งหมดประเมินเป็นจริงบน gcc เนื่องจากการประเมินเวลาคอมไพล์ที่มีความแม่นยำสูงขึ้น

เราสามารถสังเกตพฤติกรรมนี้ได้ที่นี่ใน compiler explorer เมื่อคอมไพล์ด้วย -O1 เฉพาะฟังก์ชันที่มีเครื่องหมายอินไลน์เท่านั้นที่จะได้รับการประเมินในเวลาคอมไพล์ แต่ที่ -O3 ทั้งสองฟังก์ชันจะได้รับการประเมินตามเวลาคอมไพล์

หมายเหตุ: ในตัวอย่าง compiler-explorer ฉันใช้printfiostream แทนเพราะลดความซับซ้อนของฟังก์ชันหลักทำให้มองเห็นเอฟเฟกต์ได้ชัดเจนขึ้น

แสดงให้เห็นว่าinlineไม่มีผลกับการประเมินรันไทม์

เราสามารถมั่นใจได้ว่าจะไม่มีการประเมินนิพจน์ใด ๆ ในเวลาคอมไพล์โดยรับค่าจากอินพุตมาตรฐานและเมื่อเราทำเช่นนี้นิพจน์ทั้ง 3 จะแสดงผลเท็จตามที่แสดงไว้ที่นี่: https://ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

ตรงกันข้ามกับตัวอย่างนี้โดยที่เราใช้การตั้งค่าคอมไพเลอร์เดียวกัน แต่ให้ค่าในเวลาคอมไพล์ส่งผลให้การประเมินเวลาคอมไพล์มีความแม่นยำสูงขึ้น


22

จากที่สังเกตการใช้ตัว==ดำเนินการเพื่อเปรียบเทียบค่าทศนิยมทำให้ได้ผลลัพธ์ที่แตกต่างกันด้วยคอมไพเลอร์ที่แตกต่างกันและในระดับการเพิ่มประสิทธิภาพที่แตกต่างกัน

วิธีที่ดีวิธีหนึ่งในการเปรียบเทียบค่าจุดลอยตัวคือการทดสอบความคลาดเคลื่อนสัมพัทธ์ที่ระบุไว้ในบทความ: ความคลาดเคลื่อนของจุดลอยตัวได้รับการทบทวนอีกครั้ง

ก่อนอื่นเราจะคำนวณค่าEpsilon( ความคลาดเคลื่อนสัมพัทธ์ ) ซึ่งในกรณีนี้จะเป็น:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

จากนั้นใช้ทั้งในฟังก์ชันอินไลน์และไม่อินไลน์ในลักษณะนี้:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

ฟังก์ชั่นตอนนี้คือ:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

ตอนนี้ผลลัพธ์จะเป็นไปตามที่คาดไว้ ( [1 1 1]) ด้วยคอมไพเลอร์ที่แตกต่างกันและในระดับการเพิ่มประสิทธิภาพที่แตกต่างกัน

การสาธิตสด


จุดประสงค์ของการmax()โทรคืออะไร? ตามคำนิยามfloor(x)น้อยกว่าหรือเท่ากับxดังนั้นจะเสมอเท่ากันmax(x, floor(x)) x
Ken Thomases

@KenThomases: ในกรณีนี้โดยเฉพาะซึ่งอาร์กิวเมนต์หนึ่งmaxเป็นเพียงอาร์กิวเมนต์floorอื่นไม่จำเป็นต้องใช้ แต่ฉันถือว่าเป็นกรณีทั่วไปที่อาร์กิวเมนต์maxสามารถเป็นค่าหรือนิพจน์ที่ไม่ขึ้นต่อกัน
PW

ไม่ควรoperator==(double, double)ทำอย่างนั้นตรวจสอบความแตกต่างว่าเล็กกว่าเอปไซลอนที่ปรับขนาดหรือไม่? ประมาณ 90% ของคำถามที่เกี่ยวข้องกับประเด็นลอยตัวเกี่ยวกับ SO จะไม่มีอยู่จริง
Peter - คืนสถานะ Monica

ฉันคิดว่ามันจะดีกว่าถ้าผู้ใช้ระบุEpsilonค่าขึ้นอยู่กับความต้องการเฉพาะของพวกเขา
PW
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.