C++ 数组与 C# ptr 速度混淆

C++ array vs C# ptr speed confusion

提问人:Daniel Bencik 提问时间:2/26/2016 更新时间:11/17/2020 访问量:581

问:

我正在将高性能 C++ 应用程序重写为 C#。C# 应用程序明显比 C++ 原始应用程序慢。分析告诉我,C# 应用程序在访问数组元素上花费的时间最多。因此,我创建了一个简单的数组访问基准。我得到的结果与其他人进行类似比较的结果完全不同。

C++代码:

#include <limits>
#include <stdio.h>
#include <chrono>
#include <iostream>

using namespace std;
using namespace std::chrono;

int main(void)
{
    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    int xRepLen = 100 * 1000;
    int xRepCount = 1000;

    unsigned short * xArray = new unsigned short[xRepLen];
    for (int xIdx = 0; xIdx < xRepLen; xIdx++)
        xArray[xIdx] = xIdx % USHRT_MAX;

    int * xResults = new int[xRepLen];

    for (int xRepIdx = 0; xRepIdx < xRepCount; xRepIdx++)
    {

        // in each repetition, find the first value, that surpasses xArray[xIdx] + 25 - i.e. we will perform 25 searches
        for (int xIdx = 0; xIdx < xRepLen; xIdx++)
        {
            unsigned short xValToBreach = (xArray[xIdx] + 25) % USHRT_MAX;
            xResults[xIdx] = 0;

            for (int xIdx2 = xIdx + 1; xIdx2 < xRepLen; xIdx2++)
            if (xArray[xIdx2] >= xValToBreach)
            {
                xResults[xIdx] = xIdx2; break;
            }

            if (xResults[xIdx] == 0)
                xResults[xIdx] = INT_MAX;
        }
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t2 - t1).count();
    cout << "Elasped miliseconds " << duration;
    getchar();
}

C# 代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace arrayBenchmarkCs
{
    class Program
    {
        public static void benchCs()
        {
            unsafe
            {
                int xRepLen = 100 * 1000;
                int xRepCount = 1000;

                ushort[] xArr = new ushort[xRepLen];
                for (int xIdx = 0; xIdx < xRepLen; xIdx++)
                    xArr[xIdx] = (ushort)(xIdx % 0xffff);

                int[] xResults = new int[xRepLen];

                Stopwatch xSw = new Stopwatch(); xSw.Start();
                fixed (ushort * xArrayStart = & xArr [0])
                {
                    for (int xRepIdx = 0; xRepIdx < xRepCount; xRepIdx++)
                    {

                        // in each repetition, go find the first value, that surpasses xArray[xIdx] + 25 - i.e. we will perform 25 searches
                        ushort * xArrayEnd = xArrayStart + xRepLen;
                        for (ushort* xPtr = xArrayStart; xPtr != xArrayEnd; xPtr++)
                        {
                            ushort xValToBreach = (ushort)((*xPtr + 25) % 0xffff);
                            int xResult = -1;
                            for (ushort * xPtr2 = xPtr + 1; xPtr2 != xArrayEnd; xPtr2++)
                                if ( *xPtr2  >= xValToBreach)
                                {
                                    xResult = (int)(xPtr2 - xArrayStart);
                                    break;
                                }

                            if (xResult == -1)
                                xResult = int.MaxValue;

                            // save result
                            xResults[xPtr - xArrayStart] = xResult;
                        }
                    }
                }   // fixed

                xSw.Stop();

                Console.WriteLine("Elapsed miliseconds: " + (xSw.ElapsedMilliseconds.ToString("0"));
            }
        }

        static void Main(string[] args)
        {
            benchCs();
            Console.ReadKey();
        }
    }
}

在我的工作计算机(i7-3770)上,C++版本比C#版本快约2倍。在我的家用电脑(i7-5820K)上,C++比C#版本快1.5倍。两者都以发布为单位进行度量。我希望通过在 C# 中使用指针,可以避免数组边界检查,并且在两种语言中的性能相同。

所以我的问题如下:

  • 回家了,其他人发现 C# 的速度与 C++ 相同?
  • 如果不通过指针,如何才能将 C# 性能提高到 C++ 级别?
  • 在不同计算机上进行不同加速的驱动因素可能是什么?

任何提示都非常感谢, 丹尼尔

C# C++ 数组 性能

评论

0赞 xanatos 2/26/2016
我希望您在没有调试器的情况下执行基准测试(在 Visual Studio 中为 CTRL+F5 而不是 F5)
0赞 Daniel Bencik 2/26/2016
@xanatos:是的。谢谢你的捷径。
0赞 xanatos 2/26/2016
ushort * xArrayEnd = xArrayStart + xRepLen;您可以将其移出循环for
1赞 Daniel Bencik 2/26/2016
@xanatos:事实上,性能差异保持不变。
4赞 YSC 2/26/2016
您链接的基准测试的作者看起来偏向于 C# 而不是 C++。再加上他不分享基准测试代码的事实看起来很可疑......最后,不检查边界。 确实,他一直都错了。std::vector::operator[]()std::vector::at()

答:

0赞 Richard Irons 2/26/2016 #1

你不会得到这种硬核数字处理到C++的速度。使用指针算术和不安全代码可以让你在某种程度上实现目标(如果你删除不安全和固定的部分,它的速度几乎又慢了一半)。C# 不会编译为本机代码,并且它运行的代码充满了额外的检查和内容。

如果你愿意这样做,那么真的没有什么能阻止你将 C++ 性能关键型内容编码到混合模式程序集中,并从 C# 粘附代码中调用它。unsafe

评论

0赞 Daniel Bencik 2/26/2016
谢谢你为我指明了这个方向。我读过的(少量)文章指出,运行时从托管代码切换到非托管代码并返回很慢。根据你的经验,真的是这样吗?
1赞 Richard Irons 2/27/2016
这并不是说做一次需要很长时间,但是在一个循环中做很多次会很糟糕。理想情况下,你用 C# 准备好一切,然后把所有东西交给 C++,它可以完成所有的循环和数学运算。然后,它将这些结果打包回去,您可以回到托管世界的所有好处,快速完成计算。
0赞 Krzysztof Niedziela 11/17/2020 #2

C++ 代码的工作方式与 C# 不同。内部循环是不同的。有 4 个内存操作 xResults[xIdx],而 c# 中只有 1 个。

令我震惊的是,C# 代码的性能在很大程度上取决于框架版本。 更有趣的是,.net core 3.1 上的 C# 比 C++ 高出 5%。使用其他框架,我检查了 C# 比 C++ 慢 30-50%

评论

0赞 Janosimas 11/17/2020
您是否使用与问题相同的代码?你试过不同的长度吗?你能展示一些详细的结果吗?
1赞 Krzysztof Niedziela 11/20/2020
不,我必须更改 C++ 代码才能像 c# 一样工作。我没有尝试不同的长度,但我尝试了更多的重复 - 相同的结果。
1赞 Krzysztof Niedziela 11/20/2020
更改片段: ' 无符号短 xValToBreach = (xPtr + 25) % USHRT_MAX;' xResult = -1; ' for (unsigned short xPtr2 = xPtr + 1; xPtr2 != xArrayEnd; ++xPtr2)' ' if (*xPtr2 >= xValToBreach)' ' {' ' xResult = (int)(xPtr2 - xArray); break;' ' }' ' if (xResult == -1)' ' xResult = INT_MAX;“ ' xResults[xPtr - xArray] = xResult;' ' }' ' }'for (unsigned short* xPtr = xArray; xPtr != xArrayEnd; ++xPtr){