- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
昨天我发现了一个 article by Christoph Nahr titled ".NET Struct Performance"它针对添加两个点结构(double
元组)的方法对多种语言(C++、C#、Java、JavaScript)进行了基准测试。
事实证明,C++ 版本执行大约需要 1000 毫秒(1e9 次迭代),而 C# 在同一台机器上不能低于 ~3000 毫秒(并且在 x64 中执行更差)。
为了自己测试,我采用了 C# 代码(并稍微简化为仅调用按值传递参数的方法),并在 i7-3610QM 机器(单核 3.1Ghz 加速)、8GB RAM 上运行它,Win8.1,使用 .NET 4.5.2,RELEASE 构建 32 位(x86 WoW64,因为我的操作系统是 64 位)。这是简化版:
public static class CSharpTest
{
private const int ITERATIONS = 1000000000;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Point AddByVal(Point a, Point b)
{
return new Point(a.X + b.Y, a.Y + b.X);
}
public static void Main()
{
Point a = new Point(1, 1), b = new Point(1, 1);
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
}
Point
定义为:
public struct Point
{
private readonly double _x, _y;
public Point(double x, double y) { _x = x; _y = y; }
public double X { get { return _x; } }
public double Y { get { return _y; } }
}
运行它会产生与文章中类似的结果:
Result: x=1000000001 y=1000000001, Time elapsed: 3159 ms
第一次奇怪的观察
由于应该内联该方法,我想知道如果我完全删除结构并将整个结构简单地内联在一起,代码将如何执行:
public static class CSharpTest
{
private const int ITERATIONS = 1000000000;
public static void Main()
{
// not using structs at all here
double ax = 1, ay = 1, bx = 1, by = 1;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
{
ax = ax + by;
ay = ay + bx;
}
sw.Stop();
Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
ax, ay, sw.ElapsedMilliseconds);
}
}
并且得到了几乎相同的结果(在几次重试后实际上慢了 1%),这意味着 JIT-ter 似乎在优化所有函数调用方面做得很好:
Result: x=1000000001 y=1000000001, Time elapsed: 3200 ms
这也意味着基准测试似乎没有衡量任何 struct
性能,实际上似乎只衡量基本的 double
算法(在其他所有内容都被优化之后)。
奇怪的东西
奇怪的部分来了。如果我只是在循环外添加另一个秒表(是的,我在多次重试后将其缩小到这个疯狂的步骤),代码运行快三倍:
public static void Main()
{
var outerSw = Stopwatch.StartNew(); // <-- added
{
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
outerSw.Stop(); // <-- added
}
Result: x=1000000001 y=1000000001, Time elapsed: 961 ms
太可笑了!而且 Stopwatch
不会给我错误的结果,因为我可以清楚地看到它在一秒钟后结束。
谁能告诉我这里可能发生了什么?
(更新)
下面是同一个程序中的两个方法,说明不是JITting的原因:
public static class CSharpTest
{
private const int ITERATIONS = 1000000000;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Point AddByVal(Point a, Point b)
{
return new Point(a.X + b.Y, a.Y + b.X);
}
public static void Main()
{
Test1();
Test2();
Console.WriteLine();
Test1();
Test2();
}
private static void Test1()
{
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Test1: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
private static void Test2()
{
var swOuter = Stopwatch.StartNew();
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
swOuter.Stop();
}
}
输出:
Test1: x=1000000001 y=1000000001, Time elapsed: 3242 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 974 ms
Test1: x=1000000001 y=1000000001, Time elapsed: 3251 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 972 ms
Here is a pastebin. 您需要在 .NET 4.x 上将其作为 32 位版本运行(在代码中进行了几次检查以确保这一点)。
(更新 4)
根据@usr 对@Hans 回答的评论,我检查了两种方法的优化反汇编,它们有很大不同:
这似乎表明差异可能是由于编译器在第一种情况下表现得很滑稽,而不是双字段对齐?
此外,如果我添加两个变量(总偏移量为 8 个字节),我仍然会获得相同的速度提升 - 而且它似乎不再与 Hans Passant 提到的字段对齐有关:
// this is still fast?
private static void Test3()
{
var magical_speed_booster_1 = "whatever";
var magical_speed_booster_2 = "whatever";
{
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
GC.KeepAlive(magical_speed_booster_1);
GC.KeepAlive(magical_speed_booster_2);
}
最佳答案
有一种非常简单的方法可以始终获得程序的“快速”版本。 Project > Properties > Build 选项卡,取消选中“Prefer 32-bit”选项,确保 Platform target 选择是 AnyCPU。
您真的不喜欢 32 位,不幸的是,对于 C# 项目,它始终默认打开。从历史上看,Visual Studio 工具集在 32 位进程上工作得更好,这是微软一直在努力解决的一个老问题。是时候删除该选项了,VS2015 特别解决了 64 位代码的最后几个真正障碍,具有全新的 x64 抖动和对 Edit+Continue 的普遍支持。
废话少说,您发现了变量对齐的重要性。处理器非常关心它。如果一个变量在内存中没有对齐,那么处理器必须做额外的工作来打乱字节以使它们以正确的顺序排列。有两个明显的未对齐问题,一个是字节仍然在单个 L1 缓存行内,这需要额外的周期才能将它们移动到正确的位置。还有一个特别糟糕的,你发现的那个,其中一部分字节在一个缓存行中,一部分在另一个缓存行中。这需要两个单独的内存访问并将它们粘合在一起。慢三倍。
double
和long
类型是 32 位进程中的麻烦制造者。它们的大小为 64 位。并且可以因此得到 4 位的错位,CLR 只能保证 32 位对齐。在 64 位进程中不是问题,所有变量都保证对齐到 8。这也是 C# 语言不能保证它们是原子的根本原因。以及为什么当 double 组的元素超过 1000 个时,它们会分配到大对象堆中。 LOH 提供了 8 的对齐保证。并解释了为什么添加局部变量可以解决问题,对象引用是 4 个字节,因此它将 double 变量移动 4,现在使其对齐。偶然。
32 位 C 或 C++ 编译器会做额外的工作以确保 double 不会错位。这不是一个简单的问题来解决,当一个函数被输入时,堆栈可能会错位,因为唯一的保证是它与 4 对齐。这样一个函数的序言需要做额外的工作才能使其与 8 对齐。同样的技巧在托管程序中不起作用,垃圾收集器非常关心局部变量在内存中的确切位置。必要的,以便它可以发现 GC 堆中的对象仍然被引用。它无法正确处理这样一个移动了 4 的变量,因为在进入该方法时堆栈未对齐。
这也是 .NET 抖动不容易支持 SIMD 指令的潜在问题。它们有更强的对齐要求,处理器也无法自行解决。 SSE2 需要 16 位对齐,AVX 需要 32 位对齐。无法在托管代码中获取。
最后但同样重要的是,还要注意,这使得在 32 位模式下运行的 C# 程序的性能非常不可预测。当您访问作为字段存储在对象中的 double 或 long 时,当垃圾收集器压缩堆时,perf 可能会发生巨大变化。这会移动内存中的对象,这样的字段现在可能会突然错位/对齐。当然非常随机,可能会让人头疼:)
好吧,没有简单的修复,只有一个,64 位代码是 future 。只要 Microsoft 不更改项目模板,就删除抖动强制。也许下一个版本,当他们对 Ryujit 更有信心时。
关于c# - 简单基准测试中奇怪的性能提升,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32114308/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!