Jinsi ya kuunda Swarm, mfumo wetu wa kupima uzito wa ndani, kukabiliana na changamoto za mtihani wa mwisho wa mwisho, mikataba ya kibinafsi, na simulations kubwa ya bot katika gamedev.
Jinsi ya kuunda Swarm, mfumo wetu wa kupima uzito wa ndani, kukabiliana na changamoto za mtihani wa mwisho wa mwisho, mikataba ya kibinafsi, na simulations kubwa ya bot katika gamedev.
Siku hizi, ni vigumu kufikiria bidhaa ya programu bila angalauBaadhi yakiwango cha majaribio. majaribio ya kitengo ni njia ya kawaida ya kukamata makosa katika vipande vidogo vya nambari, wakati majaribio ya mwisho hadi mwisho yanahifadhi mchakato wa kazi wa maombi yote. GameDev sio kipimo, lakini inakuja na changamoto zake za kipekee ambazo sio daima zinakubalika na jinsi tunavyojaribu maombi ya wavuti. Makala hii ni kuhusu njia ambayo tumechukua ili kukidhi mahitaji hayo ya kipekee na chombo chetu cha majaribio ya ndani.
Hi, mimi ni Andrey Rakhubov, Mhandisi Mkuu wa Programu katika MY.GAMES! Katika chapisho hili, nitashiriki maelezo kuhusu backend iliyotumika katika studio yetu, na mbinu yetu ya kupakia mtihani wa meta-mchezo katika War Robots: Frontiers.
Maelezo juu ya usanifu wetu
Bila kuingia katika maelezo yoyote yasiyofaa, backend yetu inajumuisha seti ya huduma za jadi na kundi la nodes zinazohusiana ambapo watendaji wanaishi.
Licha ya upungufu wengine ambao wanaweza kuwepo, mbinu hii imethibitishwa kuwa nzuri sana katika kutatua moja ya matatizo magumu zaidi katika maendeleo ya mchezo: kasi ya iteration. Unaweza kukumbuka kwamba wakati wa siku za mwanzo za maendeleo ya Rust-lang kulikuwa na shaka kwamba nyaraka itabadilika kama wewe kusomayaNaam, tunaambia sawa kuhusu GDD (Dokumento ya Mpango wa Mchezo).
Wachezaji husaidia kwa njia ambayo ni nafuu sana kutekeleza na kurekebisha katika hatua za mwanzo za kubuni ya kipengele na wao ni rahisi sana refactor kama huduma tofauti ikiwa haja hutokea baadaye.
Bila shaka, hii inachangia mtihani wa mzigo na ushirikiano, kwa sababu huna tena tofauti ya wazi ya huduma na API ndogo na zilizoelezwa vizuri. Badala yake, kuna kundi la nodes tofauti, ambapo kuna uhusiano mdogo au unaweza kudhibiti kati ya wachezaji.
Utafiti wa
Kama unaweza kufikiria, mtihani wa kipekee kwa watendaji na huduma (na mtihani wa mzigo / ushirikiano kwa huduma) sio tofauti na kile unachokiona katika aina nyingine yoyote ya programu.
- Mashabiki wa mtihani wa mzigo na ushirikiano
- Utafiti wa End-to-End na uendeshaji wa backend
Hatutazama kujaribu wachezaji kwa undani hapa kwa sababu sio maalum kwa GameDev, lakini inahusiana na mfano wa mchezaji mwenyewe. mbinu ya kawaida ni kuwa na cluster maalum ya node moja ambayo ni sahihi kuunganishwa katika kumbukumbu ndani ya mtihani mmoja na ambayo pia inaweza kuzuia maombi yote ya nje na kuita API ya wachezaji.
Alisema, mambo huanza kuwa ya kuvutia zaidi wakati tunapokuja na kupakia au mtihani wa mwisho hadi mwisho - na hii ni ambapo hadithi yetu inaanza.
Hivyo, katika kesi yetu, programu ya mteja ni mchezo mwenyewe. Mchezo hutumia Unreal Engine, hivyo msimbo unaandikwa katika C++, na kwenye seva tunatumia C#. Wachezaji huingiliana na vipengele vya UI katika mchezo, kuzalisha maombi kwa backend (au maombi hutolewa kwa moja kwa moja).
Katika hatua hii, idadi kubwa ya miundombinu tu kuacha kufanya kazi kwetu, na aina yoyote ya kit kama selenium ambayo inachukulia maombi ya mteja kama kivinjari ni nje ya kiwango.
Swali jingine ni kwamba tunatumia mkataba wa mawasiliano wa kibinafsi kati ya mteja na backend. sehemu hii kwa kweli inastahili makala tofauti kabisa, lakini nitazungumzia dhana kuu:
- Mawasiliano yanafanyika kupitia uhusiano wa WebSocket
- Ni mpango wa kwanza; tunatumia Protobuf kufafanua muundo wa ujumbe na huduma
- Ujumbe wa WebSocket ni ujumbe wa Protobuf unaounganishwa katika chombo na metadata ambayo inachanganya baadhi ya habari muhimu zinazohusiana na gRPC, kama URL na kichwa
Kwa hivyo, chombo chochote ambacho haiwezekani kufafanua protocols za kibinafsi sio sahihi kwa kazi hiyo.
Chombo cha kupakia ili kujaribu wote
Wakati huo huo, tulitaka kuwa na chombo kimoja ambacho kinaweza kuandika majaribio ya mzigo wa REST / gRPC na majaribio ya mwisho hadi mwisho na mkataba wetu wa kibinafsi. Baada ya kuzingatia mahitaji yote yaliyojadiliwa katika Uchunguzi na majadiliano ya awali, tumeachwa na wagombea hawa:
Kila mmoja wao alikuwa na faida na hasara zao, lakini kulikuwa na mambo kadhaa ambayo hakuna wao inaweza kutatua bila mabadiliko makubwa (wakati mwingine ya ndani):
- Kwanza, kulikuwa na haja inayohusiana na mawasiliano kati ya bot kwa madhumuni ya synchronization, kama ilivyojadiliwa hapo awali.
- Pili, wateja ni reactive kabisa na hali yao inaweza kubadilika bila hatua wazi kutoka kwa hali ya mtihani; hii inasababisha synchronization ya ziada na haja ya kupiga njia nyingi katika code yako.
- Hatimaye, zana hizi zilikuwa na lengo ndogo sana kwenye majaribio ya utendaji, zinatoa vipengele vingi vya kuongezeka kwa kasi ya mzigo au kufafanua muda wa muda, lakini hawakupata uwezo wa kuunda matukio magumu yanayohusiana kwa njia rahisi.
Ni wakati wa kukubali ukweli kwamba kwa kweli tunahitaji chombo maalum.Maji yaAlikuwa amezaliwa
Maji ya
Katika ngazi ya juu, jukumu la Swarm ni kuanzisha watendaji wengi ambao watatoa mzigo kwenye seva. Watendaji hawa wanatajwa bots, na wanaweza kuiga tabia ya mteja au tabia ya seva ya kujitolea.
Zaidi rasmi, hapa ni orodha ya mahitaji ya chombo:
- Jibu kwa updates ya hali inapaswa kuwa rahisi
- Ushindani haupaswi kuwa suala la
- Kodi inapaswa kuwa inapatikana moja kwa moja
- Mawasiliano ya bot-to-bot inapaswa kuwa rahisi
- Maonyesho mengi yanapaswa kuunganishwa ili kuunda mzigo wa kutosha
- Zana inapaswa kuwa nyepesi na inaweza kuunda shinikizo la kutosha kwenye backend yenyewe
Kama bonus, mimi pia aliongeza baadhi ya pointi ziada:
- Vituo viwili vya uendeshaji na vipimo vya mtihani wa mwisho lazima vinawezekana
- Chombo kinapaswa kuwa agnostic ya usafiri; tunapaswa kuwa na uwezo wa kuunganisha hii kwenye usafiri wowote mwingine unaowezekana ikiwa inahitajika
- Zana hii ina mtindo wa msimbo wa imperative, kwa sababu mimi binafsi nina maoni imara kwamba mtindo wa declarative sio sahihi kwa matukio magumu na maamuzi ya hali.
- Bots inapaswa kuwa na uwezo wa kuwepo tofauti na chombo cha mtihani, ambayo inamaanisha kwamba hakuna utegemezi mkali
Malengo yetu
Hebu fikiria msimbo tunataka kuandika; tutafikiria bot kama doll, na haiwezi kufanya mambo peke yake, inaweza tu kudumisha invariants, wakati Scenario ni dolleteer ambaye huchukua nguzo.
public class ExampleScenario : ScenarioBase
{
/* ... */
public override async Task Run(ISwarmAgent swarm)
{
// spawn bots and connect to backend
var leader = SpawnClient();
var follower = SpawnClient();
await leader.Login();
await follower.Login();
// expect incoming InviteAddedEvent
var followerWaitingForInvite = follower.Group.Subscription
.ListenOnceUntil(GroupInviteAdded)
.ThrowIfTimeout();
// leader sends invite and followers waits for it
await leader.Group.SendGroupInvite(follower.PlayerId);
await followerWaitingForInvite;
Assert.That(follower.Group.State.IncomingInvites.Count, Is.EqualTo(1));
var invite = follower.Group.State.IncomingInvites[0];
// now vice versa, the leader waits for an event...
var leaderWaitingForAccept = leader.Group.Subscription
.ListenOnceUntil(InviteAcceptedBy(follower.PlayerId))
.ThrowIfTimeout();
// ... and follower accept invite, thus producing the event
await follower.Group.AcceptGroupInvite(invite.Id);
await leaderWaitingForAccept;
Assert.That(follower.Group.State.GroupId, Is.EqualTo(leader.Group.State.GroupId));
PlayerId[] expectedPlayers = [leader.PlayerId, follower.PlayerId];
Assert.That(leader.Group.State.Players, Is.EquivalentTo(expectedPlayers));
Assert.That(follower.Group.State.Players, Is.EquivalentTo(expectedPlayers));
}
}
Steam ya
Backend inasababisha update nyingi kwa mteja ambayo inaweza kutokea mara kwa mara; katika mfano hapo juu, tukio moja kama hilo niGroupInviteAddedEvent
Bot inapaswa kuwa na uwezo wa kujibu matukio haya kwa ndani na kutoa fursa ya kutazama kutoka kwa msimbo wa nje.
public Task SubscribeToGroupState()
{
Subscription.Listen(OnGroupStateUpdate);
Subscription.Start(api.GroupServiceClient.SubscribeToGroupStateUpdates, new SubscribeToGroupStateUpdatesRequest());
return Task.CompletedTask;
}
Wakati code ni rahisi sana (na kama unaweza kufikiriOnGroupStateUpdate
Handler ni tu kesi ya muda mrefu ya kubadili), mambo mengi hutokea hapa.
StreamSubscription
Ni mwenyewe aIObservable<ObservedEvent<TStreamMessage>>
na hutoa upanuzi muhimu, ina mzunguko wa maisha unaohusiana, na inashughulikiwa na takwimu.
Faida nyingine: hauhitaji usindikaji wazi wa kusoma au kurekebisha hali, bila kujali ambapo mpangilio unatumika.
case GroupUpdateEventType.GroupInviteAddedEvent:
State.IncomingInvites.Add(ev.GroupInviteAddedEvent.GroupId, ev.GroupInviteAddedEvent.Invite);
break;
Ushindani haupaswi kuwa suala la
Jambo la msingi ni rahisi: kamwe kuendesha msimbo ambao unategemea script au bot iliyotengenezwa katika hali hiyo, na zaidi ya hayo, msimbo wa ndani wa bot / module haipaswi kuendeshwa kwa wakati huo huo na sehemu zingine za bot.
Hii ni sawa na kufungia kusoma / kuandika, ambapo msimbo wa hali ni kusoma (kufikia pamoja) na msimbo wa bot ni kuandika (kufikia pekee).
Mipango ya kazi na mazingira ya synchronization
Mbinu mbili zilizotajwa katika kichwa hiki sasa ni sehemu yenye nguvu sana za msimbo wa async katika C#. Ikiwa umewahi kuendeleza programu ya WPF, unaweza kujua kwamba niDispatcherSynchronizationContext
ambayo ni wajibu wa kurejesha kwa unyenyekevu wito wako wa async nyuma kwenye thread ya UI.
Katika hali yetu, hatuna wasiwasi juu ya upendo wa thread, na badala yake tunajali zaidi juu ya utaratibu wa utekelezaji wa kazi katika hali.
Kabla ya kuchukua haraka kuandika msimbo wa kiwango cha chini, hebu tuangalie darasa moja ambayo haijulikani sana.ConcurrentExclusiveSchedulerPair
Kutoka kwenye
Inatoa mipangilio ya kazi ambayo inashirikisha kutekeleza kazi wakati kuhakikisha kwamba kazi za pamoja zinaweza kutumika kwa wakati mmoja na kazi za kipekee kamwe.
Inatoa mipangilio ya kazi ambayo inashirikisha kutekeleza kazi wakati kuhakikisha kwamba kazi za pamoja zinaweza kutumika kwa wakati mmoja na kazi za kipekee kamwe.
Hii inaonekana kama hasa tunataka! Sasa tunahitaji kuhakikisha kwamba msimbo wote ndani ya script huendeshwa kwenyeConcurrentScheduler
Wakati code ya bot inafanya kaziExclusiveScheduler
.
Jinsi ya kuweka programu kwa kazi? chaguo moja ni kuhamisha wazi kama kiwango, ambayo inafanywa wakati script huanza:
public Task LaunchScenarioAsyncThread(SwarmAgent swarm, Launch launch)
{
return Task.Factory.StartNew(
() =>
{
/* <some observability and context preparations> */
return RunScenarioInstance(Scenario, swarm, LaunchOptions, ScenarioActivity, launch);
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
Scenario.BotScenarioScheduler.ScenarioScheduler).Unwrap();
}
yaRunScenarioInstance
Njia nyingine, kwa upande wake, inatoaSetUp
na yaRun
Mchakato huu unatumika kwa kutumia mbinu maalum za usambazaji (ScenarioScheduler
Ni sehemu ya ushindani waConcurrentExclusiveSchedulerPair
(Kwa ajili ya
Sasa, wakati sisi ni ndani ya code script na sisi kufanya hii ...
public Task LaunchScenarioAsyncThread(SwarmAgent swarm, Launch launch)
{
return Task.Factory.StartNew(
() =>
{
/* <some observability and context preparations> */
return RunScenarioInstance(Scenario, swarm, LaunchOptions, ScenarioActivity, launch);
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
Scenario.BotScenarioScheduler.ScenarioScheduler).Unwrap();
}
... mashine ya hali async inafanya kazi yake kwa sisi kwa kuweka kalenda kwa kazi zetu.
Sasa yaSendGroupInvite
inafanya kazi kwenye programu ya pamoja pia, hivyo tunachukua hatua hiyo pia:
public Task<SendGroupInviteResponse> SendGroupInvite(PlayerId inviteePlayerId)
{
return Runtime.Do(async () =>
{
var result = await api.GroupServiceClient.SendGroupInviteAsync(/* ... */);
if (!result.HasError)
{
State.OutgoingInvites.Add(inviteePlayerId);
}
return result;
});
}
Runtime abstraction inashughulikia mipango kwa njia sawa kama ilivyokuwa kabla, kwa kuitaTask.Factory.StartNew
kwa mipango sahihi.
Genera ya Kodi
OK, sasa tunahitaji kuingiza kila kitu ndani yaDo
wito; na wakati hii inatatua tatizo, ni makosa ya uwezekano, rahisi kusahau, na kwa ujumla kusema, vizuri, inaonekana ya ajabu.
Hebu tuangalie sehemu hii ya msimbo tena:
await leader.Group.SendGroupInvite(follower.PlayerId);
Hapa, bodi yetu ina aGroup
ya mali.Group
ni moduli ambayo inapatikana tu ili kugeuza msimbo katika darasa tofauti na kuepuka kufunikaBot
ya darasa.
public class BotClient : BotClientBase
{
public GroupBotModule Group { get; }
/* ... */
}
Hebu kusema kwamba moduli inapaswa kuwa na interface:
public class BotClient : BotClientBase
{
public IGroupBotModule Group { get; }
/* ... */
}
public interface IGroupBotModule : IBotModule, IAsyncDisposable
{
GroupBotState State { get; }
Task<SendGroupInviteResponse> SendGroupInvite(PlayerId toPlayerId);
/* ... */
}
public class GroupBotModule :
BotClientModuleBase<GroupBotModule>,
IGroupBotModule
{
/* ... */
public async Task<SendGroupInviteResponse> SendGroupInvite(PlayerId inviteePlayerId)
{
// no wrapping with `Runtime.Do` here
var result = await api.GroupServiceClient.SendGroupInviteAsync(new() /* ... */);
if (!result.HasError)
{
State.OutgoingInvites.Add(new GroupBotState.Invite(inviteePlayerId, /* ... */));
}
return result;
}
|
Na sasa inafanya kazi tu! Hakuna coding zaidi na mbaya, tu mahitaji rahisi (ambayo ni ya kawaida kwa watengenezaji) ya kuunda interface kwa kila moduli.
Lakini wapi programu? uchawi hutokea ndani ya kuzalisha chanzo ambayo huunda darasa la proxy kwa kila interface ya IBotModule na hufunika kila kazi katikaRuntime.Do
Piga simu kwa:
public const string MethodProxy =
@"
public async $return_type$ $method_name$($method_params$)
{
$return$await runtime.Do(() => implementation.$method_name$($method_args$));
}
";
Zana ya
Hatua inayofuata ni kuingiza msimbo ili kukusanya takwimu na njia. Kwanza, kila wito wa API unapaswa kufuatiliwa. Sehemu hii ni rahisi sana, kwa sababu usafirishaji wetu kwa ufanisi hufanya kama njia ya gRPC, hivyo tunatumia tu interceptor iliyoandikwa vizuri...
callInvoker = channel
.Intercept(new PerformanceInterceptor());
ambapoCallInvoker
ni abstraction ya gRPC ya mwito wa RPC upande wa mteja.
Kisha, itakuwa nzuri kupanua sehemu fulani za msimbo ili kupima utendaji. Kwa sababu hiyo, kila moduli inachukuaIInstrumentationFactory
Kwa njia ya interface zifuatazo:
public interface IInstrumentationFactory
{
public IInstrumentationScope CreateScope(
string? name = null,
IInstrumentationContext? actor = null,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0);
}
Sasa unaweza kuunganisha sehemu ambazo wewe ni nia ya:
public Task AcceptGroupInvite(GroupInvideId inviteId)
{
using (instrumentationFactory.CreateScope())
{
var result = await api.GroupServiceClient.AcceptGroupInviteAsync(request);
}
}
Ingawa bado unaweza kutumia kipengele hiki kuunda viwango vya chini, kila njia ya proxy ya moduli inafafanua kiwango cha moja kwa moja:
public const string MethodProxy =
@"
public async $return_type$ $method_name$($method_params$)
{
using(instrumentationFactory.CreateScope(/* related args> */))
{
$return$await runtime.Do(() => implementation.$method_name$($method_args$));
}
}
";
Utaratibu wa kiwango cha kurekodi muda wa utekelezaji, pengine, huunda nyayo zilizosambazwa, inaweza kuandika kumbukumbu za udanganyifu, na hufanya vitu vingi vingine vinavyoweza kubadilishwa.
Mawasiliano ya Bot-to-Bot
Mfano katika sehemu ya "Lengo letu" haina kweli kuonyesha mawasiliano yoyote. Tunajua tu bot ya wafuasiPlayerId
Ingawa mtindo huu wa kuandika mifano mara nyingi ni rahisi na rahisi, kuna matukio magumu ambapo mbinu hii haifanyi kazi.
Mwanzoni nilijaribu kutekeleza aina fulani ya mfano wa blackboard na uhifadhi wa thamani ya msingi wa kushirikiana kwa mawasiliano (kama Redis), lakini baada ya kuendesha ushahidi fulani wa majaribio ya mbinu, ilionekana kuwa kiwango cha kazi kinaweza kupunguzwa sana hadi dhana mbili rahisi: mstari, na mpiga kura.
Maelezo ya - tiketi
Katika ulimwengu halisi, wachezaji kuwasiliana na kila mmoja kwa namna fulani - wanaandika ujumbe wa moja kwa moja au kutumia mazungumzo ya sauti. Na bots, hatuna haja ya simulate kwamba granularity, na tunahitaji tu kuwasiliana nia. Kwa hiyo, badala ya "Hey Killer2000, wito mimi katika kikundi chako tafadhali" tunahitaji rahisi "Hey,Mtu wa“Nendeni mkaweke alama za mipaka mkishamaliza sasa muwaeleze wananchi ni umbali wanapaswa waache kutoka kwenye hizo alama zenu na muwaelimishe kwamba hiyo ndiyo buffer zone.
public interface ISwarmTickets
{
Task PlaceTicket(SwarmTicketBase ticket);
Task<SwarmTicketBase?> TryGetTicket(Type ticketType, TimeSpan timeout);
}
Bot inaweza kuweka tiketi, na kisha bot mwingine anaweza kupata tiketi hiyo.ISwarmTickets
ni kitu chochote zaidi ya mfanyabiashara wa ujumbe na mstari kwaticketType
(Kwa kweli nikidogo ya more than that, as there are extra options to prevent bots from retrieving their own tickets, as well as other minor tweaks).
Kwa interface hii, tunaweza hatimaye kugawana mifano ya mfano katika mifano miwili ya kujitegemea. (hapa, msimbo wowote wa ziada unachukuliwa ili kuonyesha wazo la msingi):
private async Task RunLeaderRole(ISwarmAgent swarm)
{
var ticket = await swarm.Blackboard.Tickets
.TryGetTicket<BotWantsGroupTicket>(TimeSpan.FromSeconds(5))
.ThrowIfTicketIsNull();
await bot.Group.SendGroupInvite(ticket.Owner);
await bot.Group.Subscription.ListenOnceUntil(
GotInviteAcceptedEvent,
TimeSpan.FromSeconds(5))
.ThrowIfTimeout();
}
private async Task RunFollowerRole(ISwarmAgent swarm)
{
var waitingForInvite = bot.Group.Subscription.ListenOnceUntil(
GotInviteAddedEvent,
TimeSpan.FromSeconds(5))
.ThrowIfTimeout();
await swarm.Blackboard.Tickets.PlaceTicket(new BotWantsGroupTicket(bot.PlayerId));
await waitingForInvite;
await bot.Group.AcceptGroupInvite(bot.Group.State.IncomingInvites[0].Id);
}
Mchezo wa
Tuna tabia mbili tofauti, moja kwa kiongozi, moja kwa wafuasi. Bila shaka, zinaweza kugawanywa katika matukio mawili tofauti na kuchapishwa pamoja. Wakati mwingine, hii ni njia bora ya kufanya hivyo, wakati mwingine unaweza kuhitaji muundo wa kiwango cha kikundi (wafuasi kadhaa kwa kiongozi mmoja) au hali nyingine yoyote ya kusambaza / kuchagua data tofauti / majukumu.
public override async Task Run(ISwarmAgent swarm)
{
var roleAction = await swarm.Blackboard
.RoundRobinRole(
"leader or follower",
Enumerable.Repeat(RunFollowerRole, config.GroupSize - 1).Union([RunLeaderRole]));
await roleAction(swarm);
}
Kwa hiyo hapa,RoundRobinRole
ni tu wrapper fancy karibu na meza ya kushirikiana na operesheni ya modulo kuchagua kipengele sahihi kutoka kwenye orodha.
Mfano wa Swarms
Sasa, na mawasiliano yote yamefichwa nyuma ya mikono na hesabu zinazoshirikiwa, inakuwa ndogo kuanzisha kitufe cha orchestrator, au kutumia baadhi ya hifadhi zilizopo za MQ na KV.
Ukweli funny: hatujawahi kumaliza utekelezaji wa kipengele hiki. Wakati QA walipata mikono yao juu ya utekelezaji wa node moja SwarmAgent
, walianza mara moja tu kutumia mifano kadhaa ya wafanyabiashara huru ili kuongeza mzigo.
Mafanikio ya moja kwa moja
Nini kuhusu utendaji? Ni kiasi gani kinachopoteza ndani ya vifungo vyote hivi na synchronizations ya implicit? Sitawafurahia na aina zote za majaribio yaliyofanywa, tu mbili muhimu zaidi ambazo zimeonyesha kuwa mfumo una uwezo wa kujenga mzigo mzuri wa kutosha.
Utekelezaji wa Benchmark:
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.4651/22H2/2022Update)
CPU ya Intel Core i7-10875H 2.30GHz, 1 CPU, 16 kimwili na 8 kimwili
Kuanzishwa kwa .NET SDK 9.0.203
[Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2
.NET 9.0 : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2
private async Task SimpleWorker()
{
var tasks = new List<Task>();
for (var i = 0; i < Parallelism; ++i)
{
var index = i;
tasks.Add(Task.Run(async () =>
{
for (var j = 0; j < Iterations; ++j)
{
await LoadMethod(index);
}
}));
}
await Task.WhenAll(tasks);
}
private async Task SchedulerWorker()
{
// scenarios are prepared in GlobalSetup
await Task.WhenAll(scenarios.Select(LaunchScenarioAsyncThread));
}
public class TestScenario : ScenarioBase
{
public override async Task Run()
{
for (var iteration = 0; iteration < iterations; ++iteration)
{
await botClient.Do(BotLoadMethod);
}
}
}
Kwanza, hebu tuangalie juu ya karibu tu juu ya mipangilio dhidi ya rahisi Task kuzalisha.
Katika kesi hii, mbinu zote mbili za mzigo ni kama ifuatavyo:
private ValueTask CounterLoadMethod(int i)
{
Interlocked.Increment(ref StaticControl.Counter);
return ValueTask.CompletedTask;
}
Matokeo ya Iterations = 10:
WorkerType |
Parallelism |
Mean |
Error |
StdDev |
Gen0 |
Gen1 |
Gen2 |
Allocated |
---|---|---|---|---|---|---|---|---|
Simple |
10 |
3.565us |
0.0450us |
0.0421us |
0.3433 |
- |
- |
2.83KB |
Simple |
100 |
30.281us |
0.2720us |
0.2544us |
3.1128 |
0.061 |
- |
25.67KB |
Simple |
1000 |
250.693us |
2.1626us |
2.4037us |
30.2734 |
5.8594 |
- |
250.67KB |
Scheduler |
10 |
40.629us |
0.7842us |
0.8054us |
8.1787 |
0.1221 |
- |
66.15KB |
Scheduler |
100 |
325.386us |
2.3414us |
2.1901us |
81.0547 |
14.6484 |
- |
662.09KB |
Scheduler |
1000 |
4,685.812us |
24.7917us |
21.9772us |
812.5 |
375 |
- |
6617.59KB |
rahisi ya
10
ya 565
kwa ajili ya 0.0450
0 0 0 0 0 0 0
0.3433
-
-
Mchakato wa 2.83KB
rahisi ya
100
281 ya
0 0 0 0 20
kwa ajili ya 2544
3.1128
0.061
-
5.7 Kb kwa ajili ya
rahisi ya
1000
250 693 ya
1626 kwa ajili ya
2.403 kwa ajili ya
30.2734
5.8594
-
250.67KB kwa ajili ya
Mipango ya
10
40629 ya
Mshahara wa 7842
kwa ajili ya 8054
8.1787
0.1221
-
66.15Kb ya
Mipango ya
100
5386 kwa ajili ya
2 414 ya
2 Mwaka 1901
81.0547
14.6484
-
ya 62.09kb
Mipango ya
1000
4685.812 kwa ajili ya
247917 kwa ajili ya
21772 ya
812.5
375
-
wa 6617.59KB
Inaonekana mbaya? Si kwa usahihi. Kabla ya kufanya mtihani, nilitarajia utendaji mbaya zaidi, lakini matokeo yalikuwa ya kushangaza.Nadhani tu kuhusu kiasi gani kilichotokea chini ya kapu na faida tulizopata tu kwa ~4us kwa kesi ya parallel.
Nini inaweza kuwa hali mbaya zaidi ya majaribio ya kesi mbaya? Nini kuhusu kazi ambayo haina kitu zaidi ya wito wa API? Kwa upande mmoja, karibu kila mbinu ya bot hufanya hivyo, lakini kwa upande mwingine, ikiwa hakuna kitu zaidi ya wito wa API tu, basi juhudi zote za synchronization zinatumika, sivyo?
Njia ya kupakua inaweza tu kuitaPingAsync
Kwa urahisi, wateja wa RPC hupatikana nje ya bots.
private async ValueTask PingLoadMethod(int i)
{
await clients[i].PingAsync(new PingRequest());
}
Hapa ni matokeo, tena iterations 10 (server grpc ni katika mtandao wa ndani):
WorkerType |
Parallelism |
Mean |
Error |
StdDev |
Gen0 |
Gen1 |
Allocated |
---|---|---|---|---|---|---|---|
Simple |
100 |
94.45 ms |
1.804 ms |
2.148 ms |
600 |
200 |
6.14 MB |
Simple |
1000 |
596.69 ms |
15.592 ms |
45.730 ms |
9000 |
7000 |
76.77 MB |
Scheduler |
100 |
95.48 ms |
1.547 ms |
1.292 ms |
833.3333 |
333.3333 |
6.85 MB |
Scheduler |
1000 |
625.52 ms |
14.697 ms |
42.405 ms |
8000 |
7000 |
68.57 MB |
rahisi ya
100
Urefu wa 94.45 ms
Mchakato wa 804 ms
Maoni ya 148
600
200
6.4 Mb ya
rahisi ya
1000
596.69 kwa ajili ya
5952 kwa ajili ya
Maoni ya 45730 ms
9000
7000
77.7 MB ya juu
Mipango ya
100
Urefu wa 95.48 ms
Mchakato wa 1.547 ms
Mchakato wa 1.292
833.3333
333.3333
Kiwango cha 5.85 MB
Mipango ya
1000
Maoni ya 625,52
Mchakato wa 697 ms
Maoni ya 24405 ms
8000
7000
Mchezo wa 68.57 MB
Kama ilivyotarajiwa, athari ya utendaji kwenye matukio halisi ya kazi ni ya kutosha.
Uchambuzi wa
Bila shaka, kuna kumbukumbu za backend, takwimu, na nyayo ambazo hutoa mtazamo mzuri wa kile kinachotokea wakati wa kupakia. Lakini Swarm huenda hatua zaidi, kuandika data yake mwenyewe - wakati mwingine iliyounganishwa na backend - kukamilisha.
Hapa ni mfano wa takwimu kadhaa katika mtihani wa kushindwa, na idadi ya watendaji inaongezeka. mtihani ulishindwa kutokana na ukiukwaji wa SLA kadhaa (tazama muda katika wito wa API), na tuna vifaa vyote muhimu vya kufuatilia kile kilichotokea kutoka kwa mteja.
Sisi kawaida kuacha mtihani kabisa baada ya idadi fulani ya makosa, ambayo ni kwa nini API wito mstari mwisho ghafla.
Bila shaka, nyayo zinahitajika kwa ufuatiliaji mzuri. Kwa majaribio ya Swarm, nyayo za mteja zinahusishwa na nyayo za nyuma kwenye mti mmoja.connectionIdD/botId
ambayo inasaidia katika kuimarisha.
Kumbuka: DevServer ni muundo rahisi wa monolith ya huduma zote-in-one za backend zinazotolewa kwenye PC za watengenezaji, na hii ni mfano maalum uliofanywa ili kupunguza kiasi cha maelezo kwenye skrini.
Swarm bots write another kind of trace: they mimic the traces used in performance analysis. With the aid of existing tools, those traces can be viewed and analyzed using built-in capabilities like PerfettoSQL.
Nini kilichotokea katika mwisho wa
Sasa tuna SDK ambayo bots ya GameClient na Dedicated Server wote ni kujengwa juu yake. Bots hizi sasa zinaunga mkono kimsingi ya mantiki ya mchezo - ikiwa ni pamoja na mifumo ya marafiki, vikundi vya msaada, na matchmaking. bots za kujitolea zinaweza kuiga mechi ambapo wachezaji wanapata tuzo, majaribio ya maendeleo, na mengi zaidi.
Kuwa na uwezo wa kuandika msimbo rahisi iliwezesha kuunda modules mpya na mifano rahisi ya mtihani kwa timu ya QA. Kuna wazo kuhusu mifano ya FlowGraph (programming ya Visual), lakini hii bado ni wazo tu kwa wakati huu.
Majaribio ya utendaji ni karibu ya kuendelea - ninasema karibu kwa sababu mtu bado anahitaji kuanza kwa manually.
Swarm husaidia si tu na majaribio, lakini kwa kawaida hutumiwa kuigiza na kurekebisha makosa katika mantiki ya mchezo, hasa wakati huu ni vigumu kufanya kwa mkono, kama wakati unahitaji wateja kadhaa wa mchezo kufanya mfululizo maalum wa vitendo.
Kwa ujumla, tunafurahia sana na matokeo yetu na hatuna hatia kuhusu juhudi zilizotumika katika kuendeleza mfumo wetu wa mtihani.
Natumaini umefurahia makala hii, ilikuwa changamoto kukujulisha juu ya nuances zote za maendeleo ya Swarm bila kufanya maandishi haya ya kutosha. Nina uhakika kwamba ninaweza kufuta baadhi ya maelezo muhimu katika mchakato wa usawa wa ukubwa wa maandishi na habari, lakini nafurahia kutoa mazingira zaidi ikiwa una maswali yoyote!