我将2D字段的位存储在5个无符号长数组中。
我要争取最好的表现。
我正在C#中工作,但是我尝试通过在C++中实现我的类来设置基准。

这里的问题是,C#实现大约需要10秒才能完成,而C++大约需要1秒才能使其速度比快10倍。 C++是VS2015中的x64构建。 C#在x64 VS2015 .NET 4.6中。当然都是在发布中。

编辑:在稍微优化C#代码后,相对于C++ 1.3秒,它仍然需要7到8秒。

注意:x86中的 C++大约需要6秒钟才能完成。我在64位计算机上运行代码。

问题:是什么使C++更快?有没有一种方法可以优化C#代码,使其至少类似地快? (也许是一些不安全的魔法?)

让我感到困惑的是,我们所谈论的只是遍历数组和按位运算。它不应该与C++差不多吗?

示例代码:
在实现中有两个简单的功能。 Left()和Right()将整个字段左移1位。在多头之间适当地带有一点点。

C++

#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;

class BitField
{
private:
    unsigned long long LEFTMOST_BIT = 0x8000000000000000;
    unsigned long long RIGHTMOST_BIT = 1;

public:
    unsigned long long Cells_l[5];
    BitField()
    {
        for (size_t i = 0; i < 5; i++)
        {
            Cells_l[i] = rand(); // Random initialization
        }
    }
    void Left()
    {
        unsigned long long carry = 0;
        unsigned long long nextCarry = 0;
        for (int i = 0; i < 5; i++)
        {
            nextCarry = (Cells_l[i] & LEFTMOST_BIT) >> 63;
            Cells_l[i] = Cells_l[i] << 1 | carry;
            carry = nextCarry;
        }
    }
    void Right()
    {
        unsigned long long carry = 0;
        unsigned long long nextCarry = 0;
        for (int i = 4; i >= 0; i--)
        {
            nextCarry = (Cells_l[i] & RIGHTMOST_BIT) << 63;
            Cells_l[i] = Cells_l[i] >> 1 | carry;
            carry = nextCarry;
        }
    }
};

int main()
{
    BitField bf;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    for (int i = 0; i < 100000000; i++)
    {
        bf.Left();
        bf.Left();
        bf.Left();
        bf.Right();
        bf.Right();
        bf.Left();
        bf.Right();
        bf.Right();
    }
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    auto duration = duration_cast<milliseconds>(t2 - t1).count();

    cout << "Time: " << duration << endl << endl;
    // Print to avoid compiler optimizations
    for (size_t i = 0; i < 5; i++)
    {
        cout << bf.Cells_l[i] << endl;
    }

    return 0;
}

C#
using System;
using System.Diagnostics;

namespace TestCS
{
    class BitField
    {
        const ulong LEFTMOST_BIT = 0x8000000000000000;
        const ulong RIGHTMOST_BIT = 1;

        static Random rnd = new Random();

        ulong[] Cells;

        public BitField()
        {
            Cells = new ulong[5];
            for (int i = 0; i < 5; i++)
            {
                Cells[i] = (ulong)rnd.Next(); // Random initialization
            }
        }

        public void Left()
        {
            ulong carry = 0;
            ulong nextCarry = 0;
            for (int i = 0; i < 5; i++)
            {
                nextCarry = (Cells[i] & LEFTMOST_BIT) >> 63;
                Cells[i] = Cells[i] << 1 | carry;
                carry = nextCarry;
            }
        }
        public void Right()
        {
            ulong carry = 0;
            ulong nextCarry = 0;
            for (int i = 4; i >= 0; i--)
            {
                nextCarry = (Cells[i] & RIGHTMOST_BIT) << 63;
                Cells[i] = Cells[i] >> 1 | carry;
                carry = nextCarry;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BitField bf = new BitField();
            Stopwatch sw = new Stopwatch();

            // Call to remove the compilation time from measurements
            bf.Left();
            bf.Right();

            sw.Start();
            for (int i = 0; i < 100000000; i++)
            {
                bf.Left();
                bf.Left();
                bf.Left();
                bf.Right();
                bf.Right();
                bf.Left();
                bf.Right();
                bf.Right();
            }
            sw.Stop();

            Console.WriteLine($"Done in: {sw.Elapsed.TotalMilliseconds.ToString()}ms");
        }
    }
}

编辑:修复了示例代码中的“nextCarry”错字。

最佳答案

部分差异可能是由于两个版本之间的代码差异所致-您没有在C++ nextCarry和C#Left中都未分配给Right,但是在示例中,这些可能是拼写错误。

您可能要看一下两者的反汇编以了解不同之处,但是主要是由于C++编译器有更多时间花在优化代码上。在这种情况下,它将展开循环,内联所有函数调用(包括构造函数),并将Cells_l中的所有内容推入寄存器。因此,存在一个使用寄存器而不访问内存的大循环。

我没有看过C#编译后的输出,但我怀疑它是否可以完成任何工作。

另外,如注释中所述,将C#代码中的所有Cells.Length调用替换为5(就像C++代码中的调用一样)。

10-05 19:25