【问题标题】:Performant string check执行字符串检查
【发布时间】:2019-05-05 06:26:31
【问题描述】:

我的 C# 应用程序中有这个功能:

public static string SafeTrim(object str) 
{
    if ( str == null || str == DBNull.Value )
      return string.Empty;
    else
      return str.ToString().Trim();
}

它工作正常,但在我的导入实用程序中,它在处理数十万条记录时被调用了数百万次。 ANTS profiler 指出这个函数会消耗大量的 CPU 周期,因为它被频繁调用。

编辑:我没有提到SafeTrim() 的一个非常常见的用法 我的应用程序适用于 DataRow/DataColumn 值。例子: SafeTrim(dt.Rows[0]["id"]) - 通常包含一个 DBNull.Value,包含边缘空间也很常见 需要修剪。

可以以任何方式进行优化吗?

编辑:我将在负载下尝试这些不同的方法,并且 明天回来汇报。谢谢大家!

【问题讨论】:

  • 如果要修剪字符串值,为什么要带参数object?这是如何调用的以及传递给它的内容?
  • 我会先问自己是否真的需要如此频繁地调用此方法。也许(也许)您可以通过以类型感知的方式处理记录字段来消除对该方法的大量调用(例如,对于包含数字的记录字段,您不需要调用此方法)
  • DBNull.Value 似乎是从 IDataReader 获取的;有趣的是,您可以从 IDataReader 的第一条记录中获取 IsDbNull/GetFieldType - 然后以惊人的速度继续前进。如果速度是一个问题,不要在那里偷懒。
  • 您可以应用 [MethodImpl(MethodImplOptions.AggressiveInlining)] 属性。适用于 x64 但不适用于 x86。这将阻止分析器抱怨它,因为它不再能看到该方法。然而,您的程序是否实际上更快是非常值得怀疑的。不仅仅是因为内联它可以让它变慢,这个程序的执行时间应该完全由 I/O 控制。检索数据的成本,分析器通常不会显示。将代码速度提高一倍并不会使 I/O 速度提高一倍,如果您看到了几个百分比的改进,那您就很幸运了。

标签: c#


【解决方案1】:

在我看来,一些简单的重载可能有用:

public static string SafeTrim(object str)
{
    return str is string x ? SafeTrim(x) : String.Empty;
}

public static string SafeTrim(string str)
{
    return str?.Trim() ?? string.Empty;
}

如果object str 是其他类型,我不明白返回值应该是什么,所以我没有把它放进去。


这是我的测试代码:

void Main()
{
    var rnd = new Random();
    var strings = Enumerable.Range(0, 1000000).Select(x => x.ToString() + (rnd.Next(2) == 0 ? " " : "")).ToArray();

    var results = new [] { new { source = 0, elapsed = TimeSpan.Zero } }.Take(0).ToList();

    for (int i = 0; i < 100; i++)
    {

        var sw = Stopwatch.StartNew();
        var trimmed0 = strings.Select(x => SafeTrim0(x)).ToArray();
        sw.Stop();
        results.Add(new { source = 0, elapsed = sw.Elapsed });

        sw = Stopwatch.StartNew();
        var trimmed1 = strings.Select(x => SafeTrim1(x)).ToArray();
        sw.Stop();
        results.Add(new { source = 1, elapsed = sw.Elapsed });      
    }

    results.GroupBy(x => x.source, x => x.elapsed.TotalMilliseconds).Select(x => new { x.Key, Average = x.Average() }).Dump();
}

public static string SafeTrim1(string str)
{
    return str?.Trim() ?? string.Empty;
}

public static string SafeTrim0(object str) 
{
    if ( str == null || str == DBNull.Value )
      return string.Empty;
    else
      return str.ToString().Trim();
}

SafeTrim0SafeTrim1 之间的平均运行时间分别为 155.8 毫秒和 147.7 毫秒。

【讨论】:

  • 我用你的和我的测试过,每个运行 100000000 次。我的快了2秒。 :-(
  • @HerrimanCoder - 愿意分享测试和测量代码吗?
  • 我看看能不能把它放在一起。
  • 您也应该在测试中添加一些 DBNull.Values。
【解决方案2】:

使用ReferenceEquals,您可以减少一些来自运算符重载的嵌套函数调用。

public static string SafeTrim(object str) 
{
    if(ReferenceEquals(str, null) || 
       ReferenceEquals(str, DBNull.Value)) //DbNull.Value is singleton
    { 
        return string.Empty;
    }

    return str.ToString().Trim();
}

【讨论】:

  • 我用你的和我的测试过,每个运行 100000000 次。速度没有显着差异。
【解决方案3】:
Possible solution
public static string SafeTrim(object str)
{
     string result = (str as string)?.Trim() ?? null;
     if (result == null)
           return string.Empty;
     return result;
}

【讨论】:

  • 或非字符串大小写。这行为根本不同。
  • 看一行:string result = (str as string)?.Trim() ??空值;如果参数中传递的值 DBNull 它将自动转换为 null 因为它不是字符串
  • 为了进一步提高性能,您可以通过重新检查不是分隔符字符串的第一个和最后一个字符来避免重新创建已修剪的字符串值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-26
  • 1970-01-01
  • 1970-01-01
  • 2021-10-13
  • 2017-04-15
  • 2011-02-18
相关资源
最近更新 更多