是否可以在C#中进行内联来优化字符串连接?

时间:2016-09-19 19:54:16

标签: c# c-preprocessor inlining

假设你有这样的C#代码:

const string ABC = "ABC";
const string XYZ = "XYZ";

var result = ABC + ":" + XYZ;

这样的代码由编译器优化,因此在结果变量中有一个常量字符串文字“ABC:XYZ”,而不是在运行时进行3个字符串的实际连接。

现在我想介绍一个辅助函数,它将隐藏用户的连接(使代码更健壮,更不容易出错):

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static string Combine(string first, string second) => first + ":" + second;

const string ABC = "ABC";
const string XYZ = "XYZ";

var result = Combine(ABC, XYZ);

在这种情况下,我看到该方法实际上是在调用方法中内联的,但在反汇编中我可以清楚地看到它在运行时调用string.Concat(string a, string b, string c),而不是有一个常量字符串文字“ABC:XYZ ”

我理解在编译期间速度和质量之间存在平衡,但有没有让Combine方法作为C ++宏工作以避免在运行时冗余字符串连接的技巧?

P.S。引入一次计算得到的静态只读字符串不是问题的答案。

2 个答案:

答案 0 :(得分:3)

了解MethodImplOptions.AggressiveInlining提示及其含义/有用之处非常有用。

我的理解是方法内联(程序员暗示/希望是否积极地完成) - 是关于呼叫站点(即,在您的情况下,关于“由谁” /其中“你的组合被调用”,但不是关于否则构成(可能内联)方法的主体。

换句话说,当你重构你的表达时

... ABC + ":" + XYZ ...

(这显然是恒定折叠优化的“完美”/名义候选者),

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static string Combine(string first, string second) => first + ":" + second

然后

(在通话现场)

... Combine(ABC, XYZ) ...

你真的“告诉”了与编译器完全不同的东西:

“我想要一个带有两个字符串并返回第三个字符串的静态方法,我想在这个调用网站上调用它,哦,请尝试为我内联。”

因为在你的情况下,Combine显然是一个纯粹的非递归函数,它对调用堆栈没有任何作用,无论何时用实际参数调用它都不能“就地”重写(不管是那些常量),它确实是内联调用是可行的 - 但是仍然没有说明其他什么可以进行其他优化 over Combine方法的 body 本身,这里相当于(在lambda desugaring之后)

return first + ":" + second;

(因此编译为

return string.Concat(first, second);

你注意到了)

毕竟,如果你的代码的其他部分有一个依赖于反映(通过System.Reflection的功能)的逻辑怎么办?它仍然需要在某个地方被声明为成员并且附加一个身体,不是吗? (再次,无论是否可以,“作为奖励”,在这里或那里内联,等等)

从那里开始,“附着在它身上的身体”意味着必须有一个,恰好一个,用CIL / MSIL等表示。

这就是我在你的Combine方法上看到[MethodImpl(MethodImplOptions.AggressiveInlining)]的原因,仅仅是提示,“嘿,拜托,让我们尽可能地避免单独的,愚蠢的堆栈框架来调用Combine。 “ - 除此之外没什么。

答案 1 :(得分:0)

可以使用编译时编织来完成此操作。这里的想法是你会找到你的方法的所有调用站点(不要内联它们!)并在编译时用ldtoken操作码替换它们,并将连接的字符串作为操作数。

这些工具将有助于实现您的目标。

•PostSharp •Aspect.NET。依靠凤凰城。 •AspectDNG。依靠塞西尔。 •ComposeStar / StarLight •Gripper Loom.NET •Phx.Morph /巫术。依靠凤凰城。