您现在的位置是:首页 >学无止境 >【学习积累】Queue 与 ConcurrentQueue性能测试网站首页学无止境
【学习积累】Queue 与 ConcurrentQueue性能测试
在 C# 中,关于队列(Queue)有两种,一种就是我们普通使用的队列,另一种是线程安全的队列 ConcurrentQueue<T> 。
ConcurrentQueue表示线程安全的先进先出 (FIFO) 集合。https://learn.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=netstandard-2.1 这两者在数据结构上,都是先进先出(FIFO)的集合,一般情况下我们都是用的 Queue 这种常规队列就能满足需求。当然,在多线程情况下,如果是用 Queue,因为线程不安全,在线程竞争的时候(多线程入队或多线程出队)就会造成异常,此时我们就会用到 ConcurrentQueue。那么这两者性能差异是如何的呢?
本文将对这两个队列进行一个简单的性能测试,同时讨论一种特殊情况:一个线程入队,一个线程出队时使用 Queue 的情况。
1、单一线程入队+单一线程出队情况
这里我们讨论多线程的特殊情况:假设队列只有一个线程进行入队,同时,只有一个线程进行出队。那么在这种情况下,我们使用线程不安全队列 Queue 会不会有问题?如果在在 Unity 中,写入、读取有耗时操作,会不会出现异常?
这里我们的测试用例很简单:
public static void AddTaskItem()
{
System.Random rand = new System.Random();
for (int i = 0; i < MaxCount; i++)
{
TaskItem item = new TaskItem();
item.Index = i;
TestQueue.Enqueue(item);
Thread.Sleep(rand.Next(0, 5));
}
Debug.Log("全部数据添加完毕!");
}
public class TaskItem
{
public int Index;
public void DoSomeWork(){}// 某耗时函数,此处略去
}
这里我将 MaxCount 设置为 10000,之后在 Unity 主线程进行 Update:
public int CurIndex = 0;
private void Update()
{
int updateCount = TestQueue.Count;
int lastIndex = CurIndex;
//取出当前队列的所有值,并比对;
while (testQueue.Count > 0)
{
var item = testQueue.Dequeue();
if (item.Index != CurIndex)
{
Debug.LogError($"取值错误,应该是:{CurIndex},实际是 :{item.Index}");
}
item.DoSomeWork();
CurIndex++;
}
Debug.Log($"本次取出队列:{CurIndex - lastIndex} / {updateCount}");
}
显然,只要取值错误,即当前取出的值不是在主线程记录的序号(CurIndex),就会抛出错误。不过我进行了多次测试,并没有出现过一次错误,即便是增加了耗时函数,导致每帧取值并不是一开始的值,但仍然不会出现出队入队异常。
所以我们得出结论:
在仅有一个线程入队、一个线程出队的情况下,使用队列 Queue 是不会有异常的。
2、性能测试
这里我们就简单地进行入队出队的性能测试,这里就不贴测试代码了,直接出结论:
整体来看,ConcurrentQueue 的性能开销都是大于 Queue 的,其中:
写入耗时:ConcurrentQueue 约为 Queue 的 1.3 倍。
读取耗时:ConcurrentQueue 约为 Queue 的 6 倍。
同时,随着队列中的数据增多,ConcurrentQueue 的读取耗时将会显著增加。
当然,如果队列中数量不是很多,这两者的差别并不算太大,微秒级别的差异在一般情况下可以无视。同时,队列中有上千万个元素的情况在一般游戏中非常少见(一般队列中有个几百个元素就不得了了),所以不用太在意 ConcurrentQueue 带来的额外性能开销。
同时,本次测试都是单线程的读写,相当于抛弃了 ConcurrentQueue 的优势(多线程安全)来测试,有些许不公。在实际使用时 ConcurrentQueue 一定是在多线程读写的场景,其安全性与性能肯定会显著优于 Queue 。