首页 > 解决方案 > 编译器是否优化了循环内的 if 语句

问题描述

我正在查看另一个程序员的代码并看到以下函数(以及几个变体,在一个巨大的循环和关键路径中被调用),我想知道 c# 编译器是否足够聪明来优化它:

public double SomeFunction(double p_lower, double p_upper) {
   double result = 0.0;
   int low = (int)Math.Ceiling(p_lower);
   int high = (int)Math.Ceiling(p_upper);

   for( int i = low; i <= high; ++i){
      double dbl_low;
      double dbl_high;
      
      if (i == low && i == high) {
        dbl_low = p_lower; // corrected from low in original post
        dbl_high = p_upper; // corrected from high original post
      } else if (i == low) {
        dbl_low = p_lower;
        dbl_high = i;
      } else if (i == high) {
        dbl_low = i - 1;
        dbl_high = p_upper;
      } else {
        dbl_low = i - 1;
        dbl_high = i;
      }
    
      if (dbl_low != dbl_high) {
        result += f(dbl_low,dbl_high);
      }
   }
   return result;
}

这个函数的作用很清楚,从 p_lower 到 p_upper 的范围分为三部分:分数到第一个整数,步长 1 到最后一个整数,从最后一个整数到 p_upper 的分数,并在这些间隔上调用函数。

第一个条件是上下都在同一单位区间内的边缘情况(从原始修正)

我的直觉(从我学会编程开始,编译器很糟糕)就是这样重写代码:

public double SomeFunction2(double p_lower, double p_upper) {

   if(p_upper < p_lower){
      return 0.0;
   }

   double result = 0.0;
   double low = Math.Ceiling(p_lower);
   double high = Math.Ceiling(p_upper);
   
   /// edge case
   if (Math.Abs(low - high) < 0.00001) {
     return Math.Abs(p_upper-p_lower)< 0.00001? 0.0 : f(p_lower, p_upper);
   }
   /// first fraction
   result += Math.Abs(low - p_lower)< 0.00001? 0.0 : f(p_lower, low);
   /// whole intervals
   for( double i = low + 1.0; i < high; ++i){ // < instead of <=
     result += f(i-1.0, i);
   }
   /// add last fraction and return
   return result + f(high - 1.0, p_upper);
}

这样,每个循环都不会评估整个级联的条件语句,第一个在第一个之后总是为假,第二个总是为真,除了最后一个。事实上,循环中没有条件,因为最后一个条件已包含在循环范围内。

循环计数器是一个双精度数,这不应该成为问题,因为低和高的范围是 0.0 ... 120.0,所有这些都与双精度数完全相同。

我是否在浪费时间,编译器是否处理了所有这些,我是否获得了一些可读性?

标签: c#loopsif-statementoptimization

解决方案


我稍微更改了您的第二个功能以提高可读性:

using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {

        Stopwatch sw = new Stopwatch();
        const int COUNT = 10000000;

        double[] lowers = new double[COUNT];
        double[] uppers = new double[COUNT];
        double[] result = new double[COUNT];
        double[] result2 = new double[COUNT];

        Random random = new Random();

        for (int i = 0; i < COUNT; i++)
        {
            lowers[i] = Math.Round(random.NextDouble() * 60.0, 2);
            uppers[i] = lowers[i] + Math.Round(random.NextDouble() * 40.0,2);
        }

        sw.Start();
        for (int i = 0; i < COUNT; i++)
        {
            result[i] = SomeFunction(lowers[i], uppers[i]);
        }
        sw.Stop();
        Console.WriteLine("Elapsed Time for SomeFunction is {0} ms", sw.ElapsedMilliseconds);

        sw.Reset();
        sw.Start();
        for (int i = 0; i < COUNT; i++)
        {
            result2[i] = SomeFunction2(lowers[i], uppers[i]);
        }
        sw.Stop();
        Console.WriteLine("Elapsed Time for SomeFunction2 is {0} ms", sw.ElapsedMilliseconds);

        for (int i = 0; i < COUNT; i++)
        {
            if (result[i] != result2[i])
            {
                Console.WriteLine("i: {0}",i);
            }
        }
    }
    
    public static double SomeFunction(double p_lower, double p_upper) {
        double result = 0.0;
        int low = (int)Math.Ceiling(p_lower);
        int high = (int)Math.Ceiling(p_upper);

        for(int i = low; i <= high; ++i){
            double dbl_low;
            double dbl_high;
      
            if (i == low && i == high) {
                dbl_low = p_lower;
                dbl_high = p_upper;
            } else if (i == low) {
                dbl_low = p_lower;
                dbl_high = i;
            } else if (i == high) {
                dbl_low = i - 1;
                dbl_high = p_upper;
            } else {
                dbl_low = i - 1;
                dbl_high = i;
            }
    
            if (dbl_low != dbl_high) {
                result += f(dbl_low,dbl_high);
            }
        }
        return result;
    }
    
    public static double SomeFunction2(double p_lower, double p_upper) {
        double result = 0.0;
        
        if (p_upper <= p_lower) {
            return result;
        }
        
        double low = Math.Ceiling(p_lower);
        double high = Math.Ceiling(p_upper);
   
        /// edge case
        if (high == low) { 
            return f(p_lower, p_upper);
        }
        /// first fraction
        if (low > p_lower) {
            result += f(p_lower, low);
        }

        /// whole intervals
        for (int i = (int)low + 1; i < high; ++i){
            result += f(i-1.0, i);
        }
        /// add last fraction and return
        return result + f(high - 1.0, p_upper);
    }
    
    // Simple function f(a,b) for test purpose
    public static double f(double a, double b)
    {
        return a + b;
    }
}

运行这个几次给了我:

3680 毫秒 / 1863 毫秒 -> 49%
2362 毫秒 / 1441 毫秒 -> 39%
3175 毫秒 / 2030 毫秒 -> 36%
2956 毫秒 / 1531 毫秒 -> 48%

所以它在性能方面保持相当接近


推荐阅读