理解VS性能分析
Understanding VS performance analysis
我有一个处理零售促销的 C# 程序集。它能够在 7 秒内处理包含 1,288 种合格产品的促销活动。然而,如果它的任务是处理具有大量合格产品的促销,那么所花费的时间会随着产品数量呈指数增长。例如,包含 29,962 种产品的促销需要 7 分 7 秒,而包含 77,350 种产品的促销需要 39 分 7 秒。
我一直在尝试确定程序集中是否有可以轻松优化的代码。我将程序集处理设置为最大的提升,然后将性能分析器附加到包含进程(BizTalk 主机实例),生成以下报告:
这表明花费最多时间的函数是 "GetDataPromoLines"。此函数包含简单的字符串格式。它是从函数 "MapForFF":
的以下循环中调用的
foreach (var promoLine in promoLineChunk.PromoLines)
{
outputFile = outputFile + GetDataPromoLines(promoLine, promoLineNumber+1);
promoLineNumber++;
}
promoLineChunck.PromoLines 是描述促销活动的 class 的列表,它仅包含私有字符串 - 一个用于数据库 table 的每一列,促销详情来自选择。 "GetDataPromoLines"函数的内容如下:
private string GetDataPromoLines(VW_BT_PROMOTIONSRECORDSELECT promoLine, int sequenceNumber)
{
StringBuilder sb = new StringBuilder();
string seqNum = sequenceNumber.ToString().PadLeft(5, '0');
string uniqueNumber = promoLine.CIMS_PROMO_NUMBER + seqNum;
sb.AppendLine(string.Format("PromoDiscount,\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\"",
uniqueNumber,
promoLine.CIMS_PROMO_NAME,
promoLine.TYPE,
promoLine.DESCRIPTION_,
promoLine.DISCOUNTLEVEL,
promoLine.COUPONNUMBERMIN,
promoLine.COUPONNUMBERMAX,
promoLine.COUPONNUMBERLENGTH
));
sb.AppendLine(string.Format("ItemReq,\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"",
"00001",
promoLine.IDENTITYTYPE,
promoLine.ITEMNUM,
promoLine.DIVISIONNUM,
promoLine.DEPARTMENTNUM,
promoLine.DEPTGROUPNUM,
promoLine.CLASSNUM,
promoLine.ITEMGROUPNUM,
promoLine.IR_QUANTITY
));
sb.AppendLine(string.Format("TierDefinition,\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"",
"00001",
promoLine.THRESHOLDTYPE,
promoLine.THRESHOLDQTY,
promoLine.THRESHOLDAMT,
promoLine.DISCTYPE,
promoLine.DISCPCT,
promoLine.DISCAMT,
promoLine.DISCAPPLIESTO,
promoLine.DISCQTY,
promoLine.ADDLINFO
));
return sb.ToString();
}
任何人都可以提出导致处理时间呈指数增长的原因吗?这与 CLR 拆箱有关吗?
outputFile = outputFile + GetDataPromoLines(promoLine, promoLineNumber+1);
这是试图通过附加字符串来构建整个输出文件吗?有你的 Schlemiel。
对于这种情况,你真的想使用 StringBuilder
(或者更好的是,使用 StreamWriter
或其他东西直接输出到文件流中):
StringBuilder outputFile;
foreach (var promoLine in promoLineChunk.PromoLines)
{
outputFile.Append(GetDataPromoLines(promoLine, promoLineNumber+1));
promoLineNumber++;
}
简单附加的问题是 string
在 .NET 中是不可变的 - 每次修改它时,它都会被复制过来。对于输出巨大的文本文件之类的事情,这当然是非常昂贵的 - 您花费大部分时间来复制未更改的字符串部分。
同理,不要做sb.AppendLine(string.Format(...));
- 只需使用sb.AppendFormat
。理想情况下,将 StringBuilder
作为参数传递,以避免必须自己复制行 - 尽管在 outputFile += ...
.
旁边这应该是相对微不足道的性能影响
作为旁注,在解释分析结果时要小心 - 它通常会产生微妙的误导。在你的情况下,我很确定你的问题不在 GetDataPromoLines
本身(尽管即使可以改进,如上所示),但在 outputFile += ...
。只看独家样本最高的功能是不够的。仅仅查看热路径也是不够的,尽管这已经是一个巨大的提升,通常会直接将您引向需要您注意的地方。此外,了解采样和检测之间的区别 - 采样通常会导致您尝试优化一种本身并不是真正性能问题的方法 - 相反,它不应该像现在这样经常调用。不要将探查器结果用作蒙眼布 - 您仍然需要注意真正使 sense.
的因素
我有一个处理零售促销的 C# 程序集。它能够在 7 秒内处理包含 1,288 种合格产品的促销活动。然而,如果它的任务是处理具有大量合格产品的促销,那么所花费的时间会随着产品数量呈指数增长。例如,包含 29,962 种产品的促销需要 7 分 7 秒,而包含 77,350 种产品的促销需要 39 分 7 秒。
我一直在尝试确定程序集中是否有可以轻松优化的代码。我将程序集处理设置为最大的提升,然后将性能分析器附加到包含进程(BizTalk 主机实例),生成以下报告:
这表明花费最多时间的函数是 "GetDataPromoLines"。此函数包含简单的字符串格式。它是从函数 "MapForFF":
的以下循环中调用的foreach (var promoLine in promoLineChunk.PromoLines)
{
outputFile = outputFile + GetDataPromoLines(promoLine, promoLineNumber+1);
promoLineNumber++;
}
promoLineChunck.PromoLines 是描述促销活动的 class 的列表,它仅包含私有字符串 - 一个用于数据库 table 的每一列,促销详情来自选择。 "GetDataPromoLines"函数的内容如下:
private string GetDataPromoLines(VW_BT_PROMOTIONSRECORDSELECT promoLine, int sequenceNumber)
{
StringBuilder sb = new StringBuilder();
string seqNum = sequenceNumber.ToString().PadLeft(5, '0');
string uniqueNumber = promoLine.CIMS_PROMO_NUMBER + seqNum;
sb.AppendLine(string.Format("PromoDiscount,\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\"",
uniqueNumber,
promoLine.CIMS_PROMO_NAME,
promoLine.TYPE,
promoLine.DESCRIPTION_,
promoLine.DISCOUNTLEVEL,
promoLine.COUPONNUMBERMIN,
promoLine.COUPONNUMBERMAX,
promoLine.COUPONNUMBERLENGTH
));
sb.AppendLine(string.Format("ItemReq,\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"",
"00001",
promoLine.IDENTITYTYPE,
promoLine.ITEMNUM,
promoLine.DIVISIONNUM,
promoLine.DEPARTMENTNUM,
promoLine.DEPTGROUPNUM,
promoLine.CLASSNUM,
promoLine.ITEMGROUPNUM,
promoLine.IR_QUANTITY
));
sb.AppendLine(string.Format("TierDefinition,\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"",
"00001",
promoLine.THRESHOLDTYPE,
promoLine.THRESHOLDQTY,
promoLine.THRESHOLDAMT,
promoLine.DISCTYPE,
promoLine.DISCPCT,
promoLine.DISCAMT,
promoLine.DISCAPPLIESTO,
promoLine.DISCQTY,
promoLine.ADDLINFO
));
return sb.ToString();
}
任何人都可以提出导致处理时间呈指数增长的原因吗?这与 CLR 拆箱有关吗?
outputFile = outputFile + GetDataPromoLines(promoLine, promoLineNumber+1);
这是试图通过附加字符串来构建整个输出文件吗?有你的 Schlemiel。
对于这种情况,你真的想使用 StringBuilder
(或者更好的是,使用 StreamWriter
或其他东西直接输出到文件流中):
StringBuilder outputFile;
foreach (var promoLine in promoLineChunk.PromoLines)
{
outputFile.Append(GetDataPromoLines(promoLine, promoLineNumber+1));
promoLineNumber++;
}
简单附加的问题是 string
在 .NET 中是不可变的 - 每次修改它时,它都会被复制过来。对于输出巨大的文本文件之类的事情,这当然是非常昂贵的 - 您花费大部分时间来复制未更改的字符串部分。
同理,不要做sb.AppendLine(string.Format(...));
- 只需使用sb.AppendFormat
。理想情况下,将 StringBuilder
作为参数传递,以避免必须自己复制行 - 尽管在 outputFile += ...
.
作为旁注,在解释分析结果时要小心 - 它通常会产生微妙的误导。在你的情况下,我很确定你的问题不在 GetDataPromoLines
本身(尽管即使可以改进,如上所示),但在 outputFile += ...
。只看独家样本最高的功能是不够的。仅仅查看热路径也是不够的,尽管这已经是一个巨大的提升,通常会直接将您引向需要您注意的地方。此外,了解采样和检测之间的区别 - 采样通常会导致您尝试优化一种本身并不是真正性能问题的方法 - 相反,它不应该像现在这样经常调用。不要将探查器结果用作蒙眼布 - 您仍然需要注意真正使 sense.