【问题标题】:C# performance of conditional versus memory allocation条件与内存分配的 C# 性能
【发布时间】:2018-11-18 17:58:29
【问题描述】:

考虑以下 C# 代码:

IntPtr native = GetNativeError(/* parameters here */);
return new ManagedError(native);

然后客户端代码通过以下方式检查错误:

ManagedError err = /* get it with the code above */;
if(err.IsOk()) {
    /* Success */
}

这里的内存分配可以通过if 语句来节省:

IntPtr native = GetNativeError(/* parameters here */);
if(native == IntPtr.Zero) {
    return null;
} else {
    return new ManagedError(native);
}

然后客户端代码将检查错误如下:

ManagedError err = /* get it with the code above */;
if(err == null) {
    /* Success */
}

我的问题是:哪一种方法更快?第一个有额外的内存分配。第二个有额外的if 声明。

更新:我的意思是,在成功的情况下。错误场景很少发生并且可以很慢。

【问题讨论】:

  • 由于它返回 null 的次数与它分配对象的次数相比会很低,这可能是一个微不足道的性能提升。可能是“过早的优化”。
  • @MatthewWatson 不是相反吗?成功 == 很常见 == 零分配(null)
  • 一般来说第二种方式更清晰,对我来说:ManagedError 不是错误但成功的是ManagedResult(参见HRESULTS_OK)跨度>
  • 请注意,如果 sizeof(ManagedError) <= IntPtr.Size 将其设为 struct 将使其“免费”(因为大小为 IntPtr.Size 的 struct 对 @ 没有任何开销987654336@或参考)
  • 不清楚您的项目要求是什么,但这是在 RCW 中完成的方式:在本机调用后检查错误代码。如果不是 S_OK,则抛出异常。该异常包含本机错误代码等。这为您提供了两全其美的体验。如果没有错误,则没有开销,如果有则异常处理的所有好处。您可以在此处阅读有关 RCW 的更多信息:msdn.microsoft.com/en-us/library/8bwh56xe(VS.100).aspx

标签: c# performance memory-management .net-core conditional


【解决方案1】:

首先,您已经以两种方式编写了代码。如果您想知道哪种方式的性能更好运行这两个程序并衡量哪一个的性能更好。这是唯一准确回答性能问题的方法,所以拿出你的秒表。

如果您无法衡量哪个性能更好,那么显然哪个性能更好并不重要,因为无法检测到差异。无法观察到的差异是无关紧要的。

现在让我们考虑一下您的具体问题。假设您确实决定在常见的成功案例中保存内存分配。 正确的解决方法是不要给 null 赋予特殊含义。正确的解决方案是使用空对象模式。那就是创建一个对象的特殊实例,该实例总是在你会使用 null 的地方使用

class ManagedError 
{
  public static readonly Success = new ManagedError(IntPtr.Zero);
  private ManagedError(IntPtr hr) { ... }
  public ManagedError FromNative(IntPtr hr) 
  {
    if (hr == IntPtr.Zero) return Success;
    return new ManagedError(hr);
  }
}
...
IntPtr native = GetNativeError(/* parameters here */);
return ManagedError.FromNative(native);

完成。你总是得到一个有效的对象,而且在一般情况下你不做任何分配。

另外,正如另一个答案中提到的:为什么这不是一个结构? 你为什么要进行任何引用类型的堆内存分配?如果这个东西只是一个 intptr 的包装器,那么它应该是一个结构;它会和 intptr 一样便宜!

如果你把它变成一个结构,那么这会变得更容易,因为你只需使用结构的默认实例作为你的空对象!

struct ManagedError 
{
  public static readonly Success = default(ManagedError);
  private readonly IntPtr hr;
  public ManagedError(IntPtr hr) { this.hr = hr }
}

你就完成了;根本没有堆分配。你只需把 intptr 包起来。

【讨论】:

    【解决方案2】:

    一般来说,第二种方式更清楚,对我来说:ManagedError 不是错误但成功的是ManagedResult(例如,请参阅使用HRESULT 的 COM API,这可以是S_OK(或一般S_*)或E_*(例如E_FAIL)。

    我会说通常分配一个无用的对象不是一个好主意......但这甚至不是一个坏主意(开销非常小,因为如果对象的生命周期很短,GC 会破坏它立即)。仍然是针对 .NET 模式(使用 Exception 来返回错误,或使用 bool)...所以即使在这里我也看不到大的加号或大的减号。

    请注意,如果 sizeof(ManagedError) <= IntPtr.Size 将其设置为 struct 将使其“免费”分配和传递(因为带有 sizeof <= IntPtr.SizestructIntPtr 或一般反对托管参考)

    【讨论】:

      猜你喜欢
      • 2011-01-31
      • 1970-01-01
      • 2011-06-01
      • 1970-01-01
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多