C++ 在C+;中实现长方程时,如何通过高级方法提高性能+;

C++ 在C+;中实现长方程时,如何通过高级方法提高性能+;,c++,performance,optimization,floating-point,g++,C++,Performance,Optimization,Floating Point,G++,我正在开发一些工程模拟。这涉及到执行一些长方程式,例如此方程式,以计算橡胶类材料中的应力: 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

我正在开发一些工程模拟。这涉及到执行一些长方程式,例如此方程式,以计算橡胶类材料中的应力:

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)
是一个很好的建议


这将在CPU上运行,在不久的将来,这可能会在GPU上运行得更好,但目前该选项不可用。

看起来您有很多重复操作正在进行

pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
pow(l1 * l2 * l3, -0.4e1 / 0.3e1)
您可以预先计算这些值,这样您就不会重复调用
pow
函数,这可能会很昂贵

你也可以预先计算

l1 * l2 * l3

当你反复使用这个术语时,

< P>如果你有一个NVIDIA CUDA图形卡,你可以考虑把计算卸载到图形卡上,它本身更适合于计算复杂的计算。


如果不是,你可能需要考虑多个线程来计算。

哇,真是一个地狱般的表达。使用Maple创建表达式实际上是一个次优选择。结果简直无法理解

  • 选择会说话的变量名称(不是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);
    
    const double c1=pow(l1*l2*l3,-0.1e1/0.3e1);
    常数双c2=升压::数学::功率(c1);
    
    我正在使用boost函数的地方

    此外,还有一些指数
    a
    pow
    。如果
    a
    是整数并且在编译时已知,那么您也可以用
    boost::math::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)
    
    编辑摘要
    • 我最初的回答只是注意到代码中包含了大量的重复计算,并且许多幂函数都包含了1/3的因子。例如,
      pow(x,0.1e1/0.3e1)
      cbrt(x)
      相同
    • 我的第二次编辑是错误的,而我的第三次则是根据这个错误推断出来的。这就是为什么人们害怕改变以字母“M”开头的符号数学程序的类似oracle的结果。我已经删除了(即删除)这些编辑,并将它们推到当前版本的底部。但是,我没有删除它们。我是人。我们很容易犯错误
    • 我的第四次编辑开发了一个非常紧凑的表达式,它正确地表示问题中的卷积表达式如果
      l1
      l2
      l3
      参数是正实数,如果
      a
      是非零实数。(我们尚未听到OP关于这些系数的具体性质。鉴于问题的性质,这些都是合理的假设。)
    • 此编辑尝试回答如何简化这些表达式的一般问题

    第一件事 < P>我使用Maple生成C++代码来避免错误。 Maple和Mathematica有时会忽略显而易见的问题。更重要的是,Maple和Mathematica的用户有时会犯错误。用“经常”,或者甚至“几乎总是”代替“有时”可能更接近实际情况

    您可以通过告诉Maple相关参数来帮助Maple简化该表达式。在本例中,我怀疑
    l1
    l2
    l3
    是正实数,
    a
    是非零
    l123 = l1 * l2 * l3;
    l123_pow_1_3 = std::cbrt(l123);
    l123_pow_4_3 = l123 * l123_pow_1_3;
    
    (pow(l1 * l2 * l3, -0.1e1 / 0.3e1) - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1)
    
    2.0/(3.0 * pow(l1 * l2 * l3, 1.0/3.0))
    
    2.0/(3.0 * 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);
    
    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);
    
    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);
    
    // 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);
    
    // 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);