Ruby 中异常处理的性能

简介: Ruby 中异常处理的性能

介绍

Ruby 是一种动态的解释型编程语言,以其简单性和灵活性而著称。Ruby 的一个关键特性是它的异常处理机制,它允许开发人员以干净和有组织的方式处理错误和异常。但是,使用异常进行错误处理会对 Ruby 应用程序的性能产生负面影响,尤其是在过度或不恰当地使用异常时。在本文中,我们将探讨在 Ruby 中使用异常对性能的影响,并讨论一些最佳实践以最大程度地减少它们对应用程序性能的影响。

Ruby 中的异常是如何工作的

在 Ruby 中,异常是表示程序执行期间发生的错误或异常情况的对象。当引发异常时,它会向上传播到调用堆栈,直到它被适当的异常处理程序捕获和处理。如果未找到异常处理程序,程序将终止并出现未处理的异常错误。

使用 raise 关键字引发异常,该关键字采用可选的消息参数和可选的异常类参数。例如,以下代码会引发RuntimeError并显示消息“出错了”:

raise "Something went wrong"
复制代码

您还可以引发特定的异常类,例如 ArgumentError:

raise ArgumentError, "Invalid argument"
复制代码

要处理异常,您可以使用 begin-rescue-end 块,它允许您指定一个可能引发异常的代码块和一个将在引发异常时处理异常的代码块。例如:

begin
  # code that may raise an exception
rescue
  # code to handle the exception
end
复制代码

您还可以指定一个特定的异常类或多个异常类来救援:

begin
  # code that may raise an exception
rescue StandardError
  # code to handle StandardError and its subclasses
rescue ArgumentError
  # code to handle ArgumentError and its subclasses
end
复制代码

最后,您可以使用 ensure 关键字来指定将始终执行的代码块,无论是否引发异常:

begin
  # code that may raise an exception
rescue
  # code to handle the exception
ensure
  # code that will always be executed
end
复制代码

异常的性能影响

使用异常进行错误处理会对 Ruby 应用程序的性能产生重大影响,尤其是在过度或不恰当地使用异常时。这是因为引发和处理异常涉及大量开销,包括创建和操作异常对象、展开调用堆栈以及执行异常处理代码。

以下是使用异常会影响 Ruby 应用程序性能的一些方式:

  • 对象创建开销:每次引发异常时,都会创建一个新的异常对象并使用适当的消息和异常类进行初始化。这涉及分配内存和初始化对象,这可能很昂贵,尤其是在频繁引发异常的情况下。
  • 展开调用堆栈:引发异常时,解释器必须展开调用堆栈以找到合适的异常处理程序。这涉及遍历调用堆栈并检查每个帧以查找异常处理程序,这可能很耗时,并且会显着增加程序的开销。
  • 异常处理代码:每次引发和处理异常时都会执行救援块中的代码,这会给程序增加额外的开销。如果异常处理代码很复杂或执行大量计算,它会进一步降低应用程序的性能。
  • 增加内存使用:异常比传统的错误处理机制使用更多的内存,例如返回错误代码或使用 nil 值来指示错误。这是因为异常对象是在调用堆栈上创建和存储的,这会导致内存使用量增加和垃圾回收速度变慢。
  • 代码执行速度变慢:与引发和处理异常相关的开销会减慢程序的整体执行速度。这在紧密循环或频繁调用的代码中尤为明显。

为了最大限度地减少异常对 Ruby 应用程序的性能影响,正确使用它们并仅在必要时使用它们非常重要。以下是在 Ruby 中使用异常的一些最佳实践:

  • Use exceptions for exceptional situations:异常应该用于处理真正的异常情况,例如意外输入、系统故障或其他无法以正常方式处理的情况。不要将异常用于控制流或替代传统的错误处理机制。
  • 避免在紧密循环中引发和处理异常:避免在紧密循环或频繁调用的代码中引发和处理异常。这会显着降低应用程序的性能。
  • 使用特定的异常类:使用特定的异常类,而不是通用的 StandardError 类,以清楚地传达错误的性质并使其更易于处理。
  • 避免拯救 Exception:不要拯救 Exception 类,因为这将捕获所有异常,包括那些不应处理的异常,例如 Interrupt 和 SystemExit。相反,拯救特定的异常类或使用更通用的类,例如 StandardError,它不会捕获系统级异常。
  • 考虑使用其他错误处理机制:在某些情况下,使用其他错误处理机制可能更合适,例如返回错误代码或使用 nil 值来指示错误。这比使用异常更有效,尤其是在错误处理代码被频繁调用或引发和处理异常的开销很大的情况下。

测试

下面是一个简单的测试示例,比较了在 Ruby 中使用异常与传统错误处理机制的性能:

require "benchmark"
# Traditional error handling using return codes
def divide_using_return_codes(x, y)
  return nil if y == 0
  x / y
end
# Exception-based error handling
def divide_using_exceptions(x, y)
  raise ZeroDivisionError if y == 0
  x / y
rescue ZeroDivisionError
  nil
end
# Benchmark the two methods
n = 1_000_000
Benchmark.bm do |bm|
  bm.report("return codes") do
    n.times do
      divide_using_return_codes(1, 0)
    end
  end
  bm.report("exceptions") do
    n.times do
      divide_using_exceptions(1, 0)
    end
  end
end
复制代码
user     system      total        real
return codes  0.044149   0.000053   0.044202 (  0.044223)
exceptions  0.508261   0.011618   0.519879 (  0.520129)
 => 
[#<Benchmark::Tms:0x000000014106b598
  @cstime=0.0,
  @cutime=0.0,
  @label="return codes",
  @real=0.04422300006262958,
  @stime=5.2999999999997494e-05,
  @total=0.04420199999999999,
  @utime=0.044148999999999994>,
 #<Benchmark::Tms:0x000000015486d8f0
  @cstime=0.0,
  @cutime=0.0,
  @label="exceptions",
  @real=0.5201290000695735,
  @stime=0.011618000000000003,
  @total=0.5198790000000001,
  @utime=0.5082610000000001>] 
复制代码

Apple Mac Book Pro 13 英寸,M1,2020 年 16GB 内存

基准测试的输出将显示每种方法的运行时间,让您可以比较两种方法的性能。您还可以修改基准以测试不同的场景,例如处理不同类型的错误或处理紧密循环中的错误。

请记住,使用异常对性能的影响会因具体用例和错误处理代码的复杂性而异。对您的代码进行基准测试和概要分析以确定最适合您的特定需求的错误处理机制始终是一个好主意。

结论

异常是处理 Ruby 中的错误和异常情况的强大而有用的工具。但是,正确使用它们以避免降低应用程序的性能非常重要。通过遵循最佳实践并仅在必要时使用异常,您可以确保您的应用程序平稳高效地运行。


相关文章
|
监控 调度 Ruby
【Ruby高级技术】在项目中使用多线程之后的一系列问题解决方案-同步控制、异常处理、死锁处理
【Ruby高级技术】在项目中使用多线程之后的一系列问题解决方案-同步控制、异常处理、死锁处理
180 0
|
SQL 存储 前端开发
基于Ruby网站数据库负载大降80%,这个沉默的性能杀手是如何被KILL的?
  摘要:一个前端开发者介绍了他和他的数据库朋友们是如何降低基于Ruby网站数据库负载的故事。以下为译文:   数据库负载可能是个沉默的性能杀手。我一直都在优化我的一个网站应用,用来吸引人们参与到开放代码社区,但我注意到一些随机的查询时间异常,有时会长达15s或更长。虽然我注意到这个现象有些时候了,我直到最近才开始优化我的数据库查询。我首先通过建立索引优化了我的主页(并且使用Rack Mini Profiler工具),然后我追踪并删除掉了一些代价高昂的查询。在这些重要的提升后,平均响应时间在50ms左右,95%在1s以内。但是,我遇到一个讨厌的问题,在24小时内,95%响应时间可能急升到15
147 0
|
6月前
|
JSON 数据格式 Ruby