代码之家  ›  专栏  ›  技术社区  ›  Malfist

有没有办法让这个LINQ更快?

  •  2
  • Malfist  · 技术社区  · 14 年前

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

    我这样做的方式是:

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

    但是,这将迭代大约1000个项目,并占用我的绘图方法中使用的CPU时间的20%左右。更糟糕的是,还有另外两列必须完成这项工作,因此所有项/列上的LINQ语句占用CPU时间的75-85%。

    textenderer来自system.windows.forms包,由于我没有使用单空间字体,因此需要measureText来计算字符串的像素宽度。

    我该怎么做得更快?

    7 回复  |  直到 14 年前
        1
  •  6
  •   Adam Robinson    14 年前

    我不相信你的问题在于LINQ的速度,而是因为你打电话来 MeasureText 超过1000次。我可以想象,将您的逻辑从Linq查询中取出,并将其放入普通的 foreach 循环将产生类似的运行时间。

    一个更好的办法可能是用一点理智来检查你在做什么。如果使用合理的输入(并且忽略换行符的可能性),那么您只需要测量字符串的文本,例如,在绝对最长(以字符数计)字符串的10%左右,然后使用最大值。换句话说,如果最大值是“古生物学”,那么测量字符串“foo”就没有意义。没有字体的宽度是可变的。

        2
  •  6
  •   Guffa    14 年前

    它是 MeasureText 方法需要时间,所以提高速度的唯一方法就是少做一些工作。

    您可以将调用的结果缓存到 测量文本 在字典中,这样就不需要重新测量以前已经测量过的字符串。

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

        3
  •  4
  •   Eamon Nerbonne    14 年前

    步骤0: 轮廓。 假设您发现大多数执行时间确实在 MeasureText ,然后可以尝试以下操作以减少呼叫数:

    1. 计算所有单个字符的长度。因为听起来你在渲染一个数字,所以这应该是一个小的集合。
    2. 估计长度 numstr.Select(digitChar=>digitLengthDict[digitChar]).Sum()
    3. 拿着上面的绳子 n 长度,只测量长度。
    4. 为了避免查找+求和的大部分成本,也要过滤,只包括最大字符串长度90%以内的字符串,如建议的那样。

    比如……

    // 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;
    

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

    也, 测量文本 这不是魔术;对于一组有限的输入,完全可以很容易地复制它的功能。例如,如果知道一个字符串的测量长度恰好等于该字符串中所有字符的长度之和,减去该字符串中所有字符大括号的紧排悬垂,我不会感到惊讶。如果你的线由,比如, 0 - 9 , + , - , , , . ,和一个终止符符号,然后是一个14个字符宽度和 15*15-1 内核修正可能足以精确模拟 测量文本 在一 远的 速度更快,而且不太复杂。

    最后,最好的解决方案是根本不解决问题-也许您可以重新构造应用程序,使其不需要如此精确的数字-如果简单的估计足够,则可以避免 测量文本 几乎完全。

        4
  •  1
  •   Bryan Watts    14 年前

    不幸的是,看起来Linq不是你的问题。如果你跑了 for 循环并做同样的计算,时间量将是相同的数量级。

    您是否考虑在多个线程上运行此计算?它可以很好地与 Parallel LINQ .

    编辑: 似乎并行LINQ不起作用,因为 MeasureText 是一个gdi函数,将简单地封送回ui线程(感谢@adam robinson更正我的错误)。

        5
  •  1
  •   JaredPar    14 年前

    我猜问题不是linq表达式而是调用 MeasureText 几千次。

    我认为你可以通过把问题分成4个部分来解决非单空格字体的问题。

    1. 根据渲染大小查找最大的数字
    2. 找到数字最多的公寓单元
    3. 创建一个字符串,所有值均为在1中确定的值,大小为2。
    4. 将在3中创建的值传递给MeasureText,并将其用作基础

    这不会产生一个完美的解决方案,但它将确保您至少为您的物品保留足够的空间,并避免调用的陷阱。 测量文本 太多次了。

        6
  •  0
  •   mqp    14 年前

    如果你不知道怎么做 MeasureText 更快的是,您可以预先计算字体大小和样式中所有字符的宽度,并像这样估计字符串的宽度,尽管字符对的紧排意味着这可能只是一个估计,并不精确。

        7
  •  0
  •   tvanfosson    14 年前

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

    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;