【问题标题】:Why is array initialization slowing down multithreading?为什么数组初始化会减慢多线程?
【发布时间】:2016-09-28 14:19:25
【问题描述】:

我正在尝试将线程支持添加到在多个维度中散列坐标的方法中。这里的长期目标是包含 FNV1A 哈希,但只要在 Hash 方法中简单地初始化坐标数组,就会出现减速。

我迭代了一百万次,对于 1 个线程,我得到的秒表时间约为 300 毫秒。对于 8 个线程,时间会达到约 6000 毫秒。

这是一个虚假的分享问题吗?如果是这样,我担心哈希会随着填充和偏移量注入数组而恶化。任何有关使用本地数组执行此操作的帮助将不胜感激。非常感谢!

public class Foo : MonoBehaviour {
    #region Fields
    private readonly int iterations = 1000000;
    private readonly int threadNum = 1;
    private int iterationsCompleted = 0;
    #endregion

    void Start () {
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        Multithread();
        stopWatch.Stop();
        UnityEngine.Debug.Log(stopWatch.Elapsed.TotalMilliseconds);
    }

    private void Multithread() {
        for (int i = 0; i < threadNum; i++) {
            Hash hash = new Hash();
            new Thread(() => {
                while (Interlocked.Increment(ref iterationsCompleted) < iterations) {
                    hash.Get(0, 0, 0);
                }
                UnityEngine.Debug.Log("Finished thread");
            }).Start();
        }
        while (iterationsCompleted < iterations);
    }
}

public class Hash {
    #region Fields
    // FNV parameters can be found at http://www.isthe.com/chongo/tech/comp/fnv/#FNV-param
    private const uint _FNVPrime = 16777619;
    private const uint _FNVOffset = 2166136261;
    private const uint _FNVMask8 = (1<<8)-1;
    #endregion

    #region Class Methods
    private static uint FNV1A(uint[] data) {
        uint hash = _FNVOffset;
        int dataSize = data.Length * sizeof(UInt32);
        byte[] byteArray = new byte[dataSize];
        Buffer.BlockCopy(data, 0, byteArray, 0, dataSize);
        for (int i = 0; i < dataSize; i++) {
            hash = hash ^ byteArray[i];
            hash = hash * _FNVPrime;
        }
        return hash;
    }

    public uint Get(int x, int y, uint seed) {
        uint[] data = new uint[3] { (uint)x, (uint)y, seed };
        //return FNV1A(data);
        return 0;
    }
    #endregion
}

【问题讨论】:

  • 我不确定这一点,因为我还没有统一使用线程,但我认为 MonoBehaviour 派生类会出现多线程问题(至少是从它继承的函数)。
  • 一定是分配新数组的开销。您有两个选择:使用 FNV1A 和固定数量的整数而不是数组,或者预分配一个数组,每个线程一个。
  • @TamasHegedus 请注意,对于一百万次迭代和 1 个线程,我的秒表时间接近约 300 毫秒。碰撞到 8 将时间推到 ~6000 毫秒。这似乎不像数组开销。我也会将该信息添加到帖子中。
  • @RichardHorne 我想我找到了问题所在。更新您的完整代码,以便我进行测试。
  • threadNum,iterationsCompletediterations的值是多少?

标签: c# arrays multithreading unity3d hash


【解决方案1】:

内存分配似乎是这里的问题。我创建了两个数组static,然后在函数之外分配了一次内存。每次访问它们时我locked 两个数组以确保另一个Thread 没有访问它们。即使使用了lock 关键字,也不确定我的代码有多安全。

无相关问题

while (iterationsCompleted &lt; iterations); 在主 Thread 上调用。这不好,并且每次调用 Multithread() 并仍在运行时都会暂时冻结 Unity。我在上面添加了另一个Thread,它将开始调用whenMultithread()。因此,现在正在从这个新的 Thread 而不是主 Thread 调用其他线程。

我的电脑上的测试结果

原码

1 thread = 454.3515
8 threads = 655.008

修改后的代码

1 thread = 296.794
8 threads = 107.8898

8 线程的性能比6x 提高了更多。 所有测试均在代码中包含return FNV1A(data); 并从代码中删除/注释return 0; 完成。

现在,您的工作是确保您获得的数据/哈希值符合您的预期。

public class Foo : MonoBehaviour
{
    #region Fields
    private readonly int iterations = 1000000;
    private readonly int threadNum = 1;
    private int iterationsCompleted = 0;
    #endregion

    void Start()
    {
        Multithread();
    }

    private void Multithread()
    {
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();

        //Start new Thread so that while (iterationsCompleted < iterations) ; will not run in the main loop
        new Thread(() =>
          {

              for (int i = 0; i < threadNum; i++)
              {
                  Hash hash = new Hash();
                  new Thread(() =>
                  {
                      while (Interlocked.Increment(ref iterationsCompleted) < iterations)
                      {
                          hash.Get(0, 0, 0);
                      }
                      UnityEngine.Debug.Log("Finished thread");
                  }).Start();
              }
              while (iterationsCompleted < iterations) ;
              stopWatch.Stop();
              UnityEngine.Debug.Log(stopWatch.Elapsed.TotalMilliseconds);

          }).Start();
    }
}

public class Hash
{
    #region Fields
    // FNV parameters can be found at http://www.isthe.com/chongo/tech/comp/fnv/#FNV-param
    private const uint _FNVPrime = 16777619;
    private const uint _FNVOffset = 2166136261;
    private const uint _FNVMask8 = (1 << 8) - 1;
    private const int FIXEDSIZE = 3;
    private readonly System.Object locker = new System.Object();
    #endregion

    #region Class Methods
    private static uint FNV1A(uint[] data)
    {
        uint hash = _FNVOffset;

        Buffer.BlockCopy(data, 0, byteArray, 0, byteArray.Length);

        for (int i = 0; i < byteArray.Length; i++)
        {
            hash = hash ^ byteArray[i];
            hash = hash * _FNVPrime;
        }
        return hash;
    }

    static byte[] byteArray = new byte[FIXEDSIZE * sizeof(UInt32)];
    static uint[] data = new uint[3] { (uint)0, (uint)0, 0 };

    public uint Get(int x, int y, uint seed)
    {
        lock (locker)
        {
            data[0] = (uint)x;
            data[1] = (uint)y;
            data[2] = (uint)seed;
            return FNV1A(data);
        }
    }
    #endregion
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    相关资源
    最近更新 更多