提交 b932a6d1 编写于 作者: S Stephen Toub

Merge pull request #415 from stephentoub/string_concat_opt

Avoid unnecessary boxing with String.Concat
......@@ -45,6 +45,10 @@ private BoundExpression RewriteStringConcatenation(CSharpSyntaxNode syntax, Bina
return RewriteStringConcatInExpressionLambda(syntax, operatorKind, loweredLeft, loweredRight, type);
}
// avoid run time boxing and ToString operations if we can reasonably convert to a string at compile time
loweredLeft = ConvertConcatExprToStringIfPossible(syntax, loweredLeft);
loweredRight = ConvertConcatExprToStringIfPossible(syntax, loweredRight);
// try fold two args without flattening.
var folded = TryFoldTwoConcatOperands(syntax, loweredLeft, loweredRight);
if (folded != null)
......@@ -346,5 +350,83 @@ private BoundExpression RewriteStringConcatInExpressionLambda(CSharpSyntaxNode s
return new BoundBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, default(ConstantValue), method, default(LookupResultKind), type);
}
/// <summary>
/// Checks whether the expression represents a boxing conversion of a special value type.
/// If it does, it tries to return a string-based representation instead in order
/// to avoid allocations. If it can't, the original expression is returned.
/// </summary>
private BoundExpression ConvertConcatExprToStringIfPossible(CSharpSyntaxNode syntax, BoundExpression expr)
{
if (expr.Kind == BoundKind.Conversion)
{
BoundConversion conv = (BoundConversion)expr;
if (conv.ConversionKind == ConversionKind.Boxing)
{
BoundExpression operand = conv.Operand;
if (operand != null)
{
// Is the expression a literal char? If so, we can
// simply make it a literal string instead and avoid any
// allocations for converting the char to a string at run time.
if (operand.Kind == BoundKind.Literal)
{
ConstantValue cv = ((BoundLiteral)operand).ConstantValue;
if (cv != null && cv.SpecialType == SpecialType.System_Char)
{
return _factory.StringLiteral(cv.CharValue.ToString());
}
}
// Can the expression be optimized with a ToString call?
// If so, we can synthesize a ToString call to avoid boxing.
if (ConcatExprCanBeOptimizedWithToString(operand.Type))
{
var toString = GetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString);
return BoundCall.Synthesized(syntax, operand, toString);
}
}
}
}
// Optimization not possible; just return the original expression.
return expr;
}
/// <summary>
/// Gets whether the type of an argument used in string concatenation can
/// be optimized by first calling ToString on it before passing the argument
/// to the String.Concat function.
/// </summary>
/// <param name="symbol">The type symbol of the argument.</param>
/// <returns>
/// true if ToString may be used; false if using ToString could lead to observable differences in behavior.
/// </returns>
private static bool ConcatExprCanBeOptimizedWithToString(TypeSymbol symbol)
{
// There are several constraints applied here in support of backwards compatibility:
// - This optimization potentially changes the order in which ToString is called
// on the arguments. That's a a compatibility issue if one argument's ToString
// depends on state mutated by another, such as current culture.
// - For value types, this optimization causes ToString to be called on the original
// value rather than on a boxed copy. That means a mutating ToString implementation
// could change the original rather than the copy.
// For these reasons, this optimization is currently restricted to primitives
// known to have a non-mutating ToString implementation that is independent
// of externally mutable state. Common value types such as Int32 and Double
// do not meet this bar.
switch (symbol.SpecialType)
{
case SpecialType.System_Boolean:
case SpecialType.System_Char:
case SpecialType.System_IntPtr:
case SpecialType.System_UIntPtr:
return true;
default:
return false;
}
}
}
}
......@@ -845,5 +845,255 @@ static void Main()
CompileAndVerify(source, expectedOutput: "(0)(0)(0)(1)(2)(0)(0)(0)(1)(2)");
}
[Fact]
public void ConcatWithSpecialValueTypes()
{
var source = @"
using System;
public class Test
{
static void Main()
{
const char a = 'a', b = 'b';
char c = 'c', d = 'd';
Console.WriteLine(a + ""1"");
Console.WriteLine(""2"" + b);
Console.WriteLine(c + ""3"");
Console.WriteLine(""4"" + d);
Console.WriteLine(true + ""5"" + c);
Console.WriteLine(""6"" + d + (IntPtr)7);
Console.WriteLine(""8"" + (UIntPtr)9 + false);
Console.WriteLine(c + ""10"" + d + ""11"");
Console.WriteLine(""12"" + c + ""13"" + d);
Console.WriteLine(a + ""14"" + b + ""15"" + a + ""16"");
Console.WriteLine(c + ""17"" + d + ""18"" + c + ""19"");
Console.WriteLine(""20"" + 21 + c + d + c + d);
Console.WriteLine(""22"" + c + ""23"" + d + c + d);
}
}
";
var comp = CompileAndVerify(source, expectedOutput: @"a1
2b
c3
4d
True5c
6d7
89False
c10d11
12c13d
a14b15a16
c17d18c19
2021cdcd
22c23dcd");
comp.VerifyDiagnostics();
comp.VerifyIL("Test.Main", @"
{
// Code size 611 (0x263)
.maxstack 4
.locals init (char V_0, //c
char V_1, //d
bool V_2,
System.IntPtr V_3,
System.UIntPtr V_4)
IL_0000: ldc.i4.s 99
IL_0002: stloc.0
IL_0003: ldc.i4.s 100
IL_0005: stloc.1
IL_0006: ldstr ""a1""
IL_000b: call ""void System.Console.WriteLine(string)""
IL_0010: ldstr ""2b""
IL_0015: call ""void System.Console.WriteLine(string)""
IL_001a: ldloca.s V_0
IL_001c: constrained. ""char""
IL_0022: callvirt ""string object.ToString()""
IL_0027: ldstr ""3""
IL_002c: call ""string string.Concat(string, string)""
IL_0031: call ""void System.Console.WriteLine(string)""
IL_0036: ldstr ""4""
IL_003b: ldloca.s V_1
IL_003d: constrained. ""char""
IL_0043: callvirt ""string object.ToString()""
IL_0048: call ""string string.Concat(string, string)""
IL_004d: call ""void System.Console.WriteLine(string)""
IL_0052: ldc.i4.1
IL_0053: stloc.2
IL_0054: ldloca.s V_2
IL_0056: constrained. ""bool""
IL_005c: callvirt ""string object.ToString()""
IL_0061: ldstr ""5""
IL_0066: ldloca.s V_0
IL_0068: constrained. ""char""
IL_006e: callvirt ""string object.ToString()""
IL_0073: call ""string string.Concat(string, string, string)""
IL_0078: call ""void System.Console.WriteLine(string)""
IL_007d: ldstr ""6""
IL_0082: ldloca.s V_1
IL_0084: constrained. ""char""
IL_008a: callvirt ""string object.ToString()""
IL_008f: ldc.i4.7
IL_0090: call ""System.IntPtr System.IntPtr.op_Explicit(int)""
IL_0095: stloc.3
IL_0096: ldloca.s V_3
IL_0098: constrained. ""System.IntPtr""
IL_009e: callvirt ""string object.ToString()""
IL_00a3: call ""string string.Concat(string, string, string)""
IL_00a8: call ""void System.Console.WriteLine(string)""
IL_00ad: ldstr ""8""
IL_00b2: ldc.i4.s 9
IL_00b4: conv.i8
IL_00b5: call ""System.UIntPtr System.UIntPtr.op_Explicit(ulong)""
IL_00ba: stloc.s V_4
IL_00bc: ldloca.s V_4
IL_00be: constrained. ""System.UIntPtr""
IL_00c4: callvirt ""string object.ToString()""
IL_00c9: ldc.i4.0
IL_00ca: stloc.2
IL_00cb: ldloca.s V_2
IL_00cd: constrained. ""bool""
IL_00d3: callvirt ""string object.ToString()""
IL_00d8: call ""string string.Concat(string, string, string)""
IL_00dd: call ""void System.Console.WriteLine(string)""
IL_00e2: ldloca.s V_0
IL_00e4: constrained. ""char""
IL_00ea: callvirt ""string object.ToString()""
IL_00ef: ldstr ""10""
IL_00f4: ldloca.s V_1
IL_00f6: constrained. ""char""
IL_00fc: callvirt ""string object.ToString()""
IL_0101: ldstr ""11""
IL_0106: call ""string string.Concat(string, string, string, string)""
IL_010b: call ""void System.Console.WriteLine(string)""
IL_0110: ldstr ""12""
IL_0115: ldloca.s V_0
IL_0117: constrained. ""char""
IL_011d: callvirt ""string object.ToString()""
IL_0122: ldstr ""13""
IL_0127: ldloca.s V_1
IL_0129: constrained. ""char""
IL_012f: callvirt ""string object.ToString()""
IL_0134: call ""string string.Concat(string, string, string, string)""
IL_0139: call ""void System.Console.WriteLine(string)""
IL_013e: ldstr ""a14b15a16""
IL_0143: call ""void System.Console.WriteLine(string)""
IL_0148: ldc.i4.6
IL_0149: newarr ""string""
IL_014e: dup
IL_014f: ldc.i4.0
IL_0150: ldloca.s V_0
IL_0152: constrained. ""char""
IL_0158: callvirt ""string object.ToString()""
IL_015d: stelem.ref
IL_015e: dup
IL_015f: ldc.i4.1
IL_0160: ldstr ""17""
IL_0165: stelem.ref
IL_0166: dup
IL_0167: ldc.i4.2
IL_0168: ldloca.s V_1
IL_016a: constrained. ""char""
IL_0170: callvirt ""string object.ToString()""
IL_0175: stelem.ref
IL_0176: dup
IL_0177: ldc.i4.3
IL_0178: ldstr ""18""
IL_017d: stelem.ref
IL_017e: dup
IL_017f: ldc.i4.4
IL_0180: ldloca.s V_0
IL_0182: constrained. ""char""
IL_0188: callvirt ""string object.ToString()""
IL_018d: stelem.ref
IL_018e: dup
IL_018f: ldc.i4.5
IL_0190: ldstr ""19""
IL_0195: stelem.ref
IL_0196: call ""string string.Concat(params string[])""
IL_019b: call ""void System.Console.WriteLine(string)""
IL_01a0: ldc.i4.6
IL_01a1: newarr ""object""
IL_01a6: dup
IL_01a7: ldc.i4.0
IL_01a8: ldstr ""20""
IL_01ad: stelem.ref
IL_01ae: dup
IL_01af: ldc.i4.1
IL_01b0: ldc.i4.s 21
IL_01b2: box ""int""
IL_01b7: stelem.ref
IL_01b8: dup
IL_01b9: ldc.i4.2
IL_01ba: ldloca.s V_0
IL_01bc: constrained. ""char""
IL_01c2: callvirt ""string object.ToString()""
IL_01c7: stelem.ref
IL_01c8: dup
IL_01c9: ldc.i4.3
IL_01ca: ldloca.s V_1
IL_01cc: constrained. ""char""
IL_01d2: callvirt ""string object.ToString()""
IL_01d7: stelem.ref
IL_01d8: dup
IL_01d9: ldc.i4.4
IL_01da: ldloca.s V_0
IL_01dc: constrained. ""char""
IL_01e2: callvirt ""string object.ToString()""
IL_01e7: stelem.ref
IL_01e8: dup
IL_01e9: ldc.i4.5
IL_01ea: ldloca.s V_1
IL_01ec: constrained. ""char""
IL_01f2: callvirt ""string object.ToString()""
IL_01f7: stelem.ref
IL_01f8: call ""string string.Concat(params object[])""
IL_01fd: call ""void System.Console.WriteLine(string)""
IL_0202: ldc.i4.6
IL_0203: newarr ""string""
IL_0208: dup
IL_0209: ldc.i4.0
IL_020a: ldstr ""22""
IL_020f: stelem.ref
IL_0210: dup
IL_0211: ldc.i4.1
IL_0212: ldloca.s V_0
IL_0214: constrained. ""char""
IL_021a: callvirt ""string object.ToString()""
IL_021f: stelem.ref
IL_0220: dup
IL_0221: ldc.i4.2
IL_0222: ldstr ""23""
IL_0227: stelem.ref
IL_0228: dup
IL_0229: ldc.i4.3
IL_022a: ldloca.s V_1
IL_022c: constrained. ""char""
IL_0232: callvirt ""string object.ToString()""
IL_0237: stelem.ref
IL_0238: dup
IL_0239: ldc.i4.4
IL_023a: ldloca.s V_0
IL_023c: constrained. ""char""
IL_0242: callvirt ""string object.ToString()""
IL_0247: stelem.ref
IL_0248: dup
IL_0249: ldc.i4.5
IL_024a: ldloca.s V_1
IL_024c: constrained. ""char""
IL_0252: callvirt ""string object.ToString()""
IL_0257: stelem.ref
IL_0258: call ""string string.Concat(params string[])""
IL_025d: call ""void System.Console.WriteLine(string)""
IL_0262: ret
}
");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册