- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
我正在生成一个表达式树,它将属性从源对象映射到目标对象,然后将其编译为 Func<TSource, TDestination, TDestination>
并执行。
这是结果 LambdaExpression
的调试 View :
.Lambda #Lambda1<System.Func`3[MemberMapper.Benchmarks.Program+ComplexSourceType,MemberMapper.Benchmarks.Program+ComplexDestinationType,MemberMapper.Benchmarks.Program+ComplexDestinationType]>(
MemberMapper.Benchmarks.Program+ComplexSourceType $right,
MemberMapper.Benchmarks.Program+ComplexDestinationType $left) {
.Block(
MemberMapper.Benchmarks.Program+NestedSourceType $Complex$955332131,
MemberMapper.Benchmarks.Program+NestedDestinationType $Complex$2105709326) {
$left.ID = $right.ID;
$Complex$955332131 = $right.Complex;
$Complex$2105709326 = .New MemberMapper.Benchmarks.Program+NestedDestinationType();
$Complex$2105709326.ID = $Complex$955332131.ID;
$Complex$2105709326.Name = $Complex$955332131.Name;
$left.Complex = $Complex$2105709326;
$left
}
}
清理后会是:
(left, right) =>
{
left.ID = right.ID;
var complexSource = right.Complex;
var complexDestination = new NestedDestinationType();
complexDestination.ID = complexSource.ID;
complexDestination.Name = complexSource.Name;
left.Complex = complexDestination;
return left;
}
这是将属性映射到这些类型的代码:
public class NestedSourceType
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ComplexSourceType
{
public int ID { get; set; }
public NestedSourceType Complex { get; set; }
}
public class NestedDestinationType
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ComplexDestinationType
{
public int ID { get; set; }
public NestedDestinationType Complex { get; set; }
}
执行此操作的手动代码是:
var destination = new ComplexDestinationType
{
ID = source.ID,
Complex = new NestedDestinationType
{
ID = source.Complex.ID,
Name = source.Complex.Name
}
};
问题是当我编译 LambdaExpression
并对结果 delegate
进行基准测试它比手动版本慢大约 10 倍。我不知道为什么会这样。整个想法是在没有单调乏味的手动映射的情况下实现最高性能。
当我从 Bart de Smet 的 blog post 中获取代码时在这个主题上,将计算素数的手动版本与编译表达式树进行基准测试,它们在性能上完全相同。
当 LambdaExpression
的调试 View 时,是什么导致了这种巨大的差异?看起来像您期望的那样?
编辑
根据要求,我添加了我使用的基准:
public static ComplexDestinationType Foo;
static void Benchmark()
{
var mapper = new DefaultMemberMapper();
var map = mapper.CreateMap(typeof(ComplexSourceType),
typeof(ComplexDestinationType)).FinalizeMap();
var source = new ComplexSourceType
{
ID = 5,
Complex = new NestedSourceType
{
ID = 10,
Name = "test"
}
};
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
Foo = new ComplexDestinationType
{
ID = source.ID + i,
Complex = new NestedDestinationType
{
ID = source.Complex.ID + i,
Name = source.Complex.Name
}
};
}
sw.Stop();
Console.WriteLine(sw.Elapsed);
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
Foo = mapper.Map<ComplexSourceType, ComplexDestinationType>(source);
}
sw.Stop();
Console.WriteLine(sw.Elapsed);
var func = (Func<ComplexSourceType, ComplexDestinationType, ComplexDestinationType>)
map.MappingFunction;
var destination = new ComplexDestinationType();
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
Foo = func(source, new ComplexDestinationType());
}
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
第二个比手动执行慢是可以理解的,因为它涉及字典查找和一些对象实例化,但第三个应该和它被调用的原始委托(delegate)和来自 Delegate
的转换一样快。至 Func
发生在循环之外。
我也尝试将手动代码包装在一个函数中,但我记得它并没有产生明显的区别。无论哪种方式,函数调用都不应该增加一个数量级的开销。
我还进行了两次基准测试,以确保 JIT 没有干扰。
编辑
您可以在此处获取此项目的代码:
https://github.com/JulianR/MemberMapper/
我使用 Bart de Smet 的博客文章中描述的 Sons-of-Strike 调试器扩展来转储动态方法生成的 IL:
IL_0000: ldarg.2
IL_0001: ldarg.1
IL_0002: callvirt 6000003 ComplexSourceType.get_ID()
IL_0007: callvirt 6000004 ComplexDestinationType.set_ID(Int32)
IL_000c: ldarg.1
IL_000d: callvirt 6000005 ComplexSourceType.get_Complex()
IL_0012: brfalse IL_0043
IL_0017: ldarg.1
IL_0018: callvirt 6000006 ComplexSourceType.get_Complex()
IL_001d: stloc.0
IL_001e: newobj 6000007 NestedDestinationType..ctor()
IL_0023: stloc.1
IL_0024: ldloc.1
IL_0025: ldloc.0
IL_0026: callvirt 6000008 NestedSourceType.get_ID()
IL_002b: callvirt 6000009 NestedDestinationType.set_ID(Int32)
IL_0030: ldloc.1
IL_0031: ldloc.0
IL_0032: callvirt 600000a NestedSourceType.get_Name()
IL_0037: callvirt 600000b NestedDestinationType.set_Name(System.String)
IL_003c: ldarg.2
IL_003d: ldloc.1
IL_003e: callvirt 600000c ComplexDestinationType.set_Complex(NestedDestinationType)
IL_0043: ldarg.2
IL_0044: ret
我不是 IL 方面的专家,但这看起来很简单,而且正是您所期望的,不是吗?那为什么这么慢?没有奇怪的装箱操作,没有隐藏的实例化,什么都没有。它与上面的表达式树不完全相同,因为还有一个 null
检查right.Complex
现在。
这是手动版的代码(通过Reflector获得):
L_0000: ldarg.1
L_0001: ldarg.0
L_0002: callvirt instance int32 ComplexSourceType::get_ID()
L_0007: callvirt instance void ComplexDestinationType::set_ID(int32)
L_000c: ldarg.0
L_000d: callvirt instance class NestedSourceType ComplexSourceType::get_Complex()
L_0012: brfalse.s L_0040
L_0014: ldarg.0
L_0015: callvirt instance class NestedSourceType ComplexSourceType::get_Complex()
L_001a: stloc.0
L_001b: newobj instance void NestedDestinationType::.ctor()
L_0020: stloc.1
L_0021: ldloc.1
L_0022: ldloc.0
L_0023: callvirt instance int32 NestedSourceType::get_ID()
L_0028: callvirt instance void NestedDestinationType::set_ID(int32)
L_002d: ldloc.1
L_002e: ldloc.0
L_002f: callvirt instance string NestedSourceType::get_Name()
L_0034: callvirt instance void NestedDestinationType::set_Name(string)
L_0039: ldarg.1
L_003a: ldloc.1
L_003b: callvirt instance void ComplexDestinationType::set_Complex(class NestedDestinationType)
L_0040: ldarg.1
L_0041: ret
看起来和我一模一样..
编辑
我点击了 Michael B 关于此主题的回答中的链接。我尝试在已接受的答案中实现该技巧并且成功了!如果你想要一个技巧的总结:它创建一个动态程序集并将表达式树编译成该程序集中的静态方法,并且出于某种原因,速度提高了 10 倍。这样做的缺点是我的基准类是内部的(实际上,公共(public)类嵌套在内部类中)并且当我试图访问它们时抛出异常,因为它们不可访问。似乎没有解决方法,但我可以简单地检测引用的类型是否是内部类型,并决定使用哪种编译方法。
但仍然困扰我的是为什么质数方法在性能上与编译表达式树相同。
再一次,我欢迎任何人在该 GitHub 存储库中运行代码以确认我的测量结果并确保我没有发疯 :)
最佳答案
听到这么大的声音真是太奇怪了。有几件事需要考虑。首先,VS 编译代码应用了不同的属性,这些属性可能会影响抖动以不同方式进行优化。
您是否在这些结果中包括了编译委托(delegate)的第一次执行?你不应该,你应该忽略任一代码路径的第一次执行。您还应该将普通代码转换为委托(delegate),因为委托(delegate)调用比调用实例方法稍慢,而实例方法又比调用静态方法慢。
至于其他更改,编译委托(delegate)有一个此处未使用的闭包对象,但意味着这是一个目标委托(delegate),可能执行速度稍慢。您会注意到编译后的委托(delegate)有一个目标对象,并且所有参数都向下移动了一个。
由 lcg 生成的方法也被认为是静态的,由于寄存器切换业务,当编译为委托(delegate)时,它们往往比实例方法慢。 (Duffy 说“this”指针在 CLR 中有一个保留寄存器,当您有一个静态委托(delegate)时,它必须转移到另一个寄存器,这会产生轻微的开销)。最后,运行时生成的代码似乎比 VS 生成的代码运行速度稍慢。在运行时生成的代码似乎有额外的沙盒,并且是从不同的程序集启动的(如果你不相信我,请尝试使用类似 ldftn 操作码或 calli 操作码的东西,那些反射。发出的委托(delegate)将编译但不会让你实际执行它们) 调用最小的开销。
你也在 Release模式下运行吗?有一个类似的话题,我们在这里查看了这个问题: Why is Func<> created from Expression<Func<>> slower than Func<> declared directly?
编辑:另请参阅我的回答: DynamicMethod is much slower than compiled IL function
要点在于,您应该将以下代码添加到计划创建和调用运行时生成代码的程序集中。
[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityTransparent]
[assembly: SecurityRules(SecurityRuleSet.Level2,SkipVerificationInFullTrust=true)]
并且始终使用内置委托(delegate)类型或来自带有这些标志的程序集的委托(delegate)类型。
原因是匿名动态代码托管在始终标记为部分信任的程序集中。通过允许部分信任的调用者,您可以跳过部分握手。透明度意味着您的代码不会提高安全级别(即缓慢的行为),最后真正的技巧是调用托管在标记为跳过验证的程序集中的委托(delegate)类型。 Func<int,int>#Invoke
是完全可信的,所以不需要验证。这将为您提供从 VS 编译器生成的代码的性能。如果不使用这些属性,您将看到 .NET 4 中的开销。您可能认为 SecurityRuleSet.Level1 是避免这种开销的好方法,但切换安全模型的成本也很高。
简而言之,添加这些属性,然后您的微循环性能测试将运行大致相同。
关于c# - 编译委托(delegate)表达式的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5053032/
我正在用 yacc/bison 编写一个简单的计算器。 表达式的语法看起来有点像这样: expr : NUM | expr '+' expr { $$ = $1 + $3; } | expr '-'
我开始学习 lambda 表达式,并在以下情况下遇到了以下语句: interface MyNumber { double getValue(); } MyNumber number; nu
这两个 Linq 查询有什么区别: var result = ResultLists().Where( c=> c.code == "abc").FirstOrDefault(); // vs. va
如果我们查看 draft C++ standard 5.1.2 Lambda 表达式 段 2 说(强调我的 future ): The evaluation of a lambda-expressio
我使用的是 Mule 4.2.2 运行时、studio 7.5.1 和 Oracle JDK 1.8.0_251。 我在 java 代码中使用 Lambda 表达式,该表达式由 java Invoke
我是 XPath 的新手。我有网页的html源 http://london.craigslist.co.uk/com/1233708939.html 现在我想从上面的页面中提取以下数据 完整日期 电子
已关闭。这个问题是 off-topic 。目前不接受答案。 想要改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 已关闭10 年前。 Improve th
我将如何编写一个 Cron 表达式以在每天上午 8 点和下午 3:30 触发?我了解如何创建每天触发一次的表达式,而不是在多个设定时间触发。提前致谢 最佳答案 你应该只使用两行。 0 8 * * *
这个问题已经有答案了: What do 3 dots next to a parameter type mean in Java? (9 个回答) varargs and the '...' argu
我是 python 新手,在阅读 BeautifulSoup 教程时,我不明白这个表达式“[x for x in titles if x.findChildren()][:-1]”我不明白?你能解释一
(?:) 这是一个有效的 ruby 正则表达式,谁能告诉我它是什么意思? 谢谢 最佳答案 正如其他人所说,它被用作正则表达式的非捕获语法,但是,它也是正则表达式之外的有效 ruby 语法。 在
这个问题在这里已经有了答案: Why does ++[[]][+[]]+[+[]] return the string "10"? (10 个答案) 关闭 8 年前。 谁能帮我处理这个 JavaSc
这个问题在这里已经有了答案: What is the "-->" operator in C++? (29 个答案) Java: Prefix/postfix of increment/decrem
这个问题在这里已经有了答案: List comprehension vs. lambda + filter (16 个答案) 关闭 10 个月前。 我不确定我是否需要 lambda 或其他东西。但是,
C 中的 assert() 函数工作原理对我来说就像一片黑暗的森林。根据这里的答案https://stackoverflow.com/a/1571360 ,您可以使用以下构造将自定义消息输出到您的断言
在this页,John Barnes 写道: If the conditional expression is the argument of a type conversion then effec
我必须创建一个调度程序,它必须每周从第一天上午 9 点到第二天晚上 11 点 59 分运行 2 天(星期四和星期五)。为此,我需要提供一个 cron 表达式。 0-0 0-0 9-23 ? * THU
我正在尝试编写一个 Linq 表达式来检查派生类中的属性,但该列表由来自基类的成员组成。下面的示例代码。以“var list”开头的 Process 方法的第二行无法编译,但我不确定应该使用什么语法来
此 sed 表达式将输入字符串转换为两行输出字符串。两条输出行中的每一行都由输入的子串组成。第一行需要转换成大写: s:random_stuff\(choice1\|choice2\){\([^}]*
我正在使用 Quartz.Net 在我的应用程序中安排我的工作。我只是想知道是否可以为以下场景构建 CRON 表达式: Every second between 2:15AM and 5:20AM 最
我是一名优秀的程序员,十分优秀!