Exakt. För att ta ett konkret exempel
Antag att du har ett program med två CPU-trådar. Dessa trådar läser/skriver båda till tre globala variabler, A, B och C. Antag att A, B och C initialt är 0.
Antag att tråd#1 gör detta
Medan tråd#2 gör detta
if (C == 1) {
print(B)
print(A)
}
Vi ignorerar de optimeringar kompilatorn kan göra här, vi antar att minnesoperationerna verkligen lagts ut i den ordning som listas här både för x86 och ARM64.
På x86 garanteras att tråd#2 endera inte skriver ut något alls (om tråd#2 kolla värdet på C innan det sätts till 1), eller så skrivs 42 och 17 ut. Det trots att det inte finns någon explicit synkronisering, d.v.s. x86 ger massa garantier som i praktiken är onödiga -> kostar prestanda när flera kärnor jobbar på delad data.
På ARM64 man man få fallet att inget skrivs ut, [0,0], [0,17], [42,0] samt [42,17]. Detta kanske känns konstigt för en människa, men det är faktiskt vad man får i det mest rimliga/effektiva HW-implementationen som inte ger mer garantier än vad som egentligt specificeras här. Alla moderna programspråk har specifika sätt att uttrycka minnesoperationer som har regler relaterad till andra minnesoperationer som hänt före/efter just denna minnsoperation.
"Normala" minnesoperationer står för >99% av alla man gör i ett typiskt program, de kräver ingenting om minnesoperationer som utförs på andra variabler. Vill man få x86 resultatet på ARM64 gör man detta
Antag att tråd#1 gör detta
A = 17
B = 42
C.store(1, release)
Medan tråd#2 gör detta
if (C.load(acquire) == 1) {
print(B)
print(A)
}
ARM64 har en speciell "store" operation som kallas "store-release". Den garanterar att alla minnesoperationer som hände innan denna kommer vara synliga av alla andra kärnor som gör en "load-acquire" och läser det nya värdet som lagras. Så load-acquire är den andra änden, det säger att om man läser det nya värdet som gjorts i en store-release till samma variabel så är man garanterad att alla efterföljande läsningar kommer se de värden i övriga variabler som skrevs innan store-release.
Kan låta komplicerat, men är exakt hur "lås" (mutex/binär-semaphore) fungerar i multitrådade program.
D.v.s. ARM64 är på instruktionsnivå en perfekt match till hur vi idag skriver multitrådade program som behöver dela data mellan trådar. x86 ger massor med garantier som i praktiken inte tillför något i majoriteten av fallen, men man kan inte ändra beteendet då det finns enstaka program som är beroende av hur x86 är specificerad att fungera.
Nästan 100 % fallen när program "fungerar" på x86, men får märkliga buggar vid lite udda tillfällen på ARM64 har programmet ett logisk fel av typen "data-race", men det råkar fungera i 100 % eller nära 100 % på x86 då den har så väldigt lite möjlighet att optimera ordningen för läsning/skrivning mot minne.