Rumah  >  Artikel  >  Tutorial sistem  >  Kepentingan komponen pemasa dalam perniagaan permainan dan cara melaksanakannya

Kepentingan komponen pemasa dalam perniagaan permainan dan cara melaksanakannya

王林
王林asal
2024-07-18 11:17:12750semak imbas

Kepentingan komponen pemasa dalam perniagaan permainan dan cara melaksanakannya

Pemasa ialah komponen yang agak biasa. Setakat pelayan berkenaan, tahap rangka kerja perlu menggunakan pemasa untuk tamat masa sesi, dan peringkat aplikasi perlu menggunakan pemasa untuk mengendalikan beberapa logik perniagaan berkaitan masa. Untuk perniagaan seperti permainan yang memerlukan sejumlah besar pemasa, komponen pemasa yang mudah dan cekap adalah penting.

Pelaksanaan komponen pemasa boleh dibahagikan kepada dua bahagian:

Bahagian pertama agak mudah, dan terdapat pelbagai kaedah pelaksanaan, tetapi ia pada asasnya berkaitan dengan bahasa, jadi ia bukan fokus artikel ini. Konsep konkrit yang dipanggil seolah-olah merujuk kepada cara pengguna menggunakannya.

[Faedah Artikel] Editor telah memuat naik beberapa buku pembelajaran dan bahan video yang saya rasa lebih baik dalam fail kumpulan Jika anda memerlukannya, anda boleh menyertai kumpulan [977878001] untuk mendapatkannya. ! ! Didatangkan dengan pakej maklumat kernel tambahan bernilai 699 (termasuk tutorial video, e-buku, projek praktikal dan kod)

应用定时器程序-1_linux 应用定时器_应用定时器设计电子钟

Kereta terus maklumat kernel: Laluan pembelajaran teknologi kod sumber kernel Linux + maklumat kod tutorial video

Belajar kereta api ekspres (pendaftaran percuma di Tencent Classroom): kod sumber kernel Linux/penalaan memori video/sistem fail/pengurusan proses/pemandu peranti/timbunan kontrak rangkaian

Bahagian kedua sebenarnya memerlukan lebih banyak kod daripada bahagian pertama, dan kaedah pelaksanaannya sangat terhad.

Tujuan model ini adalah mudah untuk mencari graduan yang telah mempelajari struktur data untuk menulisnya, dan tidak akan ada pepijat. Kerumitan masa tambah ialah n(lgn), dan kerumitan masa tamat masa juga n(lgn).

Namun, anggap sistem perniagaan kami menghadapi permintaan sedemikian: mendaftarkan sejumlah besar pemasa dalam tempoh yang singkat yang akan tamat dalam tempoh yang singkat. Malah, pelaksanaan timbunan minimum agak memalukan.

Merujuk kepada teks di bawah, Xiaoxiao akan memperkenalkan cara kami melaksanakan pemasa gaya kernel Linux dalam lapisan aplikasi. Bahasanya ialah C# sebagai contoh.

Untuk membuat perbandingan prestasi, kita mesti terlebih dahulu melaksanakan pengurus pemasa berdasarkan timbunan minimum Antara muka timbunan minimum adalah seperti berikut Pemasa aplikasi linux tidak tersedia, walaupun ia adalah yang paling asas struktur data.

public class PriorityQueue : IEnumerable
{
public PriorityQueue(IComparer comparer);
public void Push(T v);
public T Pop();
public T Top();
}

public interface ITimeManager
{
ITimer AddTimer(uint afterTick, OnTimerTimeout callback, params object[] userData);
FixedTick();
}

public class TrivialTimeManager : ITimeManager
{
// ...
}

Selepas itu ialah pelaksanaan pengurus pemasa gaya kernel linux. Pertama ada premis reka bentuk:

linux 应用定时器_应用定时器程序-1_应用定时器设计电子钟

Kita perlu menggunakan tanda untuk menentukan had ketepatan masa yang lebih rendah bagi keseluruhan sistem. Contohnya, untuk permainan, ketepatan di bawah 10ms tidak memerlukan penjagaan, jadi kami boleh menetapkan lebar tanda kepada 10ms. Dalam erti kata lain, WaitFor(8ms) yang ditamatkan dahulu dan WaitFor(5ms) yang ditamatkan kemudian mungkin ditamatkan masa dahulu. Satu tanda ialah 10ms Butiran masa yang boleh dinyatakan oleh tanda 32-bit adalah hampir 500 hari latihan linux terbenam, yang jauh lebih lama daripada masa kumpulan pelayan tanpa dimulakan semula.

虽然这些定时器实现,就是由于这个抉择,在面对之前提到的问题时,方才具有了更佳的性能表现。每次按照tick领到timeout数组,直接dispatch,领到这个数组的时间是一个常数,而最小堆方式领到这个数组须要的时间是m*lgn。

因为空间有限,我们不可能做到每位即将timeout的tick都有对应的数组。考虑到虽然80%以上的timer的时间都不会超过2.55s,我们只针对前256个tick做这些优化举措即可。

那怎么处理注册的256tick以后的timer?我们可以把时间还比较长的timer置于更粗细度的数组中,等到还剩下的tick数大于256以后再把她们取下来重新整理一下数组能够搞定。

假如我们保证每一次tick都严格的做到:

保证这两点,就须要每位tick都对所有数组做一次整理。这样就得不偿失了,所以这儿有个trade-off,就是我通过一个表针(index),来标记我当前处理的position,每过256tick是一个cycle,才进行一次整理。而整理的成本就通过分摊在256tick中,增加了实际上的单位时间成本。

概念比较具象,接出来贴一部份代码。

常量定义:

linux 应用定时器_应用定时器程序-1_应用定时器设计电子钟

public const int TimeNearShift = 8;
public const int TimeNearNum = 1 << TimeNearShift;// 256
public const int TimeNearMask = TimeNearNum - 1;// 0x000000ff
public const int TimeLevelShift = 6;
public const int TimeLevelNum = 1 << TimeLevelShift;// 64
public const int TimeLevelMask = TimeLevelNum - 1;// 00 00 00 (0011 1111)

基础数据结构:

using TimerNodes = LinkedList;
private readonly TimerNodes[TimeNearNum] nearTimerNodes;
private readonly TimerNodes[4][TimeLevelNum] levelTimerNodes;

相当于是256+4*64个timer数组。

tick有32位,每一个tick只会timeout掉expire与index相同的timer。

循环不变式保证near表具有这样几个性质:

level表有4个,分别对应9到14bit,15到20bit,21到26bit,27到32bit。

因为原理都类似,我这儿拿9到14bit的表来说下循环不变式:

有了数据结构和循环不变式,前面的代码也就容易理解了。主要列一下AddTimer的逻辑和Shift逻辑。

private void AddTimerNode(TimerNode node)
{
var expire = node.ExpireTick;
if (expire < index)
{
throw new Exception();
}
// expire 与 index 的高24bit相同
if ((expire | TimeNearMask) == (index | TimeNearMask))
{
nearTimerNodes[expire & TimeNearMask].AddLast(node);
}
else
{
var shift = TimeNearShift;
for (int i = 0; i < 4; i++)
{
// expire 与 index 的高bit相同
var lowerMask = (1 <> shift)&TimeLevelMask].AddLast(node);
break;
}
shift += TimeLevelShift;
}
}
}

private void TimerShift()
{
// TODO index回绕到0的情况暂时不考虑
index++;
var ct = index;// mask0 : 8bit
// mask1 : 14bit
// mask2 : 20bit
// mask3 : 26bit
// mask4 : 32bit
var partialIndex = ct & TimeNearMask;
if (partialIndex != 0)
{
return;
}
ct >>= TimeNearShift;
for (int i = 0; i >= TimeLevelShift;
continue;
}
ReAddAll(levelTimerNodes[i], partialIndex);
break;
}
}

以上代码用c/c++重画后尝尝鲜味更佳。

实现大约就是这种了,接出来我们测一下究竟linux内核风格定时器比最小堆实现的定时器快了多少。

建立的测试用例和测试方式:

static IEnumerable BuildTestCases(uint first, uint second)
{
var rand = new Random();
for (int i = 0; i < first; i++)
{
yield return new TestCase()
{
Tick = (uint)rand.Next(256),
};
}
for (int i = 0; i < 4; i++)
{
var begin = 1U << (8 + 6*i);
var end = 1U << (14 + 6*i);
for (int j = 0; j < rand.Next((int)second * (4 - i)); j++)
{
yield return new TestCase()
{
Tick = (uint)rand.Next((int)(begin+end)/2),
};
}
}
}

{
var maxTick = cases.Max(c => c.Tick);
var results = new HashSet();
foreach (var c in cases)
{
TestCase c1 = c;
mgr.AddTimer(c.Tick, (timer, data) =>
{
if (mgr.FixedTicks == c1.Tick)
results.Add((uint) data[0]);
}, c.Id);
}
var begin = DateTime.Now;
for (int i = 0; i < maxTick+1; i++)
{
mgr.FixedTick();
}
var end = DateTime.Now;
}

建立测试用例时的参数first指大于等于256tick的timer数目,second是指小于256tick的timer数目。

first固定为一千万的测试结果:

The fluctuation of the acceleration ratio is not very significant, and if second continues to decrease, the acceleration ratio of the Linux kernel timer will actually gradually increase due to the increase in shift frequency.

Second is fixed at 1000:

Similar to the inference from the first test, the higher the proportion of timers within 256 ticks, the greater the advantage over the minimum heap timer.

Final conclusion: The advantages of the Linux kernel timer compared to the minimum heap timer are still very significant. In most cases, the performance is more than 2 times, and it is strongly recommended to use it.

This time the code is put on githublinux application timer, and because there is no way to put the link to the linux software in the subscription account article, as long as the backend sends a message "timer" to Novel Jun, the github link will be manually replied. This project not only contains an industrial-grade Linux-style timer implementation code, but also a set of Unity3D-style Coroutine based on this timer implemented by Xiaoxiaojun.

--Kernel Technology English Network-Establish the most authoritative kernel technology exchange and sharing summit in the province

Original address: Understanding Linux kernel-style timer implementation - operating system - Kernel Technology English Network - establishing the most authoritative kernel technology exchange and sharing summit in the province (copyright belongs to the original author, infringement and deletion)

Atas ialah kandungan terperinci Kepentingan komponen pemasa dalam perniagaan permainan dan cara melaksanakannya. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn