C# 有没有办法让LINQ更快?
我有一个LINQ表达式,它正在减慢我的应用程序。 我正在绘制一个控件,但要做到这一点,我需要知道将显示在我的列中的文本的最大宽度 我这样做的方式是:C# 有没有办法让LINQ更快?,c#,linq,.net-3.5,custom-controls,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个项目,并占用我的绘图方法中使用的大约20%的CPU时间。更糟糕的是,
return Items.Max(w => TextRenderer.MeasureText((w.RenatlUnit == null)? "" :
w.RenatlUnit.UnitNumber, this.Font).Width) + 2;
然而,这将迭代1000个项目,并占用我的绘图方法中使用的大约20%的CPU时间。更糟糕的是,还有另外两列必须使用,因此所有项/列上的LINQ语句占用了大约75-85%的CPU时间
TextRenderer来自System.Windows.Forms软件包,因为我没有使用单间距字体,所以需要MeasureText来计算字符串的像素宽度
我怎样才能使它更快呢?我不认为你的问题在于LINQ的速度,而在于你调用
MeasureText
超过1000次。我可以想象,将您的逻辑从LINQ查询中取出,并将其放入普通的foreach
循环将产生类似的运行时间
一个更好的办法可能是对你正在做的事情进行一点理智的检查。如果您使用合理的输入(并且忽略换行的可能性),那么您实际上只需要测量字符串的文本,例如,在绝对最长(以字符数计)字符串的10%左右,然后使用最大值。换句话说,如果最大值是“古生物学”,那么测量字符串“foo”就没有意义了。没有字体具有该变量的宽度。不幸的是,LINQ看起来不是您的问题。如果为循环运行
,并执行相同的计算,则时间量将是相同的数量级
您是否考虑过在多个线程上运行此计算?这会很好地配合你的工作
编辑:似乎并行LINQ无法工作,因为MeasureText
是一个GDI函数,将被封送回UI线程(感谢@Adam Robinson纠正我)。如果您不知道如何使MeasureText
更快,您可以预先计算字体大小和样式中所有字符的宽度,并像这样估计字符串的宽度,尽管字符对的紧排可能表明这可能只是一个估计值,而不是精确值。这是MeasureText
方法需要时间,因此,提高速度的唯一方法是少做功
您可以将调用MeasureText
的结果缓存在字典中,这样您就不必重新测量以前已经测量过的字符串
您可以计算一次值,并与要显示的数据一起保存。无论何时更改数据,都会重新计算值。这样,您就不必每次绘制控件时都测量字符串。我猜问题不在于LINQ表达式,而是调用MeasureText
数千次
我认为你可以通过将问题分成4个部分来解决非等距字体问题
查找渲染大小方面的最大数字
查找数字最多的公寓单元
创建一个字符串,所有值均为#1中确定的值,大小为#2
将#3中创建的值传递到MeasureText,并将其用作基础
这不会产生一个完美的解决方案,但它将确保您至少为项目保留足够的空间,并避免调用MeasureText
太多次的陷阱 > P>你可能想考虑一个近似值,取最长字符串的长度,然后找出长度为<代码> 0 < /Cord>的字符串的宽度(或者,无论数字最大的是什么,我都记不起来)。这应该是一个更快的方法,但它只是一个近似值,可能比需要的时间更长
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;
第0步:配置文件。假设您发现大部分执行时间确实在MeasureText
中,那么您可以尝试以下方法来减少调用次数:
计算所有单个字符的长度。因为听起来像是在渲染一个数字,所以这应该是一个小集合
估计长度numstr.Select(digitChar=>digitLengthDict[digitChar]).Sum()
取长度为前N的字符串,仅测量这些长度
为了避免lookup+sum的大部分开销,还可以按照建议筛选只包括最大字符串长度90%以内的字符串
e、 类似于
// 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
。是的,我相信MeasureText也是问题所在,但是你有什么建议来补救这种情况呢?我考虑过搜索最长的字符串,然后调用MeasureText一次,但最长的字符串可能不是最长的字符串wise@Malfist以长度为单位的弦