ฉันจะปรับปรุงประสิทธิภาพผ่านแนวทางระดับสูงได้อย่างไรเมื่อใช้สมการแบบยาวใน C ++


92

ฉันกำลังพัฒนาแบบจำลองทางวิศวกรรมบางอย่าง สิ่งนี้เกี่ยวข้องกับการใช้สมการยาวบางอย่างเช่นสมการนี้เพื่อคำนวณความเค้นในวัสดุเช่นยาง:

T = (
    mu * (
            pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
            * (
                pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
                - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
            ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l1
            - pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
            - pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
        ) / a
    + K * (l1 * l2 * l3 - 0.1e1) * l2 * l3
) * N1 / l2 / l3

+ (
    mu * (
        - pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l2 / 0.3e1
        + pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
        * (
            pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
            - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
        ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l2
        - pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l2 / 0.3e1
    ) / a
    + K * (l1 * l2 * l3 - 0.1e1) * l1 * l3
) * N2 / l1 / l3

+ (
    mu * (
        - pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l3 / 0.3e1
        - pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l3 / 0.3e1
        + pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
        * (
            pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
            - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
        ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l3
    ) / a
+ K * (l1 * l2 * l3 - 0.1e1) * l1 * l2
) * N3 / l1 / l2;

ฉันใช้ Maple เพื่อสร้างรหัส C ++ เพื่อหลีกเลี่ยงข้อผิดพลาด (และประหยัดเวลาด้วยพีชคณิตที่น่าเบื่อ) เนื่องจากโค้ดนี้ถูกเรียกใช้งานหลายพันครั้ง (ถ้าไม่ใช่ล้านครั้ง) ประสิทธิภาพจึงเป็นเรื่องที่น่ากังวล น่าเสียดายที่คณิตศาสตร์ทำให้ง่ายขึ้นเท่านั้น สมการที่ยาวเป็นสิ่งที่หลีกเลี่ยงไม่ได้

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

ฉันกำลังรวบรวมโดยใช้ g ++ กับ--enable-optimize=-O3.

อัปเดต:

ฉันรู้ว่ามีนิพจน์ซ้ำ ๆ มากมายฉันใช้สมมติฐานที่ว่าคอมไพเลอร์จะจัดการกับสิ่งเหล่านี้ การทดสอบของฉันแนะนำให้ทำ

l1, l2, l3, mu, a, K เป็นจำนวนจริงที่เป็นบวกทั้งหมด (ไม่ใช่ศูนย์)

ฉันได้แทนที่l1*l2*l3ด้วยตัวแปรเทียบเท่า: J. สิ่งนี้ช่วยปรับปรุงประสิทธิภาพ

เปลี่ยนpow(x, 0.1e1/0.3e1)กับcbrt(x)เป็นคำแนะนำที่ดี

สิ่งนี้จะทำงานบนซีพียูในอนาคตอันใกล้สิ่งนี้น่าจะทำงานได้ดีขึ้นบน GPU แต่ตอนนี้ตัวเลือกนั้นยังไม่มี


32
สิ่งแรกที่ต้องนึกถึง (เว้นแต่คอมไพเลอร์จะปรับให้เหมาะสมเอง) คือการแทนที่pow(l1 * l2 * l3, -0.1e1 / 0.3e1)ตัวแปรทั้งหมดด้วยตัวแปร ... คุณต้องเปรียบเทียบโค้ดของคุณเพื่อให้แน่ใจว่ามันทำงานเร็วหรือช้า
SingerOfTheFall

6
จัดรูปแบบโค้ดเพื่อให้อ่านง่ายขึ้น - อาจช่วยในการระบุความเป็นไปได้ในการปรับปรุง
Ed Heal

26
ทำไมดาวน์โหวตและโหวตทั้งหมดถึงปิด? สำหรับพวกคุณที่ไม่ชอบการเขียนโปรแกรมเชิงตัวเลขหรือวิทยาศาสตร์ลองดูคำถามอื่น ๆ นี่เป็นคำถามที่ดีที่เหมาะกับไซต์นี้ ไซต์ scicomp ยังคงเป็นเบต้า การโยกย้ายไม่มีตัวเลือกที่ดี ไซต์ตรวจสอบโค้ดไม่ได้รับสายตาที่น่ากลัว สิ่งที่ OP เกิดขึ้นค่อนข้างบ่อยในการคำนวณทางวิทยาศาสตร์: สร้างปัญหาในโปรแกรมคณิตศาสตร์สัญลักษณ์ขอให้โปรแกรมสร้างรหัสและอย่าแตะต้องผลลัพธ์เพราะโค้ดที่สร้างขึ้นนั้นยุ่งเหยิง
David Hammen

6
@DavidHammen เว็บไซต์ Code Review ไม่ได้รับสายตาที่น่าสนใจเพียงพอ - ดูเหมือนปัญหาไก่กับไข่และความคิดที่ไม่ช่วยให้ CR ได้รับสายตาเช่นนี้อีกต่อไป เช่นเดียวกันกับแนวคิดในการลดไซต์ scicomp beta เนื่องจากเป็นเบต้า - หากทุกคนคิดเช่นนั้นไซต์เดียวที่จะเติบโตคือ Stack Overflow
Mathieu Guindon

13
คำถามนี้มีการพูดคุยเกี่ยวกับ meta ที่นี่
NathanOliver

คำตอบ:


88

แก้ไขสรุป

  • คำตอบเดิมของฉันเพียงแค่ตั้งข้อสังเกตว่ารหัสมีการคำนวณจำลองจำนวนมากและอำนาจหลายอย่างเกี่ยวข้องกับปัจจัย 1/3 ยกตัวอย่างเช่นเป็นเช่นเดียวกับpow(x, 0.1e1/0.3e1)cbrt(x)
  • การแก้ไขครั้งที่สองของฉันผิดพลาดและครั้งที่สามของฉันคาดการณ์ถึงความผิดนี้ นี่คือสิ่งที่ทำให้ผู้คนกลัวที่จะเปลี่ยนผลลัพธ์ที่เหมือน oracle จากโปรแกรมคณิตศาสตร์สัญลักษณ์ที่ขึ้นต้นด้วยตัวอักษร 'M' ฉันได้แก้ไข (เช่นขีดฆ่า) การแก้ไขเหล่านั้นและผลักดันไปที่ด้านล่างของการแก้ไขปัจจุบันของคำตอบนี้ อย่างไรก็ตามฉันไม่ได้ลบออก ฉันเป็นมนุษย์ เป็นเรื่องง่ายที่เราจะทำพลาด
  • แก้ไขที่สี่ของฉันพัฒนาแสดงออกขนาดเล็กมากที่ถูกต้องหมายถึงการแสดงออกที่ซับซ้อนในคำถามถ้าพารามิเตอร์l1, l2และl3ตัวเลขจริงบวกและถ้าaเป็นที่ไม่ใช่ศูนย์จำนวนจริง (เรายังไม่ได้รับฟังจาก OP เกี่ยวกับลักษณะเฉพาะของค่าสัมประสิทธิ์เหล่านี้เนื่องจากลักษณะของปัญหานี่เป็นสมมติฐานที่สมเหตุสมผล)
  • การแก้ไขนี้พยายามที่จะตอบปัญหาทั่วไปเกี่ยวกับวิธีลดความซับซ้อนของนิพจน์เหล่านี้

สิ่งแรกก่อน

ฉันใช้ Maple เพื่อสร้างรหัส C ++ เพื่อหลีกเลี่ยงข้อผิดพลาด

Maple และ Mathematica บางครั้งก็พลาดสิ่งที่ชัดเจน ที่สำคัญบางครั้งผู้ใช้ Maple และ Mathematica ก็ทำผิดพลาด การแทนที่ "บ่อยครั้ง" หรืออาจจะเป็น "เกือบตลอดเวลา" แทน "บางครั้งอาจใกล้เคียงกับเครื่องหมายมากกว่า

คุณสามารถช่วย Maple ลดความซับซ้อนของนิพจน์นั้นได้โดยการบอกเกี่ยวกับพารามิเตอร์ที่เป็นปัญหา ในตัวอย่างที่อยู่ในมือผมสงสัยว่าl1, l2และl3ตัวเลขจริงบวกและที่aเป็นที่ไม่ใช่ศูนย์จำนวนจริง ถ้าเป็นอย่างนั้นบอกเลย โดยทั่วไปแล้วโปรแกรมคณิตศาสตร์สัญลักษณ์เหล่านี้ถือว่าปริมาณที่มีอยู่นั้นซับซ้อน การ จำกัด โดเมนช่วยให้โปรแกรมตั้งสมมติฐานที่ไม่ถูกต้องในจำนวนเชิงซ้อน


วิธีลดความซับซ้อนของสิ่งเหล่านี้จากโปรแกรมคณิตศาสตร์สัญลักษณ์ (แก้ไขนี้)

โดยทั่วไปโปรแกรมคณิตศาสตร์สัญลักษณ์ให้ความสามารถในการให้ข้อมูลเกี่ยวกับพารามิเตอร์ต่างๆ ใช้ความสามารถนั้นโดยเฉพาะอย่างยิ่งถ้าปัญหาของคุณเกี่ยวข้องกับการหารหรือการยกกำลัง ในตัวอย่างที่อยู่ในมือคุณอาจได้ช่วยลดความซับซ้อนของเมเปิลที่แสดงออกด้วยการบอกว่าl1, l2และl3ตัวเลขจริงบวกและที่aเป็นที่ไม่ใช่ศูนย์จำนวนจริง ถ้าเป็นอย่างนั้นบอกเลย โปรแกรมคณิตศาสตร์สัญลักษณ์เหล่านี้มักจะถือว่าปริมาณที่มีอยู่นั้นซับซ้อน การ จำกัด โดเมนช่วยให้สมมติฐานโปรแกรมทำเช่นx B x = (AB) x นี่คือในกรณีที่aและbเป็นจำนวนจริงบวกและถ้าxเป็นจริง มันไม่ถูกต้องในจำนวนเชิงซ้อน

ในที่สุดโปรแกรมคณิตศาสตร์สัญลักษณ์เหล่านั้นเป็นไปตามอัลกอริทึม ช่วยด้วย ลองเล่นด้วยการขยายรวบรวมและทำให้ง่ายขึ้นก่อนที่คุณจะสร้างโค้ด ในกรณีนี้คุณอาจจะมีการเก็บรวบรวมแง่ผู้ที่เกี่ยวข้องกับปัจจัยของและผู้ที่เกี่ยวข้องกับปัจจัยของmu Kการลดนิพจน์ให้เป็น "รูปแบบที่ง่ายที่สุด" ยังคงเป็นศิลปะอยู่เล็กน้อย

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


เกี่ยวกับคำถามเฉพาะ

คุณกำลังทำการบวกและลบจำนวนมากในการคำนวณนั้น คุณอาจมีปัญหาอย่างหนักหากคุณมีเงื่อนไขที่เกือบจะยกเลิกซึ่งกันและกัน คุณกำลังสิ้นเปลือง CPU เป็นจำนวนมากหากคุณมีคำศัพท์หนึ่งคำที่ครอบงำคำอื่น ๆ

ถัดไปคุณจะสิ้นเปลือง CPU จำนวนมากโดยทำการคำนวณซ้ำ ๆ เว้นแต่คุณจะเปิดใช้งาน-ffast-mathซึ่งทำให้คอมไพลเลอร์ทำลายกฎบางส่วนของจุดลอยตัวของ IEEE คอมไพเลอร์จะไม่ (ในความเป็นจริงต้องไม่) ทำให้นิพจน์นั้นง่ายขึ้นสำหรับคุณ แต่จะทำในสิ่งที่คุณบอกให้ทำแทน อย่างน้อยที่สุดคุณควรคำนวณl1 * l2 * l3ก่อนที่จะคำนวณความยุ่งเหยิงนั้น

ในที่สุดคุณก็โทรหาpowบ่อยมากซึ่งช้ามาก หมายเหตุว่าหลายสายที่มีรูปแบบ (L1 L2 * * * * * l3) (1/3) การโทรจำนวนมากเหล่านั้นpowสามารถทำได้ด้วยการโทรครั้งเดียวไปที่std::cbrt:

l123 = l1 * l2 * l3;
l123_pow_1_3 = std::cbrt(l123);
l123_pow_4_3 = l123 * l123_pow_1_3;

ด้วยสิ่งนี้,

  • X * pow(l1 * l2 * l3, 0.1e1 / 0.3e1)X * l123_pow_1_3จะกลายเป็น
  • X * pow(l1 * l2 * l3, -0.1e1 / 0.3e1)X / l123_pow_1_3จะกลายเป็น
  • X * pow(l1 * l2 * l3, 0.4e1 / 0.3e1)X * l123_pow_4_3จะกลายเป็น
  • X * pow(l1 * l2 * l3, -0.4e1 / 0.3e1)X / l123_pow_4_3จะกลายเป็น


เมเปิ้ลพลาดอย่างเห็นได้ชัด
ตัวอย่างเช่นมีวิธีที่ง่ายกว่ามากในการเขียน

(pow(l1 * l2 * l3, -0.1e1 / 0.3e1) - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1)

สมมติว่าl1, l2และl3เป็นจริงมากกว่าตัวเลขที่ซับซ้อนและรากที่สามจริง (มากกว่ารากซับซ้อนหลักการ) จะมีการสกัดดังกล่าวข้างต้นจะช่วยลด

2.0/(3.0 * pow(l1 * l2 * l3, 1.0/3.0))

หรือ

2.0/(3.0 * l123_pow_1_3)

การใช้cbrt_l123แทนl123_pow_1_3การแสดงออกที่น่ารังเกียจในคำถามจะลดลง

l123 = l1 * l2 * l3; 
cbrt_l123 = cbrt(l123);
T = 
  mu/(3.0*l123)*(  pow(l1/cbrt_l123,a)*(2.0*N1-N2-N3)
                 + pow(l2/cbrt_l123,a)*(2.0*N2-N3-N1)
                 + pow(l3/cbrt_l123,a)*(2.0*N3-N1-N2))
 +K*(l123-1.0)*(N1+N2+N3);

ตรวจสอบอีกครั้งเสมอ แต่จะทำให้ง่ายขึ้นเช่นกัน


นี่คือขั้นตอนบางส่วนของฉันในการมาถึงด้านบน:

// Step 0: Trim all whitespace.
T=(mu*(pow(l1*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a*(pow(l1*l2*l3,-0.1e1/0.3e1)-l1*l2*l3*pow(l1*l2*l3,-0.4e1/0.3e1)/0.3e1)*pow(l1*l2*l3,0.1e1/0.3e1)/l1-pow(l2*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l1/0.3e1-pow(l3*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l1/0.3e1)/a+K*(l1*l2*l3-0.1e1)*l2*l3)*N1/l2/l3+(mu*(-pow(l1*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l2/0.3e1+pow(l2*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a*(pow(l1*l2*l3,-0.1e1/0.3e1)-l1*l2*l3*pow(l1*l2*l3,-0.4e1/0.3e1)/0.3e1)*pow(l1*l2*l3,0.1e1/0.3e1)/l2-pow(l3*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l2/0.3e1)/a+K*(l1*l2*l3-0.1e1)*l1*l3)*N2/l1/l3+(mu*(-pow(l1*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l3/0.3e1-pow(l2*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a/l3/0.3e1+pow(l3*pow(l1*l2*l3,-0.1e1/0.3e1),a)*a*(pow(l1*l2*l3,-0.1e1/0.3e1)-l1*l2*l3*pow(l1*l2*l3,-0.4e1/0.3e1)/0.3e1)*pow(l1*l2*l3,0.1e1/0.3e1)/l3)/a+K*(l1*l2*l3-0.1e1)*l1*l2)*N3/l1/l2;

// Step 1:
//   l1*l2*l3 -> l123
//   0.1e1 -> 1.0
//   0.4e1 -> 4.0
//   0.3e1 -> 3
l123 = l1 * l2 * l3;
T=(mu*(pow(l1*pow(l123,-1.0/3),a)*a*(pow(l123,-1.0/3)-l123*pow(l123,-4.0/3)/3)*pow(l123,1.0/3)/l1-pow(l2*pow(l123,-1.0/3),a)*a/l1/3-pow(l3*pow(l123,-1.0/3),a)*a/l1/3)/a+K*(l123-1.0)*l2*l3)*N1/l2/l3+(mu*(-pow(l1*pow(l123,-1.0/3),a)*a/l2/3+pow(l2*pow(l123,-1.0/3),a)*a*(pow(l123,-1.0/3)-l123*pow(l123,-4.0/3)/3)*pow(l123,1.0/3)/l2-pow(l3*pow(l123,-1.0/3),a)*a/l2/3)/a+K*(l123-1.0)*l1*l3)*N2/l1/l3+(mu*(-pow(l1*pow(l123,-1.0/3),a)*a/l3/3-pow(l2*pow(l123,-1.0/3),a)*a/l3/3+pow(l3*pow(l123,-1.0/3),a)*a*(pow(l123,-1.0/3)-l123*pow(l123,-4.0/3)/3)*pow(l123,1.0/3)/l3)/a+K*(l123-1.0)*l1*l2)*N3/l1/l2;

// Step 2:
//   pow(l123,1.0/3) -> cbrt_l123
//   l123*pow(l123,-4.0/3) -> pow(l123,-1.0/3)
//   (pow(l123,-1.0/3)-pow(l123,-1.0/3)/3) -> 2.0/(3.0*cbrt_l123)
//   *pow(l123,-1.0/3) -> /cbrt_l123
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T=(mu*(pow(l1/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l1-pow(l2/cbrt_l123,a)*a/l1/3-pow(l3/cbrt_l123,a)*a/l1/3)/a+K*(l123-1.0)*l2*l3)*N1/l2/l3+(mu*(-pow(l1/cbrt_l123,a)*a/l2/3+pow(l2/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l2-pow(l3/cbrt_l123,a)*a/l2/3)/a+K*(l123-1.0)*l1*l3)*N2/l1/l3+(mu*(-pow(l1/cbrt_l123,a)*a/l3/3-pow(l2/cbrt_l123,a)*a/l3/3+pow(l3/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l3)/a+K*(l123-1.0)*l1*l2)*N3/l1/l2;

// Step 3:
//   Whitespace is nice.
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  (mu*( pow(l1/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l1
       -pow(l2/cbrt_l123,a)*a/l1/3
       -pow(l3/cbrt_l123,a)*a/l1/3)/a
   +K*(l123-1.0)*l2*l3)*N1/l2/l3
 +(mu*(-pow(l1/cbrt_l123,a)*a/l2/3
       +pow(l2/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l2
       -pow(l3/cbrt_l123,a)*a/l2/3)/a
   +K*(l123-1.0)*l1*l3)*N2/l1/l3
 +(mu*(-pow(l1/cbrt_l123,a)*a/l3/3
       -pow(l2/cbrt_l123,a)*a/l3/3
       +pow(l3/cbrt_l123,a)*a*2.0/(3.0*cbrt_l123)*cbrt_l123/l3)/a
   +K*(l123-1.0)*l1*l2)*N3/l1/l2;

// Step 4:
//   Eliminate the 'a' in (term1*a + term2*a + term3*a)/a
//   Expand (mu_term + K_term)*something to mu_term*something + K_term*something
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  (mu*( pow(l1/cbrt_l123,a)*2.0/(3.0*cbrt_l123)*cbrt_l123/l1
       -pow(l2/cbrt_l123,a)/l1/3
       -pow(l3/cbrt_l123,a)/l1/3))*N1/l2/l3
 +K*(l123-1.0)*l2*l3*N1/l2/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l2/3
       +pow(l2/cbrt_l123,a)*2.0/(3.0*cbrt_l123)*cbrt_l123/l2
       -pow(l3/cbrt_l123,a)/l2/3))*N2/l1/l3
 +K*(l123-1.0)*l1*l3*N2/l1/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l3/3
       -pow(l2/cbrt_l123,a)/l3/3
       +pow(l3/cbrt_l123,a)*2.0/(3.0*cbrt_l123)*cbrt_l123/l3))*N3/l1/l2
 +K*(l123-1.0)*l1*l2*N3/l1/l2;

// Step 5:
//   Rearrange
//   Reduce l2*l3*N1/l2/l3 to N1 (and similar)
//   Reduce 2.0/(3.0*cbrt_l123)*cbrt_l123/l1 to 2.0/3.0/l1 (and similar)
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  (mu*( pow(l1/cbrt_l123,a)*2.0/3.0/l1
       -pow(l2/cbrt_l123,a)/l1/3
       -pow(l3/cbrt_l123,a)/l1/3))*N1/l2/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l2/3
       +pow(l2/cbrt_l123,a)*2.0/3.0/l2
       -pow(l3/cbrt_l123,a)/l2/3))*N2/l1/l3
 +(mu*(-pow(l1/cbrt_l123,a)/l3/3
       -pow(l2/cbrt_l123,a)/l3/3
       +pow(l3/cbrt_l123,a)*2.0/3.0/l3))*N3/l1/l2
 +K*(l123-1.0)*N1
 +K*(l123-1.0)*N2
 +K*(l123-1.0)*N3;

// Step 6:
//   Factor out mu and K*(l123-1.0)
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  mu*(  ( pow(l1/cbrt_l123,a)*2.0/3.0/l1
         -pow(l2/cbrt_l123,a)/l1/3
         -pow(l3/cbrt_l123,a)/l1/3)*N1/l2/l3
      + (-pow(l1/cbrt_l123,a)/l2/3
         +pow(l2/cbrt_l123,a)*2.0/3.0/l2
         -pow(l3/cbrt_l123,a)/l2/3)*N2/l1/l3
      + (-pow(l1/cbrt_l123,a)/l3/3
         -pow(l2/cbrt_l123,a)/l3/3
         +pow(l3/cbrt_l123,a)*2.0/3.0/l3)*N3/l1/l2)
 +K*(l123-1.0)*(N1+N2+N3);

// Step 7:
//   Expand
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  mu*( pow(l1/cbrt_l123,a)*2.0/3.0/l1*N1/l2/l3
      -pow(l2/cbrt_l123,a)/l1/3*N1/l2/l3
      -pow(l3/cbrt_l123,a)/l1/3*N1/l2/l3
      -pow(l1/cbrt_l123,a)/l2/3*N2/l1/l3
      +pow(l2/cbrt_l123,a)*2.0/3.0/l2*N2/l1/l3
      -pow(l3/cbrt_l123,a)/l2/3*N2/l1/l3
      -pow(l1/cbrt_l123,a)/l3/3*N3/l1/l2
      -pow(l2/cbrt_l123,a)/l3/3*N3/l1/l2
      +pow(l3/cbrt_l123,a)*2.0/3.0/l3*N3/l1/l2)
 +K*(l123-1.0)*(N1+N2+N3);

// Step 8:
//   Simplify.
l123 = l1 * l2 * l3;
cbrt_l123 = cbrt(l123);
T =
  mu/(3.0*l123)*(  pow(l1/cbrt_l123,a)*(2.0*N1-N2-N3)
                 + pow(l2/cbrt_l123,a)*(2.0*N2-N3-N1)
                 + pow(l3/cbrt_l123,a)*(2.0*N3-N1-N2))
 +K*(l123-1.0)*(N1+N2+N3);


ตอบผิดตั้งใจเก็บไว้เพื่อความนอบน้อม

โปรดทราบว่านี่เป็นเรื่องยาก มันผิด.

อัปเดต

เมเปิ้ลพลาดอย่างเห็นได้ชัด ตัวอย่างเช่นมีวิธีที่ง่ายกว่ามากในการเขียน

(ผง (l1 * l2 * l3, -0.1e1 / 0.3e1) - l1 * l2 * l3 * ธาร (l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1)

สมมติว่าl1, l2และl3เป็นจริงมากกว่าตัวเลขที่ซับซ้อนและรากที่สามจริง (มากกว่ารากซับซ้อนหลักการ) จะมีการสกัดดังกล่าวข้างต้นจะช่วยลดให้เป็นศูนย์ การคำนวณศูนย์นี้จะทำซ้ำหลาย ๆ ครั้ง

การปรับปรุงครั้งที่สอง

ถ้าฉันทำคณิตศาสตร์ถูกต้อง ( ไม่มีการรับประกันว่าฉันทำคณิตศาสตร์ถูกต้อง) การแสดงออกที่น่ารังเกียจในคำถามจะลดลงเป็น

l123 = l1 * l2 * l3; 
cbrt_l123_inv = 1.0 / cbrt(l123);
nasty_expression =
    K * (l123 - 1.0) * (N1 + N2 + N3) 
    - (  pow(l1 * cbrt_l123_inv, a) * (N2 + N3) 
       + pow(l2 * cbrt_l123_inv, a) * (N1 + N3) 
       + pow(l3 * cbrt_l123_inv, a) * (N1 + N2)) * mu / (3.0*l123);

ดังกล่าวข้างต้นสันนิษฐานว่าl1, l2และl3ตัวเลขจริงบวก


2
การกำจัด CSE ควรใช้งานได้โดยไม่ขึ้นอยู่กับความหมายที่ผ่อนคลาย (และ OP ระบุไว้ในความคิดเห็น) แม้ว่าจะมีความสำคัญ (วัดได้) ก็ควรได้รับการตรวจสอบ (สร้างการประกอบ) คะแนนของคุณเกี่ยวกับการครองคำศัพท์การปรับสูตรให้เรียบง่ายที่ไม่ได้รับฟังก์ชั่นพิเศษที่ดีกว่าและอันตรายจากการยกเลิกนั้นดีมาก
Deduplicator

3
@Deduplicator - ไม่ได้มีจุดลอยตัว เว้นแต่จะมีการเปิดใช้งานการเพิ่มประสิทธิภาพทางคณิตศาสตร์ที่ไม่ปลอดภัย (เช่นโดยการระบุ-ffast-mathด้วย gcc หรือ clang) คอมไพเลอร์ไม่สามารถพึ่งพาpow(x,-1.0/3.0)การเท่ากับx*pow(x,-4.0/3.0). อย่างหลังอาจล้นในขณะที่ครั้งแรกอาจไม่ เพื่อให้เป็นไปตามมาตรฐานจุดลอยตัวคอมไพเลอร์จะต้องไม่ปรับการคำนวณนั้นให้เป็นศูนย์
David Hammen

สิ่งเหล่านี้ค่อนข้างทะเยอทะยานมากกว่าสิ่งที่ฉันหมายถึง
Deduplicator

1
@Deduplicator: ตามที่ฉันแสดงความคิดเห็นในคำตอบอื่น : คุณต้องการ-fno-math-errnog ++ ถึง CSE การpowโทรที่เหมือนกัน (เว้นแต่อาจจะพิสูจน์ได้ว่าธารไม่จำเป็นต้องตั้งค่า errno?)
Peter Cordes

1
@Lefti - อย่าใช้คำตอบของ Walter ให้มากนัก เป็นข้อตกลงที่ดีเร็วขึ้น มีปัญหาที่อาจเกิดขึ้นกับคำตอบทั้งหมดนี้ซึ่งเป็นการยกเลิกเชิงตัวเลข สมมติว่าคุณN1, N2และN3เป็นที่ไม่ใช่เชิงลบซึ่งเป็นหนึ่งใน2*N_i-(N_j+N_k)จะเป็นค่าลบหนึ่งจะเป็นบวกและอื่น ๆ ที่จะเป็นหนึ่งในระหว่าง ซึ่งอาจส่งผลให้เกิดปัญหาการยกเลิกตัวเลขได้อย่างง่ายดาย
David Hammen

32

สิ่งแรกที่ควรทราบก็powคือราคาแพงมากดังนั้นคุณควรกำจัดสิ่งนี้ให้ได้มากที่สุด การสแกนผ่านการแสดงออกที่ผมเห็นหลายซ้ำของและpow(l1 * l2 * l3, -0.1e1 / 0.3e1) pow(l1 * l2 * l3, -0.4e1 / 0.3e1)ดังนั้นฉันคาดหวังว่าจะได้รับประโยชน์มหาศาลจากการประมวลผลล่วงหน้าเหล่านี้:

 const double c1 = pow(l1 * l2 * l3, -0.1e1 / 0.3e1);
const double c2 = boost::math::pow<4>(c1);

ที่ฉันใช้เพิ่มธารฟังก์ชั่น

นอกจากนี้คุณยังpowมีเลขชี้กำลังaอีกด้วย ถ้าaเป็น Integer และเป็นที่รู้จักในเวลาคอมไพเลอร์คุณยังสามารถแทนที่ด้วยboost::math::pow<a>(...)เพื่อเพิ่มประสิทธิภาพ ฉันขอแนะนำให้แทนที่คำศัพท์เช่นa / l1 / 0.3e1ด้วยa / (l1 * 0.3e1)เนื่องจากการคูณเร็วกว่าแล้วหาร

สุดท้ายหากคุณใช้ g ++ คุณสามารถใช้-ffast-mathแฟล็กที่ช่วยให้เครื่องมือเพิ่มประสิทธิภาพมีความก้าวร้าวมากขึ้นในการเปลี่ยนสมการ อ่านเกี่ยวกับสิ่งที่ธงนี้ทำจริงเนื่องจากมีผลข้างเคียง


5
ในรหัสของเราการใช้-ffast-mathโอกาสในการขายทำให้รหัสไม่เสถียรหรือให้คำตอบที่ไม่ถูกต้อง เรามีปัญหาคล้ายกันกับคอมไพเลอร์ของ Intel และต้องใช้-fp-model preciseตัวเลือกนี้มิฉะนั้นโค้ดจะระเบิดหรือให้คำตอบที่ผิด ดังนั้น-ffast-mathสามารถเร่งความเร็วได้ แต่ฉันขอแนะนำให้ดำเนินการอย่างระมัดระวังกับตัวเลือกนั้นนอกเหนือจากผลข้างเคียงที่ระบุไว้ในคำถามที่เชื่อมโยงของคุณ
tpg2114

2
@ tpg2114: จากการทดสอบของฉันคุณต้องการเพียงแค่-fno-math-errno g ++ เท่านั้นที่จะสามารถยกสายที่เหมือนกันpowออกจากลูปได้ นั่นเป็นส่วนที่ "อันตราย" น้อยที่สุดของ -ffast-math สำหรับโค้ดส่วนใหญ่
Peter Cordes

1
@PeterCordes นั่นคือผลลัพธ์ที่น่าสนใจ! นอกจากนี้เรายังมีปัญหาเกี่ยวกับpow การทำงานช้ามากและลงเอยด้วยการใช้dlsymแฮ็คที่กล่าวถึงในความคิดเห็นเพื่อเพิ่มประสิทธิภาพอย่างมากเมื่อเราสามารถทำได้จริงด้วยความแม่นยำน้อยลงเล็กน้อย
tpg2114

GCC จะไม่เข้าใจว่า pow เป็นฟังก์ชันที่บริสุทธิ์หรือไม่? นั่นอาจเป็นความรู้ในตัว
usr

6
@usr: นั่นเป็นเพียงประเด็นที่ฉันคิด powคือไม่ได้ฟังก์ชั่นที่บริสุทธิ์ตามมาตรฐานเพราะมันควรจะตั้งค่าerrnoในบางสถานการณ์ การตั้งค่าแฟล็กเช่น-fno-math-errnoทำให้ไม่ได้ตั้งค่าerrno(ซึ่งเป็นการละเมิดมาตรฐาน) แต่มันเป็นฟังก์ชันที่บริสุทธิ์และสามารถปรับให้เหมาะสมได้เช่นนี้
Nate Eldredge

20

ว้าวช่างเป็นการแสดงออก การสร้างนิพจน์ด้วย Maple เป็นทางเลือกที่ไม่เหมาะสมที่นี่ ผลลัพธ์คืออ่านไม่ออก

  1. เลือกชื่อตัวแปรที่พูด (ไม่ใช่ l1, l2, l3 แต่เช่นความสูงความกว้างความลึกถ้านั่นคือความหมาย) จากนั้นคุณจะเข้าใจรหัสของคุณเองได้ง่ายขึ้น
  2. คำนวณ subterms ที่คุณใช้หลายครั้งล่วงหน้าและเก็บผลลัพธ์ไว้ในตัวแปรพร้อมชื่อที่พูด
  3. คุณพูดถึงว่านิพจน์ได้รับการประเมินหลายครั้ง ฉันเดาว่ามีเพียงไม่กี่พารามิเตอร์เท่านั้นที่แตกต่างกันไปในวงในสุด คำนวณค่าย่อยที่ไม่แปรเปลี่ยนทั้งหมดก่อนลูปนั้น ทำซ้ำสำหรับวงในที่สองไปเรื่อย ๆ จนกว่าค่าคงที่ทั้งหมดจะอยู่นอกลูป

ในทางทฤษฎีคอมไพลเลอร์ควรจะทำทุกอย่างให้คุณได้ แต่บางครั้งก็ทำไม่ได้เช่นเมื่อการซ้อนแบบวนซ้ำกระจายไปในหลายฟังก์ชันในหน่วยคอมไพเลอร์ที่แตกต่างกัน อย่างไรก็ตามนั่นจะทำให้คุณสามารถอ่านเข้าใจและบำรุงรักษาโค้ดได้ดีขึ้นมาก


8
"คอมไพเลอร์ควรทำ แต่บางครั้งก็ไม่ทำ" เป็นกุญแจสำคัญที่นี่ นอกจากอ่านง่ายแน่นอน
Javier

3
ถ้าคอมไพลเลอร์ไม่จำเป็นต้องทำอะไรบางอย่างแสดงว่ามันผิดเกือบตลอดเวลา
edmz

4
เลือกชื่อตัวแปรที่พูดอีกครั้ง- หลายครั้งที่กฎที่ดีใช้ไม่ได้เมื่อคุณทำคณิตศาสตร์ เมื่อดูโค้ดที่ควรใช้อัลกอริทึมในวารสารทางวิทยาศาสตร์ฉันอยากเห็นว่าสัญลักษณ์ในรหัสตรงกับที่ใช้ในวารสาร โดยทั่วไปหมายถึงชื่อที่สั้นมากอาจมีตัวห้อย
David Hammen

8
"ผลลัพธ์คืออ่านไม่ออก" - ทำไมถึงเป็นปัญหา? คุณคงไม่สนใจว่าเอาต์พุตภาษาระดับสูงจาก lexer- หรือ parser-generator นั้น "อ่านไม่ได้" (โดยมนุษย์) สิ่งที่สำคัญที่นี่คืออินพุตของเครื่องสร้างโค้ด (Maple) สามารถอ่านและตรวจสอบได้ สิ่งที่ไม่ควรทำคือแก้ไขโค้ดที่สร้างขึ้นด้วยมือหากคุณต้องการความมั่นใจว่าไม่มีข้อผิดพลาด
alephzero

3
@DavidHammen: ในกรณีนั้นตัวอักษรตัวเดียวคือ "ชื่อที่พูด" เช่นเมื่อทำรูปทรงเรขาคณิตในคาร์ทีเซียน 2 มิติระบบพิกัดxและyมีไม่ความหมายตัวแปรตัวเดียวที่พวกเขามีทั้งคำที่มีความหมายได้อย่างแม่นยำและความหมายที่ดีและเข้าใจกันอย่างแพร่หลาย
Jörg W Mittag

17

คำตอบของ David Hammenนั้นดี แต่ก็ยังห่างไกลจากความเหมาะสม มาดูสำนวนสุดท้ายกันดีกว่า (ตอนที่เขียน)

auto l123 = l1 * l2 * l3;
auto cbrt_l123 = cbrt(l123);
T = mu/(3.0*l123)*(  pow(l1/cbrt_l123,a)*(2.0*N1-N2-N3)
                   + pow(l2/cbrt_l123,a)*(2.0*N2-N3-N1)
                   + pow(l3/cbrt_l123,a)*(2.0*N3-N1-N2))
  + K*(l123-1.0)*(N1+N2+N3);

ซึ่งสามารถปรับให้เหมาะสมเพิ่มเติมได้ โดยเฉพาะอย่างยิ่งเราสามารถหลีกเลี่ยงการเรียกcbrt()และหนึ่งในการเรียกร้องpow()หากใช้ประโยชน์จากความเหมือนทางคณิตศาสตร์บางอย่าง ลองทำอีกครั้งทีละขั้นตอน

// step 1 eliminate cbrt() by taking the exponent into pow()
auto l123 = l1 * l2 * l3;
auto athird = 0.33333333333333333 * a; // avoid division
T = mu/(3.0*l123)*(  (N1+N1-N2-N3)*pow(l1*l1/(l2*l3),athird)
                   + (N2+N2-N3-N1)*pow(l2*l2/(l1*l3),athird)
                   + (N3+N3-N1-N2)*pow(l3*l3/(l1*l2),athird))
  + K*(l123-1.0)*(N1+N2+N3);

โปรดทราบว่าฉันได้ปรับ2.0*N1ให้เหมาะสมแล้วด้วยN1+N1เช่นกันถัดไปเราสามารถทำได้ด้วยการโทรเพียงสองpow()ครั้ง

// step 2  eliminate one call to pow
auto l123 = l1 * l2 * l3;
auto athird = 0.33333333333333333 * a;
auto pow_l1l2_athird = pow(l1/l2,athird);
auto pow_l1l3_athird = pow(l1/l3,athird);
auto pow_l2l3_athird = pow_l1l3_athird/pow_l1l2_athird;
T = mu/(3.0*l123)*(  (N1+N1-N2-N3)* pow_l1l2_athird*pow_l1l3_athird
                   + (N2+N2-N3-N1)* pow_l2l3_athird/pow_l1l2_athird
                   + (N3+N3-N1-N2)/(pow_l1l3_athird*pow_l2l3_athird))
  + K*(l123-1.0)*(N1+N2+N3);

เนื่องจากการโทรไปpow()เป็นการดำเนินการที่มีค่าใช้จ่ายสูงที่สุดที่นี่จึงควรลดให้มากที่สุดเท่าที่จะเป็นไปได้ (การดำเนินการที่มีค่าใช้จ่ายถัดไปคือการโทรไปcbrt()ซึ่งเราตัดทิ้งไป)

หากมีโอกาสaเป็นจำนวนเต็มการเรียกที่powสามารถปรับให้เหมาะกับการเรียกไปยังcbrt(บวกพาวเวอร์จำนวนเต็ม) หรือถ้าathirdเป็นจำนวนเต็มครึ่งหนึ่งเราสามารถใช้sqrt(บวกพาวเวอร์จำนวนเต็ม) นอกจากนี้หากมีโอกาสl1==l2หรือการl1==l3เรียกl2==l3หนึ่งครั้งหรือทั้งสองครั้งpowสามารถตัดออกได้ ดังนั้นจึงควรพิจารณาสิ่งเหล่านี้เป็นกรณีพิเศษหากโอกาสดังกล่าวมีอยู่จริง


@gnat ฉันขอขอบคุณการแก้ไขของคุณ (ฉันคิดว่าจะทำด้วยตัวเอง) แต่จะพบว่ามันยุติธรรมกว่านี้ถ้าคำตอบของ David จะเชื่อมโยงกับสิ่งนี้ด้วย ทำไมคุณไม่แก้ไขคำตอบของดาวิดในทำนองเดียวกัน?
Walter

1
ฉันแก้ไขเพราะฉันเห็นคุณพูดถึงมันอย่างชัดเจน ฉันอ่านคำตอบของเดวิดอีกครั้งและไม่พบข้อมูลอ้างอิงถึงคำตอบของคุณที่นั่น ฉันพยายามหลีกเลี่ยงการแก้ไขในกรณีที่ไม่ชัดเจน 100% ว่าสิ่งที่ฉันเพิ่มตรงกับความตั้งใจของผู้เขียน
gnat

1
@ วอลเตอร์ - คำตอบของฉันตอนนี้เชื่อมโยงไปยังคุณ
David Hammen

1
ไม่ใช่ฉันอย่างแน่นอน ฉันเพิ่มคะแนนคำตอบของคุณเมื่อสองสามวันก่อน ฉันยังได้รับการโหวตลงคะแนนแบบสุ่มสำหรับคำตอบของฉัน สิ่งที่เกิดขึ้นในบางครั้ง
David Hammen

1
คุณและฉันได้รับการโหวตลดลงเล็กน้อย ดูการโหวตทั้งหมดของคำถาม! ณ ตอนนี้คำถามได้รับการโหวตลดลง 16 ครั้ง นอกจากนี้ยังได้รับ 80 upvotes ซึ่งมากกว่าการหักล้างผู้โหวตทั้งหมด
David Hammen

12
  1. กี่ "กี่"?
  2. ใช้เวลานานแค่ไหน?
  3. ทำทุกพารามิเตอร์เปลี่ยนระหว่างการคำนวณของสูตรนี้หรือไม่? หรือคุณสามารถแคชค่าที่คำนวณไว้ล่วงหน้าได้หรือไม่?
  4. ฉันได้พยายามทำให้สูตรนั้นง่ายขึ้นด้วยตนเองอยากทราบว่ามันช่วยอะไรได้หรือไม่?

    C1 = -0.1e1 / 0.3e1;
    C2 =  0.1e1 / 0.3e1;
    C3 = -0.4e1 / 0.3e1;
    
    X0 = l1 * l2 * l3;
    X1 = pow(X0, C1);
    X2 = pow(X0, C2);
    X3 = pow(X0, C3);
    X4 = pow(l1 * X1, a);
    X5 = pow(l2 * X1, a);
    X6 = pow(l3 * X1, a);
    X7 = a / 0.3e1;
    X8 = X3 / 0.3e1;
    X9 = mu / a;
    XA = X0 - 0.1e1;
    XB = K * XA;
    XC = X1 - X0 * X8;
    XD = a * XC * X2;
    
    XE = X4 * X7;
    XF = X5 * X7;
    XG = X6 * X7;
    
    T = (X9 * ( X4 * XD - XF - XG) / l1 + XB * l2 * l3) * N1 / l2 / l3 
      + (X9 * (-XE + X5 * XD - XG) / l2 + XB * l1 * l3) * N2 / l1 / l3 
      + (X9 * (-XE - XF + X6 * XD) / l3 + XB * l1 * l2) * N3 / l1 / l2;
    

[ADDED] ฉันได้หาข้อมูลเพิ่มเติมเกี่ยวกับสูตรสามบรรทัดสุดท้ายและได้ลงลึกถึงความงดงามนี้:

T = X9 / X0 * (
      (X4 * XD - XF - XG) * N1 + 
      (X5 * XD - XE - XG) * N2 + 
      (X5 * XD - XE - XF) * N3)
  + XB * (N1 + N2 + N3)

ให้ฉันแสดงผลงานของฉันทีละขั้นตอน:

T = (X9 * (X4 * XD - XF - XG) / l1 + XB * l2 * l3) * N1 / l2 / l3 
  + (X9 * (X5 * XD - XE - XG) / l2 + XB * l1 * l3) * N2 / l1 / l3 
  + (X9 * (X5 * XD - XE - XF) / l3 + XB * l1 * l2) * N3 / l1 / l2;


T = (X9 * (X4 * XD - XF - XG) / l1 + XB * l2 * l3) * N1 / (l2 * l3) 
  + (X9 * (X5 * XD - XE - XG) / l2 + XB * l1 * l3) * N2 / (l1 * l3) 
  + (X9 * (X5 * XD - XE - XF) / l3 + XB * l1 * l2) * N3 / (l1 * l2);

T = (X9 * (X4 * XD - XF - XG) + XB * l1 * l2 * l3) * N1 / (l1 * l2 * l3) 
  + (X9 * (X5 * XD - XE - XG) + XB * l1 * l2 * l3) * N2 / (l1 * l2 * l3) 
  + (X9 * (X5 * XD - XE - XF) + XB * l1 * l2 * l3) * N3 / (l1 * l2 * l3);

T = (X9 * (X4 * XD - XF - XG) + XB * X0) * N1 / X0 
  + (X9 * (X5 * XD - XE - XG) + XB * X0) * N2 / X0 
  + (X9 * (X5 * XD - XE - XF) + XB * X0) * N3 / X0;

T = X9 * (X4 * XD - XF - XG) * N1 / X0 + XB * N1 
  + X9 * (X5 * XD - XE - XG) * N2 / X0 + XB * N2
  + X9 * (X5 * XD - XE - XF) * N3 / X0 + XB * N3;


T = X9 * (X4 * XD - XF - XG) * N1 / X0 
  + X9 * (X5 * XD - XE - XG) * N2 / X0
  + X9 * (X5 * XD - XE - XF) * N3 / X0
  + XB * (N1 + N2 + N3)

2
เห็นได้ชัดใช่มั้ย? :) FORTRAN, IIRC ได้รับการออกแบบมาเพื่อการคำนวณสูตรที่มีประสิทธิภาพ ("FOR" สำหรับสูตร)
Vlad Feinstein

รหัส F77 ส่วนใหญ่ที่ฉันเคยเห็นมีลักษณะเช่นนั้น (เช่น BLAS & NR) ดีใจมากที่ Fortran 90-> 2008 มีอยู่ :)
Kyle Kanos

ใช่. หากคุณกำลังแปลสูตรจะมีอะไรดีไปกว่า FORmulaTRANslation?
Brian Drummond

1
'การเพิ่มประสิทธิภาพ' ของคุณโจมตีผิดที่ บิตที่มีราคาแพงคือการเรียกร้องstd::pow()ซึ่งคุณยังมีมากกว่าที่จำเป็นถึง 6, 3 เท่า กล่าวอีกนัยหนึ่งรหัสของคุณช้ากว่าที่เป็นไปได้ 3 เท่า
Walter

7

นี่อาจจะสั้นไปหน่อย แต่จริงๆแล้วฉันพบว่ามีการเร่งความเร็วที่ดีสำหรับพหุนาม (การแก้ไขฟังก์ชันพลังงาน) โดยใช้ Horner Form ซึ่งโดยทั่วไปจะเขียนใหม่ax^3 + bx^2 + cx + dเป็นd + x(c + x(b + x(a))). วิธีนี้จะหลีกเลี่ยงการโทรซ้ำ ๆ จำนวนมากpow()และหยุดไม่ให้คุณทำเรื่องโง่ ๆ เช่นการโทรแยกกันpow(x,6)และpow(x,7)แทนที่จะทำเพียงx*pow(x,6)อย่างเดียว

สิ่งนี้ไม่สามารถใช้ได้โดยตรงกับปัญหาปัจจุบันของคุณ แต่ถ้าคุณมีพหุนามลำดับสูงที่มีอำนาจจำนวนเต็มก็สามารถช่วยได้ คุณอาจต้องระวังความเสถียรของตัวเลขและปัญหาการล้นเนื่องจากลำดับของการดำเนินการเป็นสิ่งสำคัญสำหรับสิ่งนั้น (แม้ว่าโดยทั่วไปแล้วฉันคิดว่า Horner Form ช่วยในเรื่องนี้เนื่องจากx^20และxโดยปกติแล้วจะมีลำดับความสำคัญหลายขนาด)

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


แบบฟอร์ม Horner ค่อนข้างเป็นมาตรฐานสำหรับการเข้ารหัสพหุนามและสิ่งนี้ไม่มีความเกี่ยวข้องกับคำถามเลย
Walter

1
นี่อาจจะเป็นจริงจากตัวอย่างของเขา แต่คุณจะสังเกตได้ว่าเขาพูดว่า "สมการประเภทนี้" ฉันคิดว่าคำตอบจะมีประโยชน์ถ้าผู้โพสต์มีพหุนามในระบบของเขา ฉันสังเกตเห็นเป็นพิเศษว่าตัวสร้างรหัสสำหรับโปรแกรม CAS เช่น Mathematica และ Maple มักจะไม่ให้แบบฟอร์ม Horner แก่คุณเว้นแต่คุณจะขอเป็นพิเศษ ค่าเริ่มต้นเป็นวิธีที่คุณมักจะเขียนพหุนามเป็นมนุษย์
neocpp

3

ดูเหมือนว่าคุณจะมีการดำเนินการซ้ำ ๆ เกิดขึ้นมากมาย

pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
pow(l1 * l2 * l3, -0.4e1 / 0.3e1)

คุณสามารถคำนวณล่วงหน้าเพื่อไม่ให้คุณเรียกใช้powฟังก์ชันซ้ำ ๆซึ่งอาจมีราคาแพง

คุณยังสามารถคำนวณล่วงหน้าได้

l1 * l2 * l3

ในขณะที่คุณใช้คำนั้นซ้ำ ๆ


6
ฉันพนันได้เลยว่าเครื่องมือเพิ่มประสิทธิภาพทำสิ่งนี้ให้คุณแล้ว ... แม้ว่าอย่างน้อยก็ทำให้โค้ดอ่านง่ายขึ้น
Karoly Horvath

ฉันทำสิ่งนี้ แต่มันไม่ได้เร่งอะไรเลย ฉันคิดว่านี่เป็นเพราะการเพิ่มประสิทธิภาพคอมไพลเลอร์ได้รับการดูแลอยู่แล้ว

การจัดเก็บ l1 * l2 * l3 ทำให้สิ่งต่างๆเร็วขึ้น แต่ไม่แน่ใจว่าทำไมด้วยการเพิ่มประสิทธิภาพคอมไพเลอร์

เนื่องจากบางครั้งคอมไพเลอร์ไม่สามารถทำการเพิ่มประสิทธิภาพบางอย่างได้หรือพบว่ามันขัดแย้งกับตัวเลือกอื่น ๆ
Javier

1
ในความเป็นจริงคอมไพลเลอร์จะต้องไม่ทำการเพิ่มประสิทธิภาพเหล่านั้นเว้นแต่-ffast-mathจะเปิดใช้งานและตามที่ระบุไว้ในความคิดเห็นของ @ tpg2114 การเพิ่มประสิทธิภาพนั้นสามารถสร้างผลลัพธ์ที่ไม่เสถียรอย่างยิ่ง
David Hammen

0

หากคุณมีการ์ดแสดงผล Nvidia CUDA คุณสามารถพิจารณายกเลิกการโหลดการคำนวณไปยังการ์ดแสดงผลซึ่งเหมาะสำหรับการคำนวณที่มีความซับซ้อนมากกว่า

https://developer.nvidia.com/how-to-cuda-c-cpp

ถ้าไม่คุณอาจต้องพิจารณาหลายเธรดสำหรับการคำนวณ


10
คำตอบนี้ตั้งฉากกับคำถามที่อยู่ในมือ แม้ว่า GPU จะมีโปรเซสเซอร์จำนวนมาก แต่ก็ค่อนข้างช้าเมื่อเทียบกับ FPU ที่ฝังมากับ CPU การคำนวณอนุกรมเดียวกับ GPU ถือเป็นการสูญเสียครั้งใหญ่ ซีพียูต้องเติมท่อส่งไปยัง GPU รอให้ GPU ที่ช้าทำงานเดียวนั้นจากนั้นจึงยกเลิกการโหลดผลลัพธ์ ในขณะที่ GPU นั้นยอดเยี่ยมอย่างยิ่งเมื่อปัญหาอยู่ในมือขนานกันอย่างหนาแน่น แต่ก็เป็นเรื่องที่เลวร้ายอย่างยิ่งเมื่อต้องทำงานแบบอนุกรม
David Hammen

1
ในคำถามเดิม: "เนื่องจากโค้ดนี้ถูกเรียกใช้หลายครั้งประสิทธิภาพจึงเป็นปัญหา" นั่นเป็นมากกว่า "จำนวนมาก" op สามารถส่งการคำนวณในลักษณะเธรด
user3791372

0

ไม่ว่าจะด้วยเหตุผลใดคุณสามารถคำนวณเชิงสัญลักษณ์ได้ หากมีการทำงานของเวกเตอร์คุณอาจต้องการตรวจสอบโดยใช้ blas หรือ lapack ซึ่งในบางกรณีสามารถเรียกใช้การดำเนินการควบคู่กันได้

เป็นไปได้ (เสี่ยงต่อการนอกประเด็น?) ที่คุณอาจใช้ python กับ numpy และ / หรือ scipy เท่าที่เป็นไปได้การคำนวณของคุณอาจอ่านได้ง่ายขึ้น


0

ตามที่คุณถามอย่างชัดเจนเกี่ยวกับการเพิ่มประสิทธิภาพระดับสูงอาจคุ้มค่าที่จะลองใช้คอมไพเลอร์ C ++ อื่น ปัจจุบันคอมไพเลอร์เป็นสัตว์ที่มีการเพิ่มประสิทธิภาพที่ซับซ้อนมากและผู้จำหน่าย CPU อาจใช้การเพิ่มประสิทธิภาพที่มีประสิทธิภาพและเฉพาะเจาะจง แต่โปรดทราบว่าบางส่วนไม่ฟรี (แต่อาจมีโปรแกรมการศึกษาฟรี)

  • คอลเลกชันคอมไพเลอร์ GNU นั้นฟรียืดหยุ่นและมีให้ในหลายสถาปัตยกรรม
  • คอมไพเลอร์ของ Intel เร็วมากราคาแพงมากและอาจให้ผลลัพธ์ที่ดีสำหรับสถาปัตยกรรมของ AMD (ฉันเชื่อว่ามีโปรแกรมวิชาการ)
  • คอมไพเลอร์เสียงดังนั้นรวดเร็วฟรีและอาจให้ผลลัพธ์ที่คล้ายกันกับ GCC (บางคนบอกว่าเร็วกว่าดีกว่า แต่อาจแตกต่างกันไปในแต่ละแอปพลิเคชันฉันขอแนะนำให้สร้างประสบการณ์ของคุณเอง)
  • PGI (Portland Group) ไม่ฟรีเหมือนกับคอมไพเลอร์ของ Intel
  • คอมไพเลอร์ PathScale อาจให้ผลลัพธ์ที่ดีกับสถาปัตยกรรมของ AMD

ฉันเคยเห็นข้อมูลโค้ดที่ความเร็วในการดำเนินการแตกต่างกันไปตามปัจจัย 2 โดยการเปลี่ยนคอมไพเลอร์เท่านั้น (แน่นอนว่ามีการเพิ่มประสิทธิภาพเต็มรูปแบบ) แต่ควรระวังการตรวจสอบข้อมูลประจำตัวของเอาต์พุต การเพิ่มประสิทธิภาพเชิงรุกอาจนำไปสู่ผลลัพธ์ที่แตกต่างกันซึ่งเป็นสิ่งที่คุณต้องการหลีกเลี่ยงอย่างแน่นอน

โชคดี!

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