C#中的例外有多贵?

时间:2009-05-21 02:57:26

标签: c# exception

C#中的例外有多贵?只要堆叠不深,它们似乎并不是非常昂贵;但我读过相互矛盾的报道。

是否有尚未被驳回的明确报告?

11 个答案:

答案 0 :(得分:64)

Jon Skeet于2006年1月撰写Exceptions and Performance in .NET

更新了Exceptions and Performance Redux(感谢@Gulzar)

Rico Mariani在The True Cost of .NET Exceptions -- Solution

中说道

另请参阅:Krzysztof Cwalina - Design Guidelines Update: Exception Throwing

答案 1 :(得分:15)

在阅读了异常在性能方面代价高昂之后,我将一个简单的测量程序放在一起,非常类似于Jon Skeet published years ago。我在这里提到这一点主要是为了提供更新的数字。

程序花费了29914毫秒来处理一百万个异常,相当于每毫秒33个异常。这足够快,使异常成为大多数情况下返回代码的可行替代方案。

请注意,使用返回代码而不是异常,同一程序运行时间不到一毫秒,这意味着异常至少比返回代码慢30,000倍。正如Rico Mariani所强调的那样,这些数字也是最小数字。在实践中,抛出和捕获异常将花费更多时间。

在配备英特尔酷睿2双核T8100 @ 2,1 GHz和.NET 4.0的笔记本电脑上进行测量,版本不在调试器下运行(这会使速度变慢)。

这是我的测试代码:

static void Main(string[] args)
{
    int iterations = 1000000;
    Console.WriteLine("Starting " + iterations.ToString() + " iterations...\n");

    var stopwatch = new Stopwatch();

    // Test exceptions
    stopwatch.Reset();
    stopwatch.Start();
    for (int i = 1; i <= iterations; i++)
    {
        try
        {
            TestExceptions();
        }
        catch (Exception)
        {
            // Do nothing
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Exceptions: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

    // Test return codes
    stopwatch.Reset();
    stopwatch.Start();
    int retcode;
    for (int i = 1; i <= iterations; i++)
    {
        retcode = TestReturnCodes();
        if (retcode == 1)
        {
            // Do nothing
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Return codes: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

    Console.WriteLine("\nFinished.");
    Console.ReadKey();
}

static void TestExceptions()
{
    throw new Exception("Failed");
}

static int TestReturnCodes()
{
    return 1;
}

答案 2 :(得分:14)

我想我在营地里,如果异常的表现会影响你的应用程序,那么你就会抛出太多的 WAY 。例外情况应该是特殊条件,而不是常规错误处理。

也就是说,我对如何处理异常的回忆基本上是在堆栈中找到一个与抛出的异常类型相匹配的catch语句。因此,性能将受到影响最大的因素来自捕获量以及您拥有的捕获语句数量。

答案 3 :(得分:4)

就我而言,例外非常昂贵。我重写了这个:

public BlockTemplate this[int x,int y, int z]
{
    get
    {
        try
        {
            return Data.BlockTemplate[World[Center.X + x, Center.Y + y, Center.Z + z]];
        }
        catch(IndexOutOfRangeException e)
        {
            return Data.BlockTemplate[BlockType.Air];
        }
    }
}

进入这个:

public BlockTemplate this[int x,int y, int z]
{
    get
    {
        int ix = Center.X + x;
        int iy = Center.Y + y;
        int iz = Center.Z + z;
        if (ix < 0 || ix >= World.GetLength(0)
            || iy < 0 || iy >= World.GetLength(1)
            || iz < 0 || iz >= World.GetLength(2)) 
            return Data.BlockTemplate[BlockType.Air];
        return Data.BlockTemplate[World[ix, iy, iz]];
    }
}

注意到速度提升了大约30秒。启动时,此函数至少被调用32K次。代码并不清楚意图是什么,但节省的成本是巨大的。

答案 4 :(得分:3)

C#中的Barebones异常对象相当轻量级;它通常是封装InnerException的能力,当对象树变得太深时,它会使它变得沉重。

至于一个明确的报告,我不知道任何,虽然粗略的dotTrace配置文件(或任何其他探查器)的内存消耗和速度将相当容易。

答案 5 :(得分:3)

我做了自己的测量,以找出异常含义有多严重。我没有尝试测量抛出/捕捉异常的绝对时间。我最感兴趣的是如果在每次传递中抛出异常,循环将变得多慢。测量代码如下所示

     for(; ; ) {
        iValue = Level1(iValue);
        lCounter += 1;
        if(DateTime.Now >= sFinish) break;
     }

VS

     for(; ; ) {
        try {
           iValue = Level3Throw(iValue);
        }
        catch(InvalidOperationException) {
           iValue += 3;
        }
        lCounter += 1;
        if(DateTime.Now >= sFinish) break;
     }

差异是20倍。第二个片段慢了20倍。

答案 6 :(得分:2)

出现异常的性能似乎是在生成异常对象时(尽管太小而不能在90%的时间内引起任何问题)。因此,建议对您的代码进行概要分析 - 如果异常 导致性能损失,则编写一个不使用异常的新高性能方法。 (想到的一个例子是(引入TryParse以解决使用异常的Parse的perf问题)

在大多数情况下,大多数情况下do not cause significant performance hits都有例外情况 - 所以MS Design Guideline是通过抛出异常来报告失败

答案 7 :(得分:1)

只提供我的个人经历: 我正在使用NewtonSoft开发一个解析JSON文件并从中提取数据的程序。

我重写了这个:

  • 选项1,有例外。
try
{
    name = rawPropWithChildren.Value["title"].ToString();
}
catch(System.NullReferenceException)
{
    name = rawPropWithChildren.Name;
}

对此:

  • 选项2,无例外。
if(rawPropWithChildren.Value["title"] == null)
{
    name = rawPropWithChildren.Name;
}
else
{
    name = rawPropWithChildren.Value["title"].ToString();
}

Ofc您实际上没有上下文可以判断,但这是我的结果:
处于调试模式

  • 选项1,但有例外。 38.50秒

  • 选项2,但有例外。 06.48s

为了提供一些背景信息,我正在使用数千个可以为空的json属性。异常抛出过多,例如在执行时间的15%之内。好吧,不是很精确,但是它们被扔了太多次了。 我想解决此问题,所以我更改了代码,但我不明白为什么执行时间如此之快。那是因为我对异常的处理不善。

因此,我从中学到的东西是:仅在特殊情况下以及无法用简单的条件语句进行测试的情况下,才需要使用异常。还必须尽可能少地抛出它们。

这对您来说是一个随机的故事,但是我想从现在开始在我的代码中使用异常之前,我肯定会三思而后行!

答案 8 :(得分:1)

TLDR;

以微秒为单位(但这取决于堆栈深度): enter image description here 这篇文章中的图片,他发布了测试代码:.Net exceptions performance

他们有空吗?没有。

你通常得到你付出的代价吗?大多数时候是的。

它们慢吗?答案应该总是“与什么相比?”它们的速度可能比任何联网服务或数据通话快几个数量级。

说明:

我对这个问题的起源很感兴趣。据我所知,这是对边际有用的 c++ 异常的残留厌恶。 .Net 异常包含大量信息,允许整洁的代码,而无需过多检查成功和日志记录。我在 another answer 中解释了它们的大部分好处。

在 20 年的编程生涯中,我从来没有删除过投掷或接球来让事情变得更快(不是说我不能,只是说有更低的悬而未决的结果,之后没有人抱怨)。

有一个带有竞争答案的单独问题,一个捕获异常(内置方法没有提供“Try”方法)和一个避免异常。

我决定对两者进行正面的性能比较,对于较少的列,非异常版本速度更快,但异常版本的扩展性更好,最终优于避免异常的版本:

enter image description here

该测试的 linqpad 代码如下(包括图形渲染)。

不过,这里的重点是,“异常很慢”的想法引出了一个问题比什么慢?如果一个深堆栈异常花费 500 微秒,那么它是否是为了响应一个需要 3000 微秒创建数据库的唯一约束而发生的?在任何情况下,这表明出于性能原因普遍避免异常不一定会产生更高性能的代码。

性能测试代码:

void Main()
{
    var loopResults = new List<Results>();
    var exceptionResults = new List<Results>();
    var totalRuns = 10000;
    for (var colCount = 1; colCount < 20; colCount++)
    {
        using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
        {
            conn.Open();

            //create a dummy table where we can control the total columns
            var columns = String.Join(",",
                (new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
            );
            var sql = $"select {columns} into #dummyTable";
            var cmd = new SqlCommand(sql,conn);
            cmd.ExecuteNonQuery();

            var cmd2 = new SqlCommand("select * from #dummyTable", conn);

            var reader = cmd2.ExecuteReader();
            reader.Read();

            Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
            {
                var results = new List<Results>();
                Random r = new Random();
                for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
                {
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    var faultCount=0;
                    for (var testRun = 0; testRun < totalRuns; testRun++)
                    {
                        if (r.NextDouble() <= faultRate)
                        {
                            faultCount++;
                            if(funcToTest(reader, "colDNE"))
                                throw new ApplicationException("Should have thrown false");
                        }
                        else
                        {
                            for (var col = 0; col < colCount; col++)
                            {
                                if(!funcToTest(reader, $"col{col}"))
                                    throw new ApplicationException("Should have thrown true");
                            }
                        }
                    }
                    stopwatch.Stop();
                    results.Add(new UserQuery.Results{
                        ColumnCount = colCount,
                        TargetNotFoundRate = faultRate,
                        NotFoundRate = faultCount * 1.0f / totalRuns,
                        TotalTime=stopwatch.Elapsed
                    });
                }
                return results;
            };
            loopResults.AddRange(test(HasColumnLoop));

            exceptionResults.AddRange(test(HasColumnException));

        }

    }
    "Loop".Dump();
    loopResults.Dump();

    "Exception".Dump();
    exceptionResults.Dump();

    var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
    combinedResults.Dump();
    combinedResults
        .Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
        .AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
        .Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
    for (int i = 0; i < dr.FieldCount; i++)
    {
        if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
            return true;
    }
    return false;
}

public static bool HasColumnException(IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

public class Results
{
    public double NotFoundRate { get; set; }
    public double TargetNotFoundRate { get; set; }
    public int ColumnCount { get; set; }
    public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
    public TimeSpan TotalTime { get; set; }


}

答案 9 :(得分:0)

我最近在一个求和循环中测量了C#异常(抛出和捕获),该循环在每次加法时都会引发算术溢出。在四核笔记本电脑上,算术溢出的抛出和捕获约为8.5微秒= 117 KiloExceptions /秒。

答案 10 :(得分:0)

异常的代价很高,但是要在异常代码和返回代码之间进行选择时,异常代码的功能更多。

从历史上讲,该参数是,异常可确保代码被强制处理情况,而返回代码可以忽略,我从不赞成使用这些参数,因为没有程序员会故意忽略并破坏其代码-特别是一支优秀的测试团队/或编写良好的测试用例绝对不会忽略返回代码。

从现代编程实践的角度出发,不仅要考虑异常例外的代价,而且还要考虑其可行性。

1st。 由于大多数前端都将与抛出异常的API断开连接。例如使用Rest API的移动应用。相同的api也可以用于基于Angular js的Web前端。

任何一种情况都将首选返回码而不是异常。

第二。 如今,黑客随机尝试破坏所有Web实用程序。在这种情况下,如果他们不断攻击您的应用程序登录api,并且应用程序不断抛出异常,那么您每天将要处理成千上万的异常。当然,许多人会说防火墙会处理此类攻击,但是并非所有人都在花钱来管理专用防火墙或昂贵的反垃圾邮件服务,因此最好为这些情况准备代码。