Ogromna różnica wydajności (26x szybciej) podczas kompilacji dla 32 i 64 bitów

80

Próbowałem zmierzyć różnicę w używaniu a fori a foreachpodczas uzyskiwania dostępu do list typów wartości i typów referencyjnych.

Do profilowania użyłem następującej klasy.

public static class Benchmarker
{
    public static void Profile(string description, int iterations, Action func)
    {
        Console.Write(description);

        // Warm up
        func();

        Stopwatch watch = new Stopwatch();

        // Clean up
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        watch.Start();
        for (int i = 0; i < iterations; i++)
        {
            func();
        }
        watch.Stop();

        Console.WriteLine(" average time: {0} ms", watch.Elapsed.TotalMilliseconds / iterations);
    }
}

Użyłem doubledla mojego typu wartości. Stworzyłem tę „fałszywą klasę”, aby przetestować typy referencyjne:

class DoubleWrapper
{
    public double Value { get; set; }

    public DoubleWrapper(double value)
    {
        Value = value;
    }
}

W końcu uruchomiłem ten kod i porównałem różnice czasu.

static void Main(string[] args)
{
    int size = 1000000;
    int iterationCount = 100;

    var valueList = new List<double>(size);
    for (int i = 0; i < size; i++) 
        valueList.Add(i);

    var refList = new List<DoubleWrapper>(size);
    for (int i = 0; i < size; i++) 
        refList.Add(new DoubleWrapper(i));

    double dummy;

    Benchmarker.Profile("valueList for: ", iterationCount, () =>
    {
        double result = 0;
        for (int i = 0; i < valueList.Count; i++)
        {
             unchecked
             {
                 var temp = valueList[i];
                 result *= temp;
                 result += temp;
                 result /= temp;
                 result -= temp;
             }
        }
        dummy = result;
    });

    Benchmarker.Profile("valueList foreach: ", iterationCount, () =>
    {
        double result = 0;
        foreach (var v in valueList)
        {
            var temp = v;
            result *= temp;
            result += temp;
            result /= temp;
            result -= temp;
        }
        dummy = result;
    });

    Benchmarker.Profile("refList for: ", iterationCount, () =>
    {
        double result = 0;
        for (int i = 0; i < refList.Count; i++)
        {
            unchecked
            {
                var temp = refList[i].Value;
                result *= temp;
                result += temp;
                result /= temp;
                result -= temp;
            }
        }
        dummy = result;
    });

    Benchmarker.Profile("refList foreach: ", iterationCount, () =>
    {
        double result = 0;
        foreach (var v in refList)
        {
            unchecked
            {
                var temp = v.Value;
                result *= temp;
                result += temp;
                result /= temp;
                result -= temp;
            }
        }

        dummy = result;
    });

    SafeExit();
}

Wybrałem Releasei Any CPUopcje, uruchomiłem program i otrzymałem następujące czasy:

valueList for:  average time: 483,967938 ms
valueList foreach:  average time: 477,873079 ms
refList for:  average time: 490,524197 ms
refList foreach:  average time: 485,659557 ms
Done!

Następnie wybrałem opcje Release i x64, uruchomiłem program i otrzymałem następujące czasy:

valueList for:  average time: 16,720209 ms
valueList foreach:  average time: 15,953483 ms
refList for:  average time: 19,381077 ms
refList foreach:  average time: 18,636781 ms
Done!

Dlaczego wersja 64-bitowa jest o wiele szybsza? Spodziewałem się pewnej różnicy, ale nie czegoś tak dużego.

Nie mam dostępu do innych komputerów. Czy mógłbyś uruchomić to na swoich komputerach i przekazać mi wyniki? Używam programu Visual Studio 2015 i mam procesor Intel Core i7 930.

Oto SafeExit()metoda, dzięki której możesz samodzielnie skompilować / uruchomić:

private static void SafeExit()
{
    Console.WriteLine("Done!");
    Console.ReadLine();
    System.Environment.Exit(1);
}

Zgodnie z żądaniem, double?zamiast my DoubleWrapper:

Dowolny procesor

valueList for:  average time: 482,98116 ms
valueList foreach:  average time: 478,837701 ms
refList for:  average time: 491,075915 ms
refList foreach:  average time: 483,206072 ms
Done!

x64

valueList for:  average time: 16,393947 ms
valueList foreach:  average time: 15,87007 ms
refList for:  average time: 18,267736 ms
refList foreach:  average time: 16,496038 ms
Done!

Last but not least: utworzenie x86profilu daje mi prawie takie same rezultaty używaniaAny CPU .

Trauer
źródło
14
„Dowolny procesor”! = „32 bity”! Jeśli skompilowano „Dowolny procesor”, aplikacja powinna działać jako proces 64-bitowy w systemie 64-bitowym. Usunąłbym również kod, który bałaganił z GC. To właściwie nie pomaga.
Thorsten Dittmar
9
@ThorstenDittmar wywołania GC są wykonywane przed pomiarem, a nie w kodzie mierzonym. Jest to wystarczająco rozsądna rzecz, aby zmniejszyć stopień, w jakim szczęście w synchronizacji GC może wpłynąć na taki pomiar. Istnieje również „faworyzowanie wersji 32-bitowej” i „preferowanie wersji 64-bitowej” jako czynnik między kompilacjami.
Jon Hanna
1
@ThorstenDittmar Ale uruchamiam wersję wydania (poza Visual Studio), a Menedżer zadań mówi, że jest to aplikacja 32-bitowa (po skompilowaniu do dowolnego procesora). Również. Jak powiedział Jon Hanna, wezwanie GC jest przydatne.
Trauer
2
Której wersji środowiska wykonawczego używasz? Nowy RyuJIT w wersji 4.6 jest dużo szybszy, ale nawet w przypadku wcześniejszych wersji kompilator x64 i JITer były nowsze i bardziej zaawansowane niż wersje x32. Są w stanie wykonywać znacznie bardziej agresywne optymalizacje niż wersje x86.
Panagiotis Kanavos
2
Zwróciłbym uwagę, że ten typ wydaje się nie mieć żadnego efektu; zmieni doublesię float, longczy inti masz podobne wyniki.
Jon Hanna

Odpowiedzi:

87

Mogę to odtworzyć w 4.5.2. Brak RyuJIT tutaj. Dezasemblacje x86 i x64 wyglądają rozsądnie. Sprawdzanie zakresu i tak dalej są takie same. Ta sama podstawowa struktura. Brak rozwijania pętli.

x86 używa innego zestawu instrukcji float. Wydajność tych instrukcji wydaje się być porównywalna z instrukcją x64, z wyjątkiem podziału :

  1. 32-bitowe instrukcje typu float x87 używają wewnętrznie dokładności 10-bajtowej.
  2. Rozszerzony podział precyzji jest bardzo wolny.

Operacja dzielenia sprawia, że ​​wersja 32-bitowa jest wyjątkowo wolna. Odkomentowanie podziału wyrównuje wydajność w dużym stopniu (32 bity w dół z 430 ms do 3,25 ms).

Peter Cordes zwraca uwagę, że opóźnienia instrukcji dwóch jednostek zmiennoprzecinkowych nie są tak różne. Być może niektóre z pośrednich wyników to zdenormalizowane liczby lub NaN. Może to spowodować powolną ścieżkę w jednej z jednostek. A może wartości różnią się między dwiema implementacjami z powodu dokładności 10-bajtowej vs. 8-bajtowej.

Peter Cordes zwraca również uwagę, że wszystkie pośrednie wyniki to NaN ... Usunięcie tego problemu ( valueList.Add(i + 1)tak, aby żaden dzielnik nie był zerowy) w większości wyrównuje wyniki. Najwyraźniej 32-bitowy kod w ogóle nie lubi operandów NaN. Załóżmy wydrukować niektóre wartości pośrednich: if (i % 1000 == 0) Console.WriteLine(result);. Potwierdza to, że dane są teraz rozsądne.

Podczas testów porównawczych musisz porównać realistyczne obciążenie pracą. Ale kto by pomyślał, że niewinny podział może zepsuć twój punkt odniesienia ?!

Spróbuj po prostu zsumować liczby, aby uzyskać lepszy punkt odniesienia.

Division i modulo są zawsze bardzo powolne. Jeśli zmodyfikujesz Dictionarykod BCL, aby po prostu nie używać operatora modulo do obliczania mierzalnej poprawy wydajności indeksu zasobnika. Taki jest powolny podział.

Oto kod 32-bitowy:

wprowadź opis obrazu tutaj

64-bitowy kod (ta sama struktura, szybki podział):

wprowadź opis obrazu tutaj

Nie jest to wektoryzowane pomimo użycia instrukcji SSE.

usr
źródło
11
„Kto by pomyślał, że niewinny podział może zepsuć twój benchmark?” Zrobiłem to od razu, gdy tylko zobaczyłem podział w pętli wewnętrznej, zwł. jako część łańcucha zależności. Dzielenie jest niewinne tylko wtedy, gdy jest dzieleniem liczb całkowitych przez potęgę 2. Z tabel agner.org/optimize insn: Nehalem fdivma opóźnienie 7-27 cykli (i taką samą wzajemną przepustowość). divsdwynosi 7-22 cykli. addsdprzy opóźnieniu 3c, przepustowości 1 / c. Division to jedyna niepotokowa jednostka wykonawcza w procesorach Intel / AMD. C # JIT nie wektoryzuje pętli dla x86-64 (z divPd).
Peter Cordes
1
Poza tym, czy to normalne, że 32b C # nie używa matematyki SSE? Czy nie można korzystać z funkcji aktualnej maszyny w punkcie JIT? Tak więc w Haswell i późniejszych, może automatycznie wektoryzować pętle liczb całkowitych za pomocą 256b AVX2, zamiast tylko SSE. Aby uzyskać wektoryzację pętli FP, myślę, że musiałbyś napisać je równolegle z takimi rzeczami jak 4 akumulatory, ponieważ matematyka FP nie jest asocjacyjna. W każdym razie, używanie SSE w trybie 32-bitowym jest szybsze, ponieważ masz mniej instrukcji do wykonania tej samej pracy skalarnej, gdy nie musisz żonglować stosem x87 FP.
Peter Cordes
4
W każdym razie div jest bardzo powolny, ale 10B x87 fdiv nie jest dużo wolniejszy niż 8B SSE2, więc to nie wyjaśnia różnicy między x86 i x86-64. To, co mogłoby to wyjaśnić, to wyjątki FPU lub spowolnienia z denormalami / nieskończonościami. Słowo sterujące x87 FPU jest oddzielone od rejestru sterującego zaokrąglaniem / wyjątkami SSE ( MXCSR). NaNMogę pomyśleć, że różne traktowanie denormali lub sów wyjaśnia czynnik 26 perf diff. C # może ustawić denormals-are-zero w MXCSR.
Peter Cordes
2
@Trauer i usr: Właśnie zauważyłem, że valueList[i] = izaczynając od i=0, tak działa pierwsza iteracja pętli 0.0 / 0.0. Zatem każda operacja w całym benchmarku jest wykonywana za pomocą NaNs. Ten podział wygląda coraz mniej niewinnie! Nie jestem ekspertem w zakresie wydajności z NaNs, ani różnicy między x87 i SSE w tym przypadku, ale myślę, że to wyjaśnia różnicę 26x perf. Założę się, że twoje wyniki będą znacznie bliższe między 32 a 64 bitami, jeśli zainicjujesz valueList[i] = i+1.
Peter Cordes
1
Jeśli chodzi o równorzędny do zera, nie jestem zbytnio zainteresowany tym z 64-bitowym podwójnym, ale kiedy 80-bitowe rozszerzone i 64-bitowe podwójne są używane razem, sytuacje, w których 80-bitowa wartość może niedopełnić, a następnie zostać wystarczająco skalowana uzyskanie wartości, którą można by przedstawić jako 64-bitowe, doublebyłoby dość rzadkie. Jednym z głównych wzorców użycia typu 80-bitowego było umożliwienie sumowania wielu liczb bez konieczności ścisłego zaokrąglania wyników aż do samego końca. Zgodnie z tym schematem przepełnienia po prostu nie stanowią problemu.
supercat
31

valueList[i] = i, zaczynając od i=0, tak jak w pierwszej iteracji pętli 0.0 / 0.0. Zatem każda operacja w całym benchmarku jest wykonywana za pomocą NaNs.

Jak pokazał @usr w danych wyjściowych deasemblacji , wersja 32-bitowa wykorzystywała zmiennoprzecinkowe x87, podczas gdy 64-bitowa korzystała ze zmiennoprzecinkowych SSE.

Nie jestem ekspertem w zakresie wydajności z NaNs, ani różnicy między x87 i SSE w tym przypadku, ale myślę, że to wyjaśnia różnicę 26x perf. Założę się, że twoje wyniki będą znacznie bliższe między 32 a 64 bitami, jeśli zainicjujesz valueList[i] = i+1. (aktualizacja: usr potwierdził, że dzięki temu wydajność 32- i 64-bitowa była dość bliska.)

Podział jest bardzo powolny w porównaniu z innymi operacjami. Zobacz moje komentarze dotyczące odpowiedzi @ usr. Zobacz także http://agner.org/optimize/, gdzie znajdziesz mnóstwo świetnych informacji na temat sprzętu oraz optymalizacji ASM i C / C ++, niektóre z nich dotyczą C #. Ma tabele instrukcji dotyczące opóźnień i przepustowości dla większości instrukcji dla wszystkich najnowszych procesorów x86.

Jednak 10B x87 fdivnie jest dużo wolniejsze niż podwójna precyzja 8B SSE2 divsddla wartości normalnych. IDK na temat różnic w perf z NaN, nieskończoności lub denormali.

Mają jednak różne kontrolki dla tego, co dzieje się z NaN i innymi wyjątkami FPU. Słowo kontrolne FPU x87 jest oddzielona od rejestru kontrolnego zaokrąglenie / wyjątkiem SSE (MXCSR). Jeśli x87 otrzymuje wyjątek CPU dla każdego działu, ale SSE nie, to łatwo wyjaśnia współczynnik 26. A może po prostu jest tak duża różnica w wydajności podczas obsługi NaN. Sprzęt nie jest zoptymalizowany do przechodzenia NaNpo NaN.

IDK, jeśli SSE kontroluje unikanie spowolnień z denormali, wejdzie tutaj w grę, ponieważ wierzę, że resultbędzie przez NaNcały czas. IDK, jeśli C # ustawia flagę denormals-are-zero w MXCSR lub flagę flush-to-zero-(która zapisuje zera w pierwszej kolejności, zamiast traktować denormals jako zero podczas odczytu z powrotem).

Znalazłem artykuł Intela o kontrolkach zmiennoprzecinkowych SSE, porównując go ze słowem kontrolnym x87 FPU. Nie ma jednak wiele do powiedzenia NaN. Kończy się tym:

Wniosek

Aby uniknąć problemów z serializacją i wydajnością z powodu denormali i niedomiarów, użyj instrukcji SSE i SSE2, aby ustawić tryby Flush-to-Zero i Denormals-Are-Zero w sprzęcie, aby zapewnić najwyższą wydajność dla aplikacji zmiennoprzecinkowych.

IDK, jeśli to pomaga komukolwiek z dzieleniem przez zero.

for vs. foreach

Może być interesujące przetestowanie treści pętli, która ma ograniczoną przepustowość, a nie jest tylko pojedynczym łańcuchem zależności przenoszonym w pętli. W obecnej sytuacji cała praca zależy od wcześniejszych wyników; CPU nie ma nic do zrobienia równolegle (poza bounds-check następnym ładowaniem tablicy, gdy działa łańcuch mul / div).

Możesz zauważyć większą różnicę między metodami, jeśli „rzeczywista praca” zajęła więcej zasobów wykonawczych procesora. Ponadto w przypadku Intel przed Sandybridge istnieje duża różnica między dopasowaniem pętli w buforze pętli 28 uop lub nie. Jeśli nie, otrzymasz instrukcje dekodowania wąskich gardeł, zwł. gdy średnia długość instrukcji jest dłuższa (co zdarza się w przypadku SSE). Instrukcje dekodujące do więcej niż jednego uop również ograniczają przepustowość dekodera, chyba że występują we wzorcu, który jest przyjemny dla dekoderów (np. 2-1-1). Tak więc pętla z większą liczbą instrukcji dotyczących narzutu pętli może stanowić różnicę między dopasowaniem pętli w 28-wejściowej pamięci podręcznej uop, czy nie, co jest wielką sprawą w Nehalem, a czasami jest pomocne w Sandybridge i później.

Peter Cordes
źródło
Nigdy nie miałem przypadku, w którym zauważyłem jakąkolwiek różnicę w wydajności w zależności od tego, czy NaN były w moim strumieniu danych, ale obecność zdenormalizowanych liczb może mieć ogromny wpływ na wydajność. W tym przykładzie wydaje się, że tak nie jest, ale warto o tym pamiętać.
Jason R
@JasonR: Czy to tylko dlatego, NaNże w praktyce są naprawdę rzadkie? Zostawiłem wszystkie rzeczy dotyczące denormali i link do materiałów Intela, głównie dla dobra czytelników, nie dlatego, że myślałem, że będzie to naprawdę miało duży wpływ na ten konkretny przypadek.
Peter Cordes
W większości zastosowań są rzadkie. Jednak podczas opracowywania nowego oprogramowania wykorzystującego zmiennoprzecinkowe nierzadko zdarza się, że błędy implementacji generują strumienie NaN zamiast pożądanych wyników! Przyszło mi to do głowy wiele razy i nie przypominam sobie żadnego zauważalnego wzrostu wydajności, gdy pojawiają się NaN. Zauważyłem coś przeciwnego, gdy robię coś, co powoduje pojawienie się denormali; zazwyczaj powoduje to natychmiast zauważalny spadek wydajności. Zauważ, że są one oparte na moich anegdotycznych doświadczeniach; może wystąpić pewien spadek wydajności w przypadku NaN, którego po prostu nie zauważyłem.
Jason R
@JasonR: IDK, może NaNs nie są dużo, jeśli w ogóle, wolniejsze w SSE. Najwyraźniej to duży problem dla x87. Semantyka SSE FP została zaprojektowana przez firmę Intel w dniach PII / PIII. Te procesory mają pod maską tę samą niesprawną maszynerię, co obecne konstrukcje, więc przypuszczalnie podczas projektowania SSE miały na uwadze wysoką wydajność dla P6. (Tak, Skylake jest oparty na mikroarchitekturze P6. Niektóre rzeczy się zmieniły, ale nadal dekoduje do Uops i planuje je do portów wykonywania z buforem ponownego zamówienia). Semantyka x87 została zaprojektowana dla opcjonalnego zewnętrznego układu koprocesora dla skalarny procesor w kolejności.
Peter Cordes
@PeterCordes Wywołanie Skylake chipem opartym na P6 jest zbyt trudne. 1) FPU został (prawie) całkowicie przeprojektowany w erze Sandy Bridge, więc stary FPU P6 jest praktycznie nieaktualny do dziś; 2) dekodowanie x86 na uop miało krytyczną modyfikację w erze Core2: podczas gdy poprzednie projekty dekodowały instrukcje obliczeniowe i pamięci jako oddzielne Uops, układ Core2 + ma Uops składający się z instrukcji obliczeniowej i operatora pamięci. Doprowadziło to do znacznego zwiększenia wydajności i sprawności energetycznej kosztem bardziej złożonej konstrukcji i potencjalnie niższej częstotliwości szczytowej.
shodanshok
1

Mamy obserwację, że 99,9% wszystkich operacji zmiennoprzecinkowych będzie dotyczyło NaN, co jest co najmniej bardzo nietypowe (odkryte najpierw przez Petera Cordesa). Mamy inny eksperyment usr, który wykazał, że usunięcie instrukcji dzielenia prawie całkowicie znika różnicę czasu.

Faktem jest jednak, że NaN są generowane tylko dlatego, że pierwszy podział oblicza 0,0 / 0,0, co daje początkowy NaN. Jeśli podział nie zostanie wykonany, wynik zawsze będzie wynosił 0,0, a zawsze będziemy obliczać 0,0 * temp -> 0,0, 0,0 + temp -> temp, temp - temp = 0,0. Zatem usunięcie podziału nie tylko usunęło podziały, ale także usunęło NaN. Spodziewałbym się, że naN są w rzeczywistości problemem i że jedna implementacja radzi sobie z NaN bardzo wolno, podczas gdy druga nie ma problemu.

Warto byłoby rozpocząć pętlę od i = 1 i ponownie zmierzyć. Cztery operacje skutkują * temp, + temp, / temp, - temp efektywnie dodając (1 - temp), więc nie mielibyśmy żadnych nietypowych liczb (0, nieskończoność, NaN) dla większości operacji.

Jedynym problemem może być to, że dzielenie zawsze daje wynik w postaci liczby całkowitej, a niektóre implementacje dzielenia mają skróty, gdy poprawny wynik nie używa wielu bitów. Na przykład, podzielenie 310,0 / 31,0 daje 10,0 jako pierwsze cztery bity z resztą 0,0, a niektóre implementacje mogą przestać oceniać pozostałe 50 lub więcej bitów, podczas gdy inne nie. Jeśli istnieje istotna różnica, rozpoczęcie pętli z wynikiem = 1,0 / 3,0 spowodowałoby różnicę.

gnasher729
źródło
-2

Może być kilka powodów, dla których to działa szybciej w wersji 64-bitowej na twoim komputerze. Powodem, dla którego zapytałem, którego procesora używasz, był fakt, że kiedy 64-bitowe procesory pojawiły się po raz pierwszy, AMD i Intel miały różne mechanizmy do obsługi 64-bitowego kodu.

Architektura procesora:

Architektura procesora Intela była czysto 64-bitowa. Aby wykonać kod 32-bitowy, instrukcje 32-bitowe musiały zostać przekonwertowane (wewnątrz procesora) na instrukcje 64-bitowe przed wykonaniem.

Architektura procesora AMD polegała na zbudowaniu 64-bitowej architektury tuż nad architekturą 32-bitową; to znaczy, że była to zasadniczo architektura 32-bitowa z rozszerzeniami 64-bitowymi - nie było procesu konwersji kodu.

Było to oczywiście kilka lat temu, więc nie mam pojęcia, czy / jak zmieniła się technologia, ale zasadniczo można oczekiwać, że 64-bitowy kod będzie działał lepiej na maszynie 64-bitowej, ponieważ procesor jest w stanie pracować z podwójną ilością bity na instrukcję.

.NET JIT

Argumentuje się, że .NET (i inne języki zarządzane, takie jak Java) mogą przewyższać języki takie jak C ++ ze względu na sposób, w jaki kompilator JIT jest w stanie zoptymalizować kod zgodnie z architekturą procesora. W związku z tym może się okazać, że kompilator JIT wykorzystuje coś w architekturze 64-bitowej, co prawdopodobnie nie było dostępne lub wymagało obejścia, gdy jest wykonywane w wersji 32-bitowej.

Uwaga:

Zamiast używać DoubleWrapper, czy rozważałeś użycie Nullable<double>lub skróconą składnię: double?- Chciałbym sprawdzić, czy ma to jakikolwiek wpływ na twoje testy.

Uwaga 2: Niektórzy ludzie mylą moje komentarze na temat architektury 64-bitowej z IA-64. Dla wyjaśnienia, w mojej odpowiedzi 64-bitowy odnosi się do x86-64, a 32-bitowy odnosi się do x86-32. Nic tutaj nie odnosi się do IA-64!

Matthew Layton
źródło
4
OK, więc dlaczego jest 26x szybszy? Nie mogę znaleźć tego w odpowiedzi.
usr
2
Domyślam się, że to różnice jittera, ale nie więcej niż zgadywanie.
Jon Hanna
2
@seriesOne: Myślę, że MSalters próbuje powiedzieć, że mieszasz IA-64 z x86-64. (Intel używa również IA-32e dla x86-64 w swoich podręcznikach). Procesory do komputerów stacjonarnych każdego użytkownika to x86-64. Itanic zatonął kilka lat temu i myślę, że był używany głównie w serwerach, a nie w stacjach roboczych. Core2 (pierwszy procesor z rodziny P6 obsługujący tryb długi x86-64) w rzeczywistości ma pewne ograniczenia w trybie 64-bitowym. np. uop macro-fusion działa tylko w trybie 32-bitowym. Intel i AMD zrobiły to samo: rozszerzyły swoje projekty 32-bitowe do 64-bitowego.
Peter Cordes
1
@PeterCordes gdzie wspomniałem IA-64? Zdaję sobie sprawę, że procesory Itanium były zupełnie innym projektem i zestawem instrukcji; wczesne modele oznaczone jako EPIC lub Explicitly Parallel Instruction Computing. Myślę, że MSalters łączy 64-bitowe i IA-64. Moja odpowiedź jest prawdziwa dla x86-64 architecture- nie było nic tam przedstawieniu rodzinę procesorów Itanium
Matthew Layton
2
@ series0ne: Ok, więc twój akapit o procesorach Intela jako „czysto 64-bitowych” jest kompletnym nonsensem. Założyłem, że myślisz o IA-64, ponieważ wtedy nie byłbyś całkowicie w błędzie. Nigdy nie było dodatkowego etapu tłumaczenia dla uruchomienia kodu 32-bitowego. Dekodery x86-> uop mają tylko dwa podobne tryby: x86 i x86-64. Intel zbudował 64-bitowy P4 na szczycie P4. 64-bitowy Core2 zawierał wiele innych ulepszeń architektonicznych w porównaniu z Core i Pentium M, ale rzeczy, takie jak makro-fuzja działająca tylko w trybie 32-bitowym, pokazują, że 64-bitowy był przykręcony. (dość wcześnie w procesie projektowania, ale nadal.)
Peter Cordes