假设你有这样的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。引入一次计算得到的静态只读字符串不是问题的答案。
答案 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 /巫术。依靠凤凰城。