[英]How can I improve performance via a high-level approach when implementing long equations in C++
我正在开发一些工程模拟。 这包括实现一些长方程,例如这个方程,以计算橡胶类材料中的应力:
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
。 这确实有助于提高性能。
用cbrt(x)
代替pow(x, 0.1e1/0.3e1)
cbrt(x)
是一个很好的建议。
这将在CPU上运行,在不久的将来,这可能会在GPU上运行得更好,但目前该选项不可用。
pow(x, 0.1e1/0.3e1)
与cbrt(x)
相同。 l1
, l2
和l3
是正实数,并且a
是非零实数。 (我们还没有听到OP关于这些系数的具体性质。鉴于问题的性质,这些都是合理的假设。) 我使用Maple生成C ++代码以避免错误。
Maple和Mathematica有时会错过显而易见的事实。 更重要的是,Maple和Mathematica的用户有时会犯错误。 代替“经常”,或者甚至“几乎总是”代替“有时可能更接近标记”。
您可以通过告诉它有关的参数来帮助Maple简化该表达式。 在我手边的例子中,我怀疑l1
, l2
和l3
是正实数,而a
是非零实数。 如果是这种情况,请告诉它。 这些符号数学程序通常假设手头的数量很复杂。 限制域允许程序做出在复数中无效的假设。
符号数学程序通常提供提供有关各种参数的信息的能力。 使用该能力,特别是如果您的问题涉及分裂或取幂。 在手头的例子中,你可以通过告诉它l1
, l2
和l3
是正实数并且a
是非零实数来帮助Maple简化该表达式。 如果是这种情况,请告诉它。 这些符号数学程序通常假设手头的数量很复杂。 限制域允许程序进行假设,例如x b x =(ab) x 。 仅当a
和b
是正实数并且x
是实数时才是这样。 它在复数中无效。
最终,这些符号数学程序遵循算法。 帮助它。 在生成代码之前尝试使用扩展,收集和简化。 在这种情况下,您可以收集涉及mu
因子和涉及K
因子的那些术语。 将表达式简化为“最简单的形式”仍然是一门艺术。
当你得到一堆丑陋的生成代码时,不要接受它作为你不能触及的事实。 尽量简化它。 看一下符号数学程序在生成代码之前的作用。 看看我如何将你的表达简化为更简单,更快速的东西,以及沃尔特的答案如何进一步采取了我的几个步骤。 没有神奇的食谱。 如果有一个神奇的食谱,Maple会应用它并给出Walter给出的答案。
你在那个计算中做了很多加法和减法。 如果您的条款几乎相互抵消,您可能会陷入深深的麻烦。 如果你有一个术语比其他术语占主导地位,你就浪费了大量的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);
请注意,这很糟糕。 这是不对的。
更新
枫树确实错过了显而易见的事。
例如,有一种更容易编写的方法
(pow(l1 * l2 * l3,-0.1e1 / 0.3e1) - l1 * l2 * l3 * pow(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
是正实数。
首先要注意的是, 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);
这里我使用升压战俘功能。
此外,你有更多的指数a
pow
。 如果a
是Integer并且在编译器时间已知,那么您也可以用boost::math::pow<a>(...)
替换它们以获得进一步的性能。 我还建议用a / (l1 * 0.3e1)
替换像a / l1 / 0.3e1
这样a / l1 / 0.3e1
术语,因为乘法比除法快。
最后,如果使用g ++,则可以使用-ffast-math
标志,该标志允许优化器在转换方程时更积极。 阅读这个标志实际上做了什么 ,因为它有副作用。
哇,多么神奇的表达。 用Maple创建表达式实际上是次优选择。 结果简直难以理解。
从理论上讲,编译器应该能够为您完成所有这些工作,但有时它不能 - 例如,当循环嵌套在不同的编译单元中扩展多个函数时。 无论如何,这将为您提供更好的可读性,可理解性和可维护性的代码。
大卫·哈门的答案很好,但仍远未达到最佳状态。 让我们继续他的最后一个表达(在撰写本文时)
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
是任何机会a
是整数,那么对pow
的调用可以优化为调用cbrt
(加上整数幂),或者如果athird
是半整数,我们可以使用sqrt
(加上整数幂)。 此外,如果任何机会l1==l2
或l1==l3
或l2==l3
,可以消除对pow
一个或两个调用。 因此,如果现实存在这样的机会,将这些视为特殊情况是值得的。
我试图手动简化该公式,想知道它是否可以保存任何东西?
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;
[增加]我在最后三行公式上做了更多的工作,并将其归结为这种美:
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)
这可能有点简洁,但我实际上通过使用Horner Form找到了多项式(能量函数的插值)的良好加速,其基本上重写了ax^3 + bx^2 + cx + d
为d + x(c + x(b + x(a)))
。 这将避免大量重复调用pow()
并阻止你pow(x,6)
比如单独调用pow(x,6)
和pow(x,7)
而不是只做x*pow(x,6)
。
这不是直接适用于您当前的问题,但如果您有具有整数幂的高阶多项式,它可以提供帮助。 您可能必须注意数值稳定性和溢出问题,因为操作顺序对此非常重要(尽管通常我认为Horner Form对此有帮助,因为x^20
和x
通常相隔很多个数量级)。
另外作为一个实用的提示,如果你还没有这样做,请先尝试简化maple中的表达式。 您可以让它为您执行大多数常见的子表达式消除。 我不知道它对该程序中的代码生成器有多大影响,但我知道Mathematica在生成代码之前执行FullSimplify可能会产生巨大的差异。
看起来你有很多重复的操作正在进行中。
pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
pow(l1 * l2 * l3, -0.4e1 / 0.3e1)
你可以预先计算那些这样你就不会重复调用pow
它可以是昂贵的功能。
你也可以预先诽谤
l1 * l2 * l3
因为你反复使用那个词。
如果你有一个Nvidia CUDA显卡,你可以考虑将计算卸载到显卡 - 这本身更适合计算复杂的计算。
https://developer.nvidia.com/how-to-cuda-c-cpp
如果没有,您可能需要考虑多个线程进行计算。
无论如何,你能否象征性地提供计算。 如果有向量操作,您可能真的想要使用blas或lapack进行调查,在某些情况下可以并行运行。
可以想象(冒着偏离主题的风险?)你可以使用python和numpy和/或scipy。 在可能的范围内,您的计算可能更具可读性。
当您明确询问高级优化时,可能值得尝试不同的C ++编译器。 如今,编译器是非常复杂的优化动物,CPU供应商可能会实现非常强大和特定的优化。 但请注意,其中一些不是免费的(但可能有免费的学术课程)。
我已经看到代码片段的执行速度相差2倍,只是通过更改编译器(当然是完全优化)。 但要注意检查输出的身份。 积极的优化可能会导致不同的输出,这是你绝对想要避免的。
祝好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.