Dokładnie ten sam kod zwraca różne wyniki przy każdym uruchomieniu

0

Mam taki kod:

#include <cstdint>
#include <cstdio>
#include <bitset>
#include <immintrin.h>
#include<iostream>

uint32_t multiply(uint32_t a, uint32_t b)
    {
        uint32_t p = 4194311;
        uint64_t L = 1;
        uint64_t c;

        //__m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0, a), _mm_set_epi64x(0, b), 0);
        //uint64_t c = _mm_cvtsi128_si64(result);   // Extract  low 64-bits of result

        for (unsigned int i = 0; i < 32; ++i)
        {
            c ^= a & (L << i) ? (uint64_t)b << i : 0;
        }

        for (unsigned int i = 32; i --> 0; )
        {
            if (c & (L << (i + 32)))
            {
                c ^= L << (i + 32);
                c ^= (uint64_t)p << i;
            }
        }

        return (uint32_t)c;
}

I zwraca mi on za każdym razem inne wyniki dla ciągu pewnych iteracji. Problem jest w:

for (unsigned int i = 0; i < 32; ++i)
        {
            c ^= a & (L << i) ? (uint64_t)b << i : 0;
        }

Bo gdy użyję zamiennie:

__m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0, a), _mm_set_epi64x(0, b), 0);
uint64_t c = _mm_cvtsi128_si64(result);   // Extract  low 64-bits of result

To wynik za każdym razem jest taki sam. Z tą zagadkową pętlą wyniki są różne, nawet, gdy uruchamiam w konsoli ten sam skompilowany już program.

Moja hipoteza jest taka, że gdzieś następuje jakieś przepełnienie i bity są ucinane w losowych miejscach lub jakoś podmieniane. Może w (uint64_t)b jest problem? c++ nie przestaje mnie zadziwiać, choć pewnie zaraz okaże się, że znów popełniam jakiś trywialny błąd.

1

Podaj cały kod który odpalasz w tej konsoli.

0

Na przykład taki wariant:


#include <cstdint>
#include <cstdio>
#include <bitset>
#include <immintrin.h>
#include<iostream>

    uint32_t multiply(uint32_t a, uint32_t b)
    {
        uint32_t p = 4194311;
        uint64_t L = 1;
        uint64_t c;

        //__m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0, a), _mm_set_epi64x(0, b), 0);
        //uint64_t c = _mm_cvtsi128_si64(result);   // Extract  low 64-bits of result

        for (unsigned int i = 0; i < 32; ++i)
        {
            c ^= a & (L << i) ? (uint64_t)b << i : 0;
        }

        for (unsigned int i = 32; i --> 0; )
        {
            if (c & (L << (i + 32)))
            {
                c ^= L << (i + 32);
                c ^= (uint64_t)p << i;
            }
        }

        return (uint32_t)c;
}


int main()
{
    uint32_t x = 11111;
    uint32_t result;

    for(int i=0; i<200; i++)
    {
        x = multiply(x,k);
        result = x;

        std::cout << result << "\n";
    }
    return 0;
}





1

Zmieniasz x na każdym kroku pętli, to czemu się dziwisz że jest inaczej?

0

Patrzę na 200 iterację pętli. I raz wynosi ona 1937479107 a innym razem 3251208498. Cała lista liczb jest inna:

367137385
3763762252
3127281598
530243260
4138462693
2757768637
3269357401
909180422
3008579787
700006485
3917818210
84646894
2724250859
3330654675
76728767
661622947
4091892654
540099657
1215027563
3398077511
1177347046
230042163
2752230367
3517881944
3476047342
491748729
2846067145
2073924571
3640999402
3982842920
2758565123
4003874093
2680233808
1743722252
3668408817
3173480877
243881977
1502596114
1219198042
381987749
3447919387
4072254780
4160380087
3457276305
2269488404
3446867398
361328010
2382803192
2104648643
2386048884
1082616674
3945339119
1714936222
396202182
4081933038
2085246370
4089000827
2893912818
2208522848
940483012
841284968
3454455603
3498120874
1132501087
596151757
3892417342
1314626757
4130039069
3956735970
1727899821
3035428693
3896786083
402013618
4125610753
408358505
3804055563
3568176677
3317164961
1218593720
2614341043
1285444660
3174398765
716580678
2058019818
432957920
2100340859
1796173387
3521070117
3341444287
2490488847
4268323922
3459945494
1963341940
3787406681
3562009034
4105811893
2273922702
772128737
2399094513
2036694829
2037373195
1068371214
1172646007
1247978367
1323356867
775896631
3095350156
3770680350
2773064754
1391088664
3666282980
3345309804
2377268789
1693908057
596037302
3588354924
3034156807
1023774367
4041689634
376213024
2277819345
1981957920
2861960305
4014053591
2792956786
1364889218
3789189867
3196381518
3074414052
4010806892
3392513325
2770272606
3037732693
2089350736
682736385
2430749492
1296303765
991747506
387240395
1259976060
541702561
3245301783
3826075725
3233646448
689693872
4142434131
2461754516
1195499117
1537547592
1553098239
3842672367
1034685117
1763269955
3457613216
458766040
1593043401
3435598893
1773685778
214611250
23183838
178355624
1224510563
1165061401
1268176248
2225295527
1233987621
1728256961
151324733
207711097
2973392069
1575542483
1581669646
1954785511
1039093905
1206636664
5284693
3480189209
968527119
1358635856
1772136744
1316242468
1203506733
1149763957
371169561
3973770771
509196048
3736852247
2226691169
2575699832
433265097
4267036012
3905225130
2180585192
2570701107
2361294052
1214964576
1439312304
3464607670
2601556672
1590903035

Drugie uruchomienie kodu, bez ponownej kompilacji, bez zmieniania niczego w kodzie:

367137385
3289194812
1611052107
2570607531
1654701036
4058754210
1305943116
3214097463
1123911942
1722254084
899758683
1671182735
3746040009
2974404977
1590048168
1854989518
1195102056
2238687082
2503067751
1122347012
1201279562
537148695
3119845979
3809403943
1560894095
1983179333
4095111473
394884616
2359909246
3501109555
3909602311
1557335903
4088114527
3815393225
3255953767
1090645154
2662978335
3347443493
577296335
960500477
3263984142
3166868018
203108781
1250125634
1177513053
3911922883
2064582697
2654124088
1857892818
4100097062
2183006837
3472627478
2453648170
199947186
752515828
3531644198
2317663951
1319486855
248132925
1327991953
833996189
1654112255
508681708
3756608311
1516839136
3835610812
253179389
1860114898
603950257
2753364515
2170119509
3425933005
3920110435
4230778569
3001785011
158733344
1185000127
2648088091
2249386726
4053386931
2593890182
4057364263
3633245047
708741011
363768112
91593539
2081052239
1397978050
55956699
1693783139
4095681523
2900271038
1797997468
2848440880
3038056600
3578432679
1900431596
771882633
2194390215
2959516445
2933866207
2481985621
1543328157
3935187514
2231358127
2434827831
2419613204
3656688961
3926375392
1497853734
2736273973
2143754271
1596100213
3806547648
1264313775
2914963193
2437178953
778226476
3649600401
2055965130
3368966208
2950462447
577822508
215608806
2929823957
1070742314
2223603145
756957896
1556003597
4124877735
4059151316
1939022689
346779213
4172521530
1186682638
3610516093
3933831199
3199010184
888342406
3367480327
2687016080
1890866180
3717468327
543226262
362782320
4286278987
3592929050
189990853
2897672540
2413086746
3671783727
2758870497
1571519869
701380271
2663224641
3967787565
3733497663
2112892502
2194987509
1032700842
3906533528
1665371944
3042579816
3424296802
1028685068
2196885259
4263363314
1588024288
413542070
3164225447
4012895064
1614552100
3140519739
2255231218
1929623434
1954426149
754841142
1493758709
4151415055
292317925
3097834794
1097770740
3792302900
3446023943
2344675129
2987832542
1755509748
1448176945
1270475525
1677561931
3476105805
2351247734
4148724558
3909509060
2520355264
1084420897
1253719806
2602600197
396562914
3921343671

Nic nie zmieniam w kodzie a dostaję zupełnie inne wyniki, gdy go uruchamiam. Jedynie pierwsza iteracja daje zawsze to samo, chyba, bo tego też nie jestem pewien.

Korzystam z aplikacji Ubuntu na Windows, tam to kompiluję, choć piszę kod w Visual Studio. Może to w konsoli jest problem?

1

Zmniejszyłem do 2-ch aby długo nie czekać:
screenshot-20220514164539.png

0

A ja dostaję:

$. /mojprogram
367137385
4040469388
$. /mojprogram
367137385
4168303020
$. /mojprogram
367137385
4095167788
$. /mojprogram
367137385
4136574748
$. /mojprogram
367137385
3285852380
$. /mojprogram
367137385
3556439020
$. /mojprogram
367137385
4017922780
$. /mojprogram
367137385
3755137532

Docelowo kod był bardziej skomplikowany. To miał być generator liczb pseudolosowych, ale nie aż tak dobry, że dla tych samych parametrów daje inne wyniki :D

6

A no to wszystko jasne, masz nie zainicjalizowana zmienną c.

0

Faktycznie, zainicjalizowanie c rozwiązuje problem. A co się właściwie dzieje, jeśli jej nie zainicjalizuję? Za każdym razem jest inicjalizowana czym popadnie?

4
Tomasz_D napisał(a):

Faktycznie, zainicjalizowanie c rozwiązuje problem. A co się właściwie dzieje, jeśli jej nie zainicjalizuję? Za każdym razem jest inicjalizowana czym popadnie?

Przyznane jest jej jakieś miejsce w pamięci, które było wcześniej używane przez inne zmienne. W zależności od konfiguracji możesz trafić na całkiem różne wartości.

0

Na oko to jest dla mnie bug w języku programowania per se. Takie rzeczy nie powinny mieć miejsca. Ale pomijając fakt, że wiele rzeczy w c++ mi się nie podoba.

Czy pierwsze obliczenie c nie powinno czyścić tego miejsca w pamięci i umieszczać tam wyniku, dokładnie jak przy pierwszej iteracji? Jeśli tak to dlaczego akurat pierwsza iteracja jest zawsze taka sama? Nie rozumiem jak można c ^= a & (L << i) ? (uint64_t)b << i : 0; wymieszać z czymś innym, podczas, gdy jest określone, że ma to być uint64_t. Nawet, jeśli nie ma sensu trzymać pustego miejsca uint64_t, dopóki nie zostanie użyta zmienna, to jak można miejsca jej dedykowanego nie wyczyścić, gdy zostaje wywołania/użyta?

Czy takie rzeczy nie powinny zostać wyeliminowane z języka programowania w ogóle?

4

Na oko to jest dla mnie bug w języku programowania per se. Takie rzeczy nie powinny mieć miejsca.

Jest to popularny pogląd, bo to faktycznie jest błędogenne. Tym niemniej, istnieje sporo sytuacji, w których nie chcemy tracić na niepotrzebną inicjalizację zmiennej (bo planujemy ją potem zainicjalizować sami), i w takich sytuacjach zachowanie C++ ma sens.

Czy pierwsze obliczenie c nie powinno czyścić tego miejsca w pamięci i umieszczać tam wyniku, dokładnie jak przy pierwszej iteracji?

Nie powinno, bo tam już coś jest. Nie to, co chciałeś, być może, ale C++ o tym nie wie i o to nie dba — z punktu widzenia języka nie ma różnicy między przypadkowymi, a celowo wpisanymi danymi. Alternatywą byłoby trzymanie „sprawdzarki” dla każdej zmiennej, która pilnuje za programistę, czy on już coś z tą zmienną zrobił, czy nie.

Nawet, jeśli nie ma sensu trzymać pustego miejsca uint64_t, dopóki nie zostanie użyta zmienna, to jak można miejsca jej dedykowanego nie wyczyścić, gdy zostaje wywołania/użyta?

Bo czyszczenie kosztuje, a nie zawsze jest potrzebne — w szczególności, nie ma sensu czyścić przed zapisem (bo i tak zapis nadpisze to, co tam było). To tak, jakbyś chciał zawsze mieć x = 0; x = y; zamiast samego x = y.

2

Kompilatory C++ zwykle maja flagę, która wstrzymuje kompilację po wykryciu błędu typu użycie niezainicjalizowanej zmiennej, brak instrukcji return w funkcji mającej zwrócić wartość itp. W gcc to -Werror -Wall -Wextra.

Nie jest też oczywiste jaką wartość przypisać na początku, np.:

long long silnia(int n)  {
  long long wynik; // samoczynne wyzerowanie nie pomaga, trzeba podstawić jeden
  for (int i = 1; i <= n; ++i) {
    wynik *= i;
  }
  return wynik;
}
0

Ad vocem. Jeśli nie zrobimy castingu, to mogą się dziać chyba podobne rzeczy. Liczyłem przed chwilą coś takiego:

__int128 output = ((x1 ^ (h1_h0 >> 64)) << 64) | (x0 ^ (uint64_t)h1_h0);

Gdzie x0 to __int128, ale wcześniej umieściłem tam pewne liczby tylko w dolnych 64 bitach:

__int128 x0 = _mm_cvtsi128_si64(d1_d0);

I dostaję błędne wyniki. Dopiero zapisanie:

__int128 output = ((x1 ^ (h1_h0 >> 64)) << 64) | ((uint64_t)x0 ^ (uint64_t)h1_h0);

Nie powoduje pewnych dziwnych problemów. W tych dwóch przypadkach wyniki są inne. Co więcej wygląda na to, że do górnej połówki tego inta wędrują jakieś powiązane z poprzednimi wynikami bity, jeśli nie zrobimy castingu.

1

Niech ktoś jeszcze powie koledze o braku kontroli wartości indeksu przy odczycie tablicy :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1