我有一个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来计算字符串的像素宽度。
我怎样才能让它更快?
答案 0 :(得分:6)
我不相信你的问题在于LINQ的速度,而在于你正在调用MeasureText
超过1000次。我认为从LINQ查询中取出逻辑并将其放入普通的foreach
循环会产生类似的运行时间。
更好的想法可能是对你正在做的事情采取一点点理智。如果您使用合理的输入(并忽略换行的可能性),那么您实际上只需要测量字符串的文本,例如,在绝对最长(以字符数表示)字符串的10%左右,字符串,然后使用最大值。换句话说,如果最大值是“古生物学”,则测量字符串“foo”没有意义。没有字体宽度可变。
答案 1 :(得分:6)
这是需要时间的MeasureText
方法,因此提高速度的唯一方法是减少工作量。
您可以将调用的结果缓存到字典中MeasureText
,这样您就不必重新测量之前已经测量过的字符串。
您可以计算一次值,并与要显示的数据保持一致。无论何时更改数据,都会重新计算值。这样,每次绘制控件时都不必测量字符串。
答案 2 :(得分:4)
第0步: 个人资料。假设您发现大部分执行时间确实在MeasureText
,那么您可以尝试以下方法来减少数量电话:
numstr.Select(digitChar=>digitLengthDict[digitChar]).Sum()
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个部分来解决非等宽字体问题。
这不会产生完美的解决方案,但它会确保您为项目保留至少足够的空间,并避免多次调用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;