Puncte:11

Este If/else vulnerabil la sincronizarea atacurilor pe canalul lateral?

drapel tf
Tom

Am o ramificare în c++:

dacă (x și 1)
{
    x = function_1(x);
}
altfel
{
    x = function_2(x);
}

Dacă funcția_1 și funcția_2 sunt timp constant și este nevoie de același timp pentru a le calcula, este o astfel de ramificare încă vulnerabilă pentru atacurile pe canalul lateral? Poate atacatorul să știe cumva ce condiție a fost executată?

Tom avatar
drapel tf
Tom
@kelalaka ok, multumesc. Și ce zici de ((x & 1) * funcția_1(x)) ^ ((~x & 1) * funcția_2(x))? Este mai bine sau încă rău? Bănuiesc că nici aceasta nu este o soluție foarte bună, dar nu sunt sigur.
kelalaka avatar
drapel in
Nu văd nicio diferență, în plus, asigură-te că nimic nu este optimizat. Acesta este motivul pentru care blocurile ASM sunt comune în implementările pe canale laterale. În cele de mai sus au fost greșeli, corectate aici (altul comentariu este șters) $$x = (x \wedge 1)* f_1(x) + ((x \wedge 1)\oplus 0x1 ) * f_2(x)$$
Tom avatar
drapel tf
Tom
@kelalaka da, a fost o greșeală, de aceea am crezut că este ceva diferit. Acum rezultatul este același ca în formula mea. Dar cred că dvs. este încă mai bun, deoarece în formula mea există "~x & 1", și probabil că acesta nu este un timp constant (în comparație cu x & 1), mai ales dacă x este un număr mare
kelalaka avatar
drapel in
Nu este vorba despre non-constantitatea lui ~ sau x-or.Este vorba despre tine să executați întotdeauna ambele metode indiferent de valoarea lui $x$ și în pasul final le adăugați cu măștile. Acest lucru nici măcar nu necesită să se bazeze pe aceeași actualitate constantă de $f_1$ și $f_2$
kelalaka avatar
drapel in
Vezi răspunsul canonic al lui [Squeamish Ossifrage](https://crypto.stackexchange.com/a/96634/18298)...
Puncte:14
drapel ng

Da, dacă/altfel este vulnerabil la atacul de sincronizare. La fel este și selectarea funcției de apelat printr-un index de matrice, ca în aceea alt raspuns.

Singura soluție aproape de încredere pentru execuția în timp constant într-un limbaj de nivel înalt, fără indicații despre mediul țintă, este: Asigurați-vă că calea de execuție a codului și tiparele de acces la memorie sunt independente de date.

Acest lucru exclude apelarea unui singur dintre funcția_1 sau funcția_2 (așa cum fac codul întrebării și celălalt răspuns) și matrice de indexare, bazate pe date care se modifică sub control sau într-o manieră observabilă de către un adversar, chiar parțial și indirect.

Dacă costul suplimentar al apelării ambelor funcții este tolerabil, iar comportamentul funcțiilor este bine definit și acceptabil, indiferent de bitul de ordin scăzut al intrării lor, atunci acest lucru se va descurca cu majoritatea compilatoarelor și hardware-ului C actual:

   m = -(x&1); // masca variabila; presupune că m și x sunt int
   x = (funcția_1(x) și m) | (funcția_2(x) & ~m);

sau echivalent

   x = funcția_1(x) & -(x&1) | function_2(x) & ~ -(x&1);

Aceasta apelează ambele funcții și selectează rezultatul în mod corespunzător, folosind acea mască -(x&1) are toți biții la 1 pentru impar X, la 0 altfel.

Nu cunosc niciun compilator CPU + C introdus din 1985 în care să existe o dependență de sincronizare X introdus prin acea tehnică. Literatura modernă privind implementarea în timp constant a cripto-ului presupune că nu există, fără să spun. Asta în ciuda faptului că timpul de execuție al procesoarelor moderne de înaltă performanță este nespecificat și se poate schimba în mod imprevizibil din perspectiva unui programator de aplicații (de exemplu, datorită compilatoarelor/linkerelor și opțiunilor acestora, alinierii codului și datelor, întreruperi, stări de așteptare a memoriei și cicluri de reîmprospătare). , cache-uri, conducte, execuție speculativă, predictori de ramuri, alte fire de execuție pe același nucleu/CPU/procesoare conectate, arbitrarea resurselor între fire de execuție pe același nucleu, virtualizare, setări BIOS sau VM-uri și fluxul aparent nesfârșit de modificări de microcod defecte care compromit performanță pentru a atenua cel mai recent atac raportat pe canalul lateral...)

Important este că acea ipoteză nerealistă din întrebare

funcția_1 și funcția_2 sunt timp constant și este nevoie de același timp pentru a le calcula

pot fi înlocuite cu cele plauzibile și falsificabile:

Variaţii în timpul de execuţie a funcția_1 sunt necorelate cu valoarea lui X. funcția_2 are aceeași proprietate.

Avertisment 1: limbajele C/C++ asigură doar rezultatele obținute, niciuna asupra timpului de execuție. Nu cunosc nici un manual al compilatorului căruia îi pasă și unele procesoare fără documentație.

Avertisment 2: -(x&1) pentru a obține o mască în conformitate cu un pic de ordin scăzut X este un limbaj bine-cunoscut și funcționează în prezent pe aproape toate procesoarele. Asta pentru că folosesc complement a doi conventie, cu -1 reprezentate de toate. Dar am pierdut cunoștințele despre dacă C promite să ascunde orice comportament diferit ar putea avea hardware-ul (las asta la comentariile utile). Și asta trebuie verificat indiferent de situație.

Avertisment 3: Adaptarea poate fi necesară în funcție de tipul de X datorită regulilor de promovare C. Eu de obicei castez 1 la tipul de X, de exemplu. dacă X este o uint32_t, Eu folosesc -(x&(uint32_t)1), și ar trăi fericit cu el dacă nu ar exista avertismente ale compilatorului.

Unii compilatori se plâng atunci când iau negativul unei expresii nesemnate. Sunt greșite, conform standardului C. Calea ușoară este să te înclini și să folosești (0-(x&1)), cu turnarea constantelor ca mai sus. O alta este să dezactivați acea plângere cu ceva pragma sau opțiune. Încă un alt, fără speranță din experiența mea, este să trimit un raport de problemă. Argumentele făcute cu grijă merg la NUL (echivalentul local al lui /dev/null) inclusiv: constructul este util, comun, susținut în mod explicit de ISO C; iar avertismentele în acest sens ar putea face ca toate avertismentele să fie reduse la tăcere fără discernământ.

Notă: am presupus tipul de returnare a funcția_1 și funcția_2 este de același tip ca X.


Efectuarea securității/criptografiei pe procesoarele standard în care adversarii pot rula și coduri a fost constant fragilă încă din anii 1980. Există progrese utile: izolarea proceselor cu unități de gestionare a memoriei, inele de securitate sau enclave. Există euristici software pentru a reduce vulnerabilitatea la niveluri acceptabile. Dar, până acum, niciun glonț de argint.

Deci, atunci când mizele sunt mari, este deseori cel mai bine să descărcați funcțiile de securitate către hardware de încredere (Smart Carduri, procesoare de securitate, HSM, TPM-uri...) concepute pentru securitate mai întâi, nu pentru performanță; și unde adversarii nu pot rula cod (cel mai simplu, prin urmare, cel mai bun), sau acesta este proiectat.

SAI Peregrinus avatar
drapel si
C nu promite (încă) comportamentul complementului doi. În viitorul standard C23, acesta va fi garantat, așa că unele platforme vor trebui să emuleze complementul doi pentru a avea compilatoare C23. De asemenea, expresia nu este nesemnată, chiar dacă `x` este nesemnat. `1` este un literal întreg cu semn, astfel încât atât `x`, cât și `1` sunt promovate conform regulilor din secțiunea 6.3.1.8. Apoi se aplică `&`, iar rezultatul este negat. Rezultatul este de tipul întreg (promovat) al lui `(x&1)`. Puteți folosi literale tastate (de exemplu, `1ULL`) pentru a face ca `1` să aibă același rang ca `x`.
drapel in
@SAIPeregrinus: POSIX face [promite](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdint.h.html) complementul a doi chiar acum, deci dacă dezvoltați pentru o țintă compatibilă cu POSIX (care este majoritatea inclusiv Windows), atunci ești bun.
Lorenzo Donati support Ukraine avatar
drapel ru
@SAIPeregrinus Nitpick: 1ULL este `unsigned long long` a cărui dimensiune este *cel puțin* 64 de biți, conform standardului C, deci are rang mai mare decât `x` (`uint32_t`). Referință: secțiunea C standard (N1256 draft - C99) **5.2.4.2.1 Dimensiunile tipurilor întregi**: *valoare maximă pentru un obiect de tip unsigned long long int* `ULLONG_MAX 18446744073709551615 // 2^64 â 1`
Tom avatar
drapel tf
Tom
Acesta este un răspuns grozav și un subiect mare, după cum văd. BTW, lucrez la tipul de date __m128i, așa că fac ceva de genul: _mm_xor_si128(x,k) & -_mm_cvtsi128_si64(x & 1). Pentru a obține cel mai mic bit semnificativ, trebuie să folosesc _mm_cvtsi128_si64. _mm_cvtsi128_si64 și __m128i sunt două tipuri de date diferite, dar ȘI funcționează oricum corect, din cauza proprietăților __m128i. Deci nu este întotdeauna cazul că 1 trebuie să fie de același tip cu x, și chiar și x&1 nu trebuie să fie de același tip cu funcția_1 sau funcția_2. Dar SSE2 este un caz special.
Tom avatar
drapel tf
Tom
@fgrieu Dacă înțeleg totul corect, nu contează când înlocuim formula prezentată cu x = function_1(x) & -(x&1) | funcția_2(x) și -(x&1^1)? Deci folosim xor 1 în loc de ~.
fgrieu avatar
drapel ng
@Tom: Da, funcționează și asta. Cu toate acestea, majoritatea compilatorilor vor observa că `~ -(x&1)` poate fi calculat eficient din `-(x&1)`, sau gratuit dacă operatorul `&~` are suport hardware, ceea ce este obișnuit, deoarece este folosit în multe dintre ele comune și utile. idiomuri, de ex. în `y&m | z&~m`. Dar am câteva îndoieli că mulți compilatori vor reutiliza `-(x&1)` în evaluarea lui `& -(x&1^1)`.
fgrieu avatar
drapel ng
@Tom: Mulțumesc pentru clarificare. Ți-am șters comentariul de când a fost citit, deci nu mai este necesar. Votarea pozitivă a unui comentariu este suficientă mulțumire (și din acest motiv, comentariul prezent va fi distrus RSN).
Puncte:4
drapel us

Aceasta a început ca o modificare a răspunsului lui fgrieu, dar a fost foarte lung, așa că am decis să îl postez ca răspuns separat.

Sunt de acord cu tot ce a scris fgrieu în răspunsul său

Voi cita răspunsul lui fgrieu aici pentru a pune mai mult accent:

Singura soluție aproape de încredere pentru execuția în timp constant într-un limbaj de nivel înalt, fără indicații despre mediul țintă, este: Asigurați-vă că calea de execuție a codului și tiparele de acces la memorie sunt independente de date.

Dacă nu luăm în considerare modelele de acces la memorie, deoarece nu este în domeniul de aplicare al întrebării. Citatul poate numai teoretic poate fi realizat cu timp constant de acces la memorie și execuție constantă în timp a fiecărei instrucțiuni. Desigur, acestea vin cu costul ciclurilor de ceas suplimentare și al acceselor suplimentare la memorie.

Modelele de acces la memorie se referă la un alt tip de atac de canal lateral care necesită Oblivious Random Access Memory (ORAM) pentru a fi securizat împotriva atacurilor de canal lateral.


Dacă funcția_1 și funcția_2 sunt timp constant și durează același timpul pentru a le calcula, este încă vulnerabil pentru o astfel de ramificare atacuri pe canale laterale?

Cel mai frecvent da, dacă/altfel este vulnerabil la atacul de sincronizare, deoarece atunci când utilizați o instrucțiune if/else vă puteți aștepta cu ușurință de la compilator sau de la mediul de execuție să execute o ramură. În orice caz, în general, încercați să o evitați.

Selectarea funcției de apelat printr-un index de matrice, ca în răspunsul mentallurg în Python, de exemplu, nu este o abordare atât de proastă, are timp constant de acces la memorie și codul care este executat de Python pentru a efectua apelul funcției este timpul constant.


Înainte să încep.

  • În cazul accesărilor la memorie, rețineți că, deoarece ne interesează atacurile de sincronizare, ne interesează doar accesul la memorie timp si nu modele care ar putea scurge informații, deoarece acest lucru ne-ar complica puțin situația.
  • Consider cache-urile doar la sfârșit, deocamdată nu luăm în considerare cache-urile, deoarece toată lumea știe ca-urile CPU pot fi cel mai rău coșmar al unui criptograf în astfel de situații.
  • Presupun că fiecare instrucțiune care nu include un acces la memorie necesită aceleași cicluri de ceas pentru a se executa.

Analiza răspunsului mentallurg:

Să luăm de exemplu acest cod Python simplu

def f1(x):
    returnează x + 1

def f2(x):
    returnează x + 2

def main():
    funcții = [f1, f2]
    x = int(input("Introduceți valoarea:"))
    i = x % 2
    rezultat = funcții[i](x)
    imprimare (rezultat)

if __name__=='__main__':
    principal()

Acesta este compilat la următorul bytecode Python (ieșirea pycdas pentru .pyc compilat cu Python 3.10):

        [Cod]
            Nume fișier: test.py
            Nume obiect: principal
            Număr de argumente: 0
            Număr Arg Numai Poz: 0
            Număr KW Doar Arg: 0
            Localnici: 4
            Dimensiunea stivei: 3
            Indicatori: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
            [Nume]
                'f1'
                'f2'
                'int'
                'intrare'
                'imprimare'
            [Nume Var]
                'functii'
                'X'
                'eu'
                'rezultat'
            [Varie gratuite]
            [Cell Vars]
            [Constante]
                Nici unul
                „Introduceți valoarea:”
                2
            [Dezasamblare]
                0 LOAD_GLOBAL 0: f1
                2 LOAD_GLOBAL 1: f2
                4 BUILD_LIST 2
                6 STORE_FAST 0: funcții
                8 LOAD_GLOBAL 2: int
                10 LOAD_GLOBAL 3: intrare
                12 LOAD_CONST 1: „Introduceți valoarea:”
                14 CALL_FUNCTION 1
                16 CALL_FUNCTION 1
                18 STORE_FAST 1: x
                20 LOAD_FAST 1: x
                22 LOAD_CONST 2: 2
                24 BINARY_MODULO           
                26 STORE_FAST 2: i
                28 LOAD_FAST 0: funcții
                30 LOAD_FAST 2: i
                32 BINARY_SUBSCR           
                34 LOAD_FAST 1: x
                36 CALL_FUNCTION 1
                38 STORE_FAST 3: rezultat
                40 LOAD_GLOBAL 4: imprimare
                42 LOAD_FAST 3: rezultat
                44 CALL_FUNCTION 1
                46 POP_TOP                 
                48 LOAD_CONST 0: Nici unul
                50 RETURN_VALUE

Căutarea și execuția liniei rezultat = funcții[i](x) se face de la liniile octeți 26-36. Să aruncăm o privire la BINARY_SUBSCR operator. Este nevoie de două argumente, o listă (poate fi un dict sau un tuplu, dar să nu ne concentrăm asupra acestui lucru) ca primul și un index din stivă (cele care au fost încărcate anterior cu LOAD_FAST), returnează valoarea la acel indice și scade stiva cu 1.

Acum, să aruncăm o privire cum BINARY_SUBSCR este implementat în CPython. Implementarea poate fi găsită Aici si este urmatoarea:

        ȚINTĂ(BINARY_SUBSCR) {
            PREVIZIT(SUBSCR_BINAR);
            PyObject *sub = POP();
            PyObject *container = TOP();
            PyObject *res = PyObject_GetItem(container, sub);
            Py_DECREF(container);
            Py_DECREF(sub);
            SET_TOP(rez);
            dacă (res == NULL)
                merge la eroare;
            JUMPBY(INLINE_CACHE_ENTRIES_BINARY_SUBSCR);
            EXPEDIERE();
        }

Acum se poate concentra întreaga analiză PyObject *res = PyObject_GetItem(container, sub);. Aceasta este o metodă generică și, până când elementul este preluat, sunt numite alte metode intermediare. Desigur, ne putem aștepta $O(1)$ complexitate. Rămâne de verificat. La sfarsit PyList_GetItem se numeste care este urmatorul:

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    dacă (!PyList_Check(op)) {
        PyErr_BadInternalCall();
        returnează NULL;
    }
    dacă (!valid_index(i, Py_SIZE(op))) {
        _Py_DECLARE_STR(list_err, „indexul listei în afara intervalului”);
        PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err));
        returnează NULL;
    }
    return ((PyListObject *)op) -> ob_item[i];
}

După cum putem vedea pe ultima linie. Are $O(1)$ complexitate.Desigur, datorită complexității limbajelor de nivel înalt, acestea nu sunt niciodată folosite în practică pentru astfel de aplicații. Deci, să încercăm acest cod într-un limbaj de nivel inferior, cum ar fi C, pentru a vedea ce produce.

#include <stdio.h>

int f1(int x) {
    întoarce x + 1;
}

int f2(int x) {
    întoarce x + 2;
}

int main() {
    int x;
    scanf("%d", &x);
    int (*(funcții[2]))(int) = {f1, f2};
    int i;
    i = x %2;
    int rezultat = functions[i](x);
}

Acesta este x86_64 de la Godbolt cu cel mai recent GCC fără optimizări:

f1:
        adiu   $sp,$sp,-8
        sw      $fp,4($sp)
        mutare    $fp,$sp
        sw      $4,8($fp)
        lw      $2,8($fp)
        nup
        adiu   $2,$2,1
        mutare    $sp,$fp
        lw      $fp,4($sp)
        adiu   $sp,$sp,8
        jr 31 USD
        nup

f2:
        adiu   $sp,$sp,-8
        sw      $fp,4($sp)
        mutare    $fp,$sp
        sw      $4,8($fp)
        lw      $2,8($fp)
        nup
        adiu   $2,$2,2
        mutare    $sp,$fp
        lw      $fp,4($sp)
        adiu   $sp,$sp,8
        jr 31 USD
        nup

$LC0:
    .ascii „%d\000”
principal:
    adiu $sp,$sp, -56
    sw $31,52($sp)
    sw $fp, 48($sp)
    muta $fp,$sp
    adiu $2,$fp, 32
    muta $5,2 dolari
    lui $2,% salut(0 USD)
    adiu $4,$2,%lo($LC0)
        jal __isoc99_scanf
        nup

        lui     $2,%hi(f1)
    adiu $2,$2,%lo(f1)
    sw $2,36($fp)
    lui $2,%hi(f2)
        adiu   $2,$2,%lo(f2)
        sw      $2,40($fp)
        lw      $3,32($fp)
        li      $2,-2147483648 # 0xffffffff80000000
    ori $2,$2,0x1
    și $2,$3,$2
        bgez    $2,$L6
        nup

        adiu   $2,$2,-1
        li      $3,-2 # 0xfffffffffffffffe
    sau $2,$2,$3
        adiu   $2,$2,1
$L6:
    sw $2,24($fp)
    lw $2,24($fp)
    nup
    sll $2,2,2 dolari
    adiu $3,$fp, 24
    addu $2,$3,$2
        lw      $2,12($2)
        lw      $3,32($fp)
        nup
        mutare    $4,$3
        mutare    $25,$2
        25 USD
        nup

        sw      $2,28($fp)
        mutare    $2,$0
        mutare    $sp,$fp
        lw      $31,52($sp)
        lw      $fp,48($sp)
        adiu   $sp,$sp,56
        jr 31 USD
        nup

Mai precis, ne interesează aceste instrucțiuni în care se face apelarea la funcția corespunzătoare:

        lw      $2,24($fp)
        nup
        sll     $2,$2,2
        adiu   $3,$fp, 24
        addu    $2,$3,2 dolari
    lw $2,12(2 USD)
    lw $3,32($fp)
    nup
    muta $4,3 dolari
    muta $25,2 dolari
    jalr $25
        nup

        sw      $2,28($fp)
        mutare    $2,$0

După cum putem vedea, nu se fac ramuri, cu excepția jalr care apelează funcția corespunzătoare.


Analiza raspunsului lui fgrieu

Desigur, este ușor de observat că acesta este un timp constant:

#include <stdio.h>

int f1(int x) {
    întoarce x + 1;
}

int f2(int x) {
    întoarce x + 2;
}

int main() {
    int x;
    scanf("%d", &x);
    int (*(funcții[2]))(int) = {f1, f2};
    int m = -(x&1); // masca variabila m trebuie să aibă același tip ca x
    x = (funcția_1(x) și m) | (funcția_2(x) & ~m);
    printf(x);
}

Din nou, Goldbolt cu aceleași opțiuni:

f1:
        împinge rbp
        mov rbp, rsp
        mov DWORD PTR [rbp-4], edi
        mov eax, DWORD PTR [rbp-4]
        adăugați eax, 1
        pop rbp
        ret
f2:
        împinge rbp
        mov rbp, rsp
        mov DWORD PTR [rbp-4], edi
        mov eax, DWORD PTR [rbp-4]
        adăugați eax, 2
        pop rbp
        ret
.LC0:
        .string „%d”
principal:
        împinge rbp
        mov rbp, rsp
        împinge rbx
        sub RSP, 40
        lea rax, [rbp-24]
        mov rsi, rax
        mov edi, OFFSET FLAT:.LC0
        mutare eax, 0
        apelați __isoc99_scanf
        mov QWORD PTR [rbp-48], OFFSET FLAT:f1
        mov QWORD PTR [rbp-40], OFFSET FLAT:f2
        mov eax, DWORD PTR [rbp-24]
        și eax, 1
        neg eax
        mov DWORD PTR [rbp-20], eax
        mov eax, DWORD PTR [rbp-24]
        mov edi, eax
        mutare eax, 0
        funcția de apelare_1
        și eax, DWORD PTR [rbp-20]
        mov ebx, eax
        mov eax, DWORD PTR [rbp-24]
        mov edi, eax
        mutare eax, 0
        apel funcția_2
        mov edx, DWORD PTR [rbp-20]
        nu edx
        și eax, edx
        sau eax, ebx
        mov DWORD PTR [rbp-24], eax
        mov eax, DWORD PTR [rbp-24]
        cdqe
        mov rdi, rax
        mutare eax, 0
        apelați printf
        mutare eax, 0
        mov rbx, QWORD PTR [rbp-8]
        părăsi
        ret

Ne concentrăm pe această parte în care se face atribuirea lui m și ramificarea inline:

        mov eax, DWORD PTR [rbp-24]
        și eax, 1
        neg eax
        mov DWORD PTR [rbp-20], ea
        mov eax, DWORD PTR [rbp-24]
        mov edi, eax
        mutare eax, 0
        funcția de apel_1
        și eax, DWORD PTR [rbp-20]
        mov ebx, eax
        mov eax, DWORD PTR [rbp-24]
        mov edi, eax
        mutare eax, 0
        apel funcția_2
        mov edx, DWORD PTR [rbp-20]
        nu edx
        și eax, edx
        sau eax, ebx
        mov DWORD PTR [rbp-24], eax

Putem vedea de fapt execuția în timp constant.

Deci, care este diferența dintre aceste soluții:

Ambele satisfac măsurile de analiză anti-timing, dar a doua ascunde și tiparele de acces. Apelează întotdeauna ambele funcții. Deoarece nu am menționat cache-urile până acum, în prezența cache-urilor, cel de-al doilea pare mai sigur împotriva atacurilor de canal lateral.Pur și simplu pentru că are nevoie ca codul ambelor funcții să fie în cache pentru a executa instrucțiunea. În al doilea caz, doar cel care este apelat este stocat în cache. Dacă presupunem că pentru o anumită perioadă de timp f1 a fost chemat şi f2 a fost evacuat din cache, ar exista o diferență de timp în care f2 va fi sunat din nou.

Pentru alte soluții și citiri suplimentare pe această temă puteți lua în considerare [1] și [2].

Puncte:-1
drapel kr

Puteți pune funcții într-o matrice și le puteți referi prin index.

În Python ar arăta astfel:

def f1( x ):
    ...

def f2( x ):
    ...

funcții = [ f1, f2 ]

i = x % 2

rezultat = funcții[ i ]( x )
Tom avatar
drapel tf
Tom
Mulțumiri. Și asta pare a fi inteligent. Desigur, încerc doar să-mi îmbunătățesc codul, nu pentru uz profesional, deocamdată.
kelalaka avatar
drapel in
Sunteți sigur că acest lucru (este|va) nu este convertit ca și altfel în fundal? Să te bazezi pe compilator este prea riscant atâta timp cât nu lucrezi cu [Thomas Pornin T1](https://www.youtube.com/watch?v=IHbtK5Kwt6A)
drapel kr
@kelalaka: Sunt sigur că funcționează conform așteptărilor. Acesta nu este cazul care poate fi decis de compilator sau de mediul de execuție în platformele comune precum C, C++, C#, Java, Python. Ceea ce este adesea optimizat sunt expresiile **booleene**. De exemplu. dacă există expresia `a & b` și runtime-ul știe `a == false` (sau `a == 0`), atunci în multe platforme nu este nici măcar delegat la runtime, dar este cerut de specificația limbajului care timpul de execuție *trebuie* să omite evaluarea celui de-al doilea operand.
fgrieu avatar
drapel ng
Aceasta ar putea fi o îmbunătățire. Dar **asta nu rezolvă pe deplin problema**. Tot felul de lucruri, inclusiv cache-urile, aliniere... conspiră pentru a face imposibilă realizarea unui cod portabil pentru computerele moderne în timp constant, având în același timp căi de cod sau modele de acces la date care depind de date, ca în întrebare. Realizarea acestui lucru este posibilă numai cu un control strict al mediului de execuție (de exemplu, în limbajul de asamblare pe CPU cu un model clar al ciclurilor de execuție), iar limbajele de nivel înalt nu permit acest lucru.
drapel kr
@fgrieu: Începând cu unele **nu poți** controla execuția.Chiar dacă îl implementați în asamblator, nu puteți controla modul în care sistemul de operare organizează accesul la memorie, cum este împărțită memoria, cum sistemul de operare utilizează diferite optimizări. La un nivel mai îndepărtat, nici măcar sistemul de operare nu poate controla modul în care computerul utilizează cache-uri de diferite tipuri. În plus, cele mai multe procesoare moderne din PC-uri folosesc **predicție de ramuri**, astfel încât un anumit cod poate fi executat **în avans** (înainte de a fi nevoie cu adevărat), iar rezultatul său poate fi folosit mai târziu sau poate nu. folosit și doar aruncat. Și nu există nicio modalitate de a o controla.
drapel kr
@fgrieu: ... De aceea este logic să vorbim despre reducerea scurgerii de informații pe canalul lateral doar la un anumit nivel macro. Unii pași de reducere a scurgerilor la nivel scăzut pot include utilizarea sistemelor de operare specializate, utilizarea hardware-ului specializat, utilizarea instrumentelor care generează mai mult „zgomot” în timpul execuției. Consider OP și anume o întrebare despre reducerea scurgerilor de canal lateral la nivel *macro*.
fgrieu avatar
drapel ng
@mentallurg: Sunt pe deplin de acord cu „nu poți controla execuția” și restul acestui comentariu.O iau drept argument soluția pe care o conturează răspunsul și orice care duce la executarea unei singure funcții în funcție de paritatea lui `x`, ar trebui să fie de așteptat să _reducă_ variațiile de sincronizare corelate cu paritatea lui `x`, atunci când cea din `x` Răspunsul meu, când și dacă este posibil, le _elimină_ pe toate arhitecturile pe care le cunosc, indiferent de sistemul de operare, procesor, predicția ramurilor și starea microcodului care se confruntă cu cel mai recent atac.

Postează un răspuns

Majoritatea oamenilor nu înțeleg că a pune multe întrebări deblochează învățarea și îmbunătățește legătura interpersonală. În studiile lui Alison, de exemplu, deși oamenii își puteau aminti cu exactitate câte întrebări au fost puse în conversațiile lor, ei nu au intuit legătura dintre întrebări și apreciere. În patru studii, în care participanții au fost implicați în conversații ei înșiși sau au citit transcrieri ale conversațiilor altora, oamenii au avut tendința să nu realizeze că întrebarea ar influența – sau ar fi influențat – nivelul de prietenie dintre conversatori.