在我的控制台项目中产生偶数的更有效的语法是什么?

本文关键字:有效 语法 是什么 控制台 我的 项目 | 更新日期: 2023-09-27 18:02:05

我最近开始学习c#,当我写这个For loop时,我知道有两种方法可以让我的代码输出偶数。我学习了版本2的语法,这对我来说很直观。然而,版本1是我在网上找到的一个例子。我想知道这两个版本是否有区别。

//Version 1
for (int num = 0; num < 100; num+=2) 
{
    Console.WriteLine (num); 
}
//Version 2
for (int num = 0; num < 100; num++)
{
    if (num % 2 == 0)
    {
        Console.WriteLine (num);
    } 
}

在这两种可能的方式中,这两种语法有什么不同吗?如果是,哪一种效率更高,为什么?

在我的控制台项目中产生偶数的更有效的语法是什么?

对于给定的N值(在示例代码中,N=100)

  • 版本#1添加N/2
  • 版本#2做了N加法,加上N整数除法(一个相对昂贵的操作)。

你忘记了版本3,比特旋转。按位操作比除法便宜得多,因为我们知道c#世界中的整数是两个补码的二进制值,所以低阶位的状态告诉我们整数是否为偶数,因此:

bool isEven( int n ) { return 0 == ( n & 1 ) ; }

我们可以编写一个测试工具,像这样:

class Program
{
  public static int Version1(int n)
  {
    int cnt = 0;
    for ( int i = 0 ; i < n ; i+=2 )
    {
      ++cnt;
    }
    return cnt;
  }
  public static int Version2(int n)
  {
    int cnt = 0;
    for ( int i = 0 ; i < n ; ++i )
    {
      if ( i % 2 == 0 )
      {
        ++cnt;
      }
    }
    return cnt;
  }
  public static int Version3(int n)
  {
    int cnt = 0;
    for ( int i = 0 ; i < n ; ++i )
    {
      if ( 0 == (i & 1) )
      {
        ++cnt;
      }
    }
    return cnt;
  }
  private static void Main(string[] args)
  {
    int n = 500000000;
    Stopwatch timer = new Stopwatch();
    timer.Start();
    Version1( n );
    timer.Stop();
    Console.WriteLine( "{0:c} -- Version #1 (incrementing by 2)" , timer.Elapsed ) ;
    timer.Restart();
    Version2(n);
    timer.Stop();
    Console.WriteLine( "{0:c} -- Version #2 (incrementing by 1 with modulo test)" , timer.Elapsed ) ;
    timer.Restart();
    Version3(n);
    timer.Stop();
    Console.WriteLine( "{0:c} -- Version #3 (incrementing by 1 with bit twiddling)" , timer.Elapsed ) ;
    return;
  }
}

找出哪个更快。上面的程序运行了500,000,000次循环,所以我们得到了足够大的可以测量的数字。

以下是我在VS 2013中得到的计时:

  • 调试构建(优化):

    00:00:00.5500486 -- Version #1 (incrementing by 2)
    00:00:02.0843776 -- Version #2 (incrementing by 1 with modulo test)
    00:00:01.2507272 -- Version #3 (incrementing by 1 with bit twiddling)
    
    • 版本#2 3.789比版本#1慢
    • 版本#3 2.274比版本#1慢1倍
  • 发布构建

    (优化)
    00:00:00.1680107 -- Version #1 (incrementing by 2)
    00:00:00.5109271 -- Version #2 (incrementing by 1 with modulo test)
    00:00:00.3367961 -- Version #3 (incrementing by 1 with bit twiddling)
    
    • 版本#2 3.041比版本#1
    • 版本#3 2.005比版本#1

您可以精确地测量哪种方法更快,精度为毫秒的万分之一,在。net框架中也称为"Tick"。

这是通过在系统中使用Stopwatch类完成的。诊断名称空间:

var sw1 = new System.Diagnostics.Stopwatch();
var sw2 = new System.Diagnostics.Stopwatch();
//Version 1
sw1.Start();
for (int num = 0; num < 100; num += 2)
{
    Console.WriteLine(num);
}
sw1.Stop();
//Version 2
sw2.Start();
for (int num = 0; num < 100; num++)
{
    if (num % 2 == 0)
    {
        Console.WriteLine(num);
    }
}
sw2.Stop();
Console.Clear();
Console.WriteLine("Ticks for first method: " + sw1.ElapsedTicks);
Console.WriteLine("Ticks for second method: " + sw2.ElapsedTicks);

输出将显示第一种方法更快。

为什么会这样?在第一个版本中,忽略控制台输出,只执行了一个操作(+= 2),最后,程序进行了50个循环。

在第二个版本中,除了在循环中进行100次循环之外,它还需要进行两次操作(++% 2)和一次比较(num % 2 == 0)。

如果您完全像这样测量程序,那么它将在时间上有很大的差异,例如多个毫秒。这是因为控制台。WriteLine实际上占用了很多时间。由于在第二个版本中完成了50倍的工作,因此需要更多的时间。如果您想单独测量算法,请忽略控制台输出。

如果你这样做,我的机器上的节拍差平均是24到43个。

综上所述,第一种方法效率更高,约为19/10000毫秒。

版本1从纯分析的角度来看"更快"(独立于编译优化)。但是正如dbarnes在你的问题的评论中提到的,你在c#中编写的代码和编译器的另一边输出的代码可能完全不同。版本2"更慢",因为它有更多的循环迭代,并且使用if语句的代码复杂度略高。

一开始不要太关注优化(在你知道它存在之前就关注性能被称为"过早优化"可以证明是一个值得解决的问题)。简单地学习代码,然后重构,然后查看存在的性能问题。

事实上,我推测编译器可以被配置为"展开"这两个循环:它去掉循环,并分别在版本1或版本2中简单地复制循环体50或100次。

实际上添加机制(版本1)稍好一些

200万次或200,000次迭代 的过程中,加法的结果优于模量(%)<0.0070000毫秒

但是说真的,除非你有一个关键任务的微优化需要,否则坚持使用模运算符来方便阅读代码,而不是试图节省大约0.000070000毫秒的执行时间。

Here Is The Source

版本1的效率更高,因为对于相同的输出,它迭代的次数减少了一半。版本1将迭代50次,而版本2将迭代100次,但只打印一半的结果。不过,我认为第二种方式更能说明你的意图。

版本1更高效。因为"version 2"比"version 1"多比较一次,而"version 1"需要两次比较。

//Version 1
for (int num = 0; num < 100; num+=2)        // Two comparisons 
{
    Console.WriteLine (num); 
}
//Version 2
for (int num = 0; num < 100; num++)         // Two comparisons
{
    if (num % 2 == 0)                       // One comparison
    {
        Console.WriteLine (num);
    }   
}

这种方法比方法1和方法2快,因为递归比循环快。

        var sw3 = new System.Diagnostics.Stopwatch();
        Action<int, int> printEvens = null;
        printEvens = (index, count) =>
        {
            if (index % 2 == 0)
                Console.WriteLine(index.ToString());
            if (index < count)
                printEvens(++index, count);
        };
        sw3.Start();
        printEvens(0, 100);
        sw3.Stop();

我的机器输出是…

方法1:96282方法二:184336方法3:递归59188