有什么办法让这个LINQ更快?

时间:2010-09-20 16:52:58

标签: c# linq .net-3.5 custom-controls

我有一个LINQ表达式,这会减慢我的应用程序。 我正在绘制一个控件,但要执行此操作,我需要知道将在我的列中显示的文本的最大宽度。

我这样做是这样的:

return Items.Max(w => TextRenderer.MeasureText((w.RenatlUnit == null)? "" : 
w.RenatlUnit.UnitNumber, this.Font).Width) + 2;

然而,这会迭代超过~1000个项目,并占用我绘图方法中使用的CPU时间的大约20%。更糟糕的是,还有两个必须完成的列,因此所有项/列上的LINQ语句占用了大约75-85%的CPU时间。

TextRenderer来自System.Windows.Forms包,因为我没有使用等宽字体,所以需要使用MeasureText来计算字符串的像素宽度。

我怎样才能让它更快?

7 个答案:

答案 0 :(得分:6)

我不相信你的问题在于LINQ的速度,而在于你正在调用MeasureText超过1000次。我认为从LINQ查询中取出逻辑并将其放入普通的foreach循环会产生类似的运行时间。

更好的想法可能是对你正在做的事情采取一点点理智。如果您使用合理的输入(并忽略换行的可能性),那么您实际上只需要测量字符串的文本,例如,在绝对最长(以字符数表示)字符串的10%左右,字符串,然后使用最大值。换句话说,如果最大值是“古生物学”,则测量字符串“foo”没有意义。没有字体宽度可变。

答案 1 :(得分:6)

这是需要时间的MeasureText方法,因此提高速度的唯一方法是减少工作量。

您可以将调用的结果缓存到字典中MeasureText,这样您就不必重新测量之前已经测量过的字符串。

您可以计算一次值,并与要显示的数据保持一致。无论何时更改数据,都会重新计算值。这样,每次绘制控件时都不必测量字符串。

答案 2 :(得分:4)

第0步: 个人资料。假设您发现大部分执行时间确实在MeasureText,那么您可以尝试以下方法来减少数量电话:

  1. 计算所有单个字符的长度。因为它听起来像是在渲染一个数字,所以这应该是一个很小的集合。
  2. 估算长度numstr.Select(digitChar=>digitLengthDict[digitChar]).Sum()
  3. 使用顶部 N 长度的字符串,并仅测量那些。
  4. 为了避免查找+总和的大部分成本,也按照建议过滤到仅包含最大字符串长度的90%内的那些字符串。
  5. e.g。有点像...

    // somewhere else, during initialization - do only once.
    var digitLengthDict = possibleChars.ToDictionary(c=>c,c=>TextRenderer.MeasureText(c.ToString()));
    
    //...
    
    var relevantStringArray = Items.Where(w=>w.RenatlUnit!=null).Select(w.RenatlUnit.UnitNumber).ToArray();
    
    double minStrLen = 0.9*relevantStringArray.Max(str => str.Length);
    
    return (
        from numstr in relevantStringArray 
        where str.Length >= minStrLen
        orderby numstr.Select(digitChar=>digitLengthDict[digitChar]).Sum() descending
        select TextRenderer.MeasureText(numstr)
        ).Take(10).Max() + 2;
    

    如果我们对字符串的分布有更多了解,那会有所帮助。

    此外,MeasureText不是魔术;很有可能你可以完全轻松地复制它的功能,用于一组有限的输入。例如,我不会惊讶地发现,字符串的测量长度恰好等于字符串中所有字符长度的总和,减去字符串中所有字符双字母的字距。如果您的字符串包含,例如0 - 9+-,.和终止符号,然后,一个包含14个字符宽度和15*15-1内核更正的查找表可能足以在更快的速度下精确模拟MeasureText,并且没有太大的复杂性。

    最后,最好的解决方案是根本不解决问题 - 也许你可以重新设计应用程序以不需要这么精确的数字 - 如果更简单的估计就足够了,你几乎可以完全避免MeasureText

答案 3 :(得分:1)

不幸的是,看起来LINQ不是你的问题。如果您运行for循环并执行相同的计算,则时间量将是相同的数量级。

您是否考虑过在多个线程上运行此计算?它可以很好地与Parallel LINQ一起使用。

编辑:似乎并行LINQ无法正常工作,因为MeasureText是一个GDI函数,只会被编组回UI线程(感谢@Adam Robinson纠正我。)

答案 4 :(得分:1)

我的猜测是问题不是LINQ表达式,而是调用MeasureText数千次。

我认为您可以通过将问题分解为4个部分来解决非等宽字体问题。

  1. 根据渲染大小找出最大数字
  2. 找到位数最多的公寓单元
  3. 创建一个字符串,其中所有值均为#1中确定的值,且大小为#2。
  4. 将#3中创建的值传递给MeasureText,并将其作为基础
  5. 这不会产生完美的解决方案,但它会确保您为项目保留至少足够的空间,并避免多次调用MeasureText的陷阱。

答案 5 :(得分:0)

如果您无法弄清楚如何使MeasureText更快,您可以预先计算字体大小和样式中所有字符的宽度,并估计字符串的宽度,尽管字符对的字距调整会暗示它可能只是估计而不准确。

答案 6 :(得分:0)

您可能需要考虑作为一个近似值,取最长字符串的长度,然后找到0的长度字符串的宽度(或者无论最宽的数字是什么,我都记不住了)。这应该是一个更快的方法,但它只是一个近似值,可能比必要的更长。

var longest = Items.Max( w => w.RenatlUnit == null
                                  || w.RenatlUnit.UnitNumber == null)
                              ? 0
                              : w.RenatlUnit.UnitNumber.Length );
if (longest == 0)
{
    return 2;
}
return TextRenderer.MeasureText( new String('0', longest ) ).Width + 2;