tryb dual channel - wykorzystanie

0

Jak to jest z tym dual channelem - kiedy to działa a kiedy nie?

Zauważyłem że niektóre programy testowe podwajają szybkość pamięci a inne prawie wcale (może trochę, tak do 5%).
Zatem to musi być zależeć od algorytmu, może opcji kompilacji, czyli instrukcji lub sposobu kodowania niektórych operacji.

Kopiowanie pamięci można wykonać na kilka sposobów:

  1. tradycyjne mov, zwykle z rep.
  2. z rozwinięciem, tz. używamy kilka rejestrów a nie tylko dwa:

prosta wersja:

void copy(dst, src, n)
{
  while( --n >= 0 ) *dst++ = *src++;
}

teraz z rozwinięciem:

void copy(dst, src, n2)
{
  s1 = src+1; d1 = dst+1;

  while( --n2 >= 0 ) 
   {
     *dst = *src; dst+=2; src+=2;
     *d1 = *s1; d1+=2; s1+=2;
   }
} 

To powinno zasuwać równolegle (można bardziej rozwinąć, np. x 4)

  1. to samo ale za pomocą instrukcji SSE

Być może o to tu chodzi?
Zwykle nie rozwijamy kodu w taki sposób, więc to w ogóle nie używa możliwości dual channel i stąd marne przyspieszania w tradycyjnych benchmarkach (one chyba testują jedynie szybkości pojedynczego kanału, a nie przepustowość pamięci w ogóle).

0

Trochę do d**y to równoległe...
kopiujemy zwyczajne ale po dwa, cztery, w pętli:

void copy(dst, src, n)
{
  fore(int i = 0 ; i < n; i++) 
   {
     dst[i] = src[i]; i++;
     dst[i] = src[i];      
   }
} 
0

Użyj memcpy zamiast bawić się w swoje cuda, a następnie zrób wersję wielowątkową i odpal tyle wątków ile naraz obsługuje procesor.

Ewentualnie dodatkowo użyj asmlib: http://www.agner.org/optimize/#asmlib

0

Marne żarty - to jest memcpy, a raczej fragment.

Tam mają być kęsy po 64bit w tych tablicach, bo jedna szyna ma 64 bity;
i podwajamy to, po to żeby dwie szyny naraz zasuwały - w trybie dual.

Mówiłem że Everest podwaja szybkość, a inne testery nie - dlaczego?
Pewnie dlatego że te inne używają memcpy z biblioteki...

Anger podobnie to robi, ale mi chodzi o złapanie tego duala za jaja, a nie o taką zwyczajną optymalizację.

0

Ale jakie kęsy? Domyślnie wszystko przelatuje przez pamięć podręczną, a tam pojedyncza linia pamięci podręcznej to 64 bajty (nie bity).

Jeśli kopiujesz po jednym bajcie i np skopiowanie tego bajtu zajmuje jeden cykl (optymistyczne założenie jeśli robisz to w najprostszy sposób) to przy taktowaniu 3 GHz otrzymasz 3 GB/s przepływności, co jest dużo mniejsze niż przepływność jednego kanału.

Jeden kanał DDR 800 MHz daje 6.4 GB/s przepływności.

Nawet jeśli udałoby ci się wysycić przepływność to i tak nie jest powiedziane, że dany procesor jest w stanie skierować całą przepływność pamięci na jeden wątek. Najlepiej jest po prostu odpalić zoptymalizowane memcpy na tylu wątkach ile procesor naraz obsługuje i dopiero wtedy sprawdzić przepływność.

0
Wibowit napisał(a):

Ale jakie kęsy? Domyślnie wszystko przelatuje przez pamięć podręczną, a tam pojedyncza linia pamięci podręcznej to 64 bajty (nie bity).

int64 *src;
tu są np. te kęsy po 64 bity.

No a z tym cache to chyba wyjdzie i tak prędkość ram dla dużych ilości (trochę mniej z uwagi na bezurzyteczne operacje ładowania do tego cache...).

Wibowit napisał(a):

Jeśli kopiujesz po jednym bajcie i np skopiowanie tego bajtu zajmuje jeden cykl (optymistyczne założenie jeśli robisz to w najprostszy sposób) to przy taktowaniu 3 GHz otrzymasz 3 GB/s przepływności, co jest dużo mniejsze niż przepływność jednego kanału.
Jeden kanał DDR 800 MHz daje 6.4 GB/s przepływności.

Nie sądzę. Po jedym bajcie nie da rady transferować szynami - to i tak zasuwa po 64bit, a w dual nawet po 128 w porywach, ale ciężko wyczuć kiedy (może tylko na SSE tak idzie).

</quote>Nawet jeśli udałoby ci się wysycić przepływność to i tak nie jest powiedziane, że dany procesor jest w stanie skierować całą przepływność pamięci na jeden wątek. Najlepiej jest po prostu odpalić zoptymalizowane memcpy na tylu wątkach ile procesor naraz obsługuje i dopiero wtedy sprawdzić przepływność.</quote>
Przestań... pewnie samo odpalenie tych wątków trwałoby dłużej od kopiowania, a wejdzie jeszcze synchronizacja i inne sprawy.

0

Nie sądzę. Po jedym bajcie nie da rady transferować szynami - to i tak zasuwa po 64bit, a w dual nawet po 128 w porywach, ale ciężko wyczuć kiedy (może tylko na SSE tak idzie).

W żadnym miejscu nie masz dostępu do szyn bezpośrednio, no chyba, że przez pewne instrukcje SSE jak np MOVNTQ. Ale jeśli je pominąć i zostać przy normalnych instrukcjach to:

  • wszystko przelatuje przez pamięć podręczną i jest ładowane w kawałkach 16 bajtowych lub większych,
  • CPU i kontroler pamięci muszą zapewnić spójność danych, czyli wykrywać sytuacje w których ten sam obszar pamięci jest wykorzystywany przez różne rdzenie lub jest w różnych poziomach pamięci podręcznej,
  • żeby dobić się do losowego miejsca w pamięci procesor musi zrobić wiele rzeczy, np przetłumaczyć adres z logicznego na fizyczny z użyciem TLB (i ewentualnie softwarowego fallbacka), sprawdzić uprawnienia, wysłać komendy do kontrolera pamięci; kontroler znowu musi odpalić wiele komend na RAMie, np otworzyć konkretne strony pamięci do odczytu, itp itd to wszystko ma jakieś opóźnienie (na RAMach są opóźnienia CAS, RAS, etc),
  • dzięki temu że procesor operuje całymi liniami pamięci, a nie pojedynczymi bajtami to wiele opóźnień i narzutu jest zredukowanych, oczywiście o ile jest wystarczająca ilość trafień,
  • od dobrych paru lat procesory mają hardware prefetching, a więc sprzętowe układy, które wykrywają, że np ładujesz pamięć sekwencyjnie i wtedy ładują ją nieco wprzód, tzn na chwilę zanim kod będzie je przetwarzał,
  • dodatkowo mają np write coalescing, więc jeśli kilka instrukcji zapisuje do tej samej lini pamięci i całą ją zapełniają, to procesor nie odczytuje jej oryginalnej zawartości (co jest logiczne, bo jest cała nadpisana),
  • itd

To wszystko sprawia, że nie da się rzucać żadnych 'kęsów', ani 'chwytać za jaja' czegokolwiek.

Przestań... pewnie samo odpalenie tych wątków trwałoby dłużej od kopiowania, a wejdzie jeszcze synchronizacja i inne sprawy.

Przecież odpalenie wątku znowu takie czasochłonne nie jest. No chyba że kopiujesz mało. Żeby dobrze przetestować wydajność to i tak pasuje odpalić program przynajmniej na kilka sekund, a w ciągu kilku sekund można odpalić tysiące albo i miliony wątków.

0

No i co z tego, że tak bardzo sobie komplikujesz życie wchodząc w techniczne detale, skoro Everest i tak wykazuje w testach prawie 2 razy większą wydajność na dualu, a inne, np. SiSandra lub HwInfo praktycznie wcale - może z 5% tylko?

Te testy są i tak niewiele warte ponieważ programy zwykle nie przerzucają ton zwartych obszarów pamięci, lecz wykonują miliony przerzutów niewielkich danych, które są w całkowicie różnych obszarach ramu (może z wyjątkiem grafiki, bo akurat tu sytuacja jest niemal identyczna z tym co robią te benchmarki)

Takie test szybkości ramu należy wykonywać z pominięciem cache, bo inaczej nie byłby to test ramu.
Zresztą szybkości cache również można testować - osobno każdy poziom.

Przecież odpalenie wątku znowu takie czasochłonne nie jest. No chyba że kopiujesz mało. Żeby dobrze przetestować wydajność to i tak pasuje odpalić program przynajmniej na kilka sekund, a w ciągu kilku sekund można odpalić tysiące albo i miliony wątków.

Pewnie że to jest czasochłonne, i przekonałem się o tym własnoręcznie.
Kiedyś zrobiłem w programie funkcję, która robiła coś tam w tle, zależnie co użytkownik wybierze - kliknie.

I najpierw odpalałem tam nowy wątek po każdej zmianie, no i zamulenie było wręcz kosmiczne!

Wówczas zamiast odpalać ten wątek od nowa za każdym, po prostu trzymałem jeden w zawieszeniu na jakimś tam muteksie... czy innym evencie,
a po kliknięciu zmieniałem tylko odpowiednio te eventy.
Teraz zasuwało to ze 1000 razy szybciej - praktycznie zero opóźnień.

Była też trzecia wersja:
trzymałem stale jeden wątek ale używałem suspend/resume, no i to również strasznie wolno działało.

Pecet ma zwykle około 4GB - ile może trwać kopiowanie takiej ilości pamięci?
DDR3 ma przecież szybkość rzędu 10GB/s...

W praktyce, tj. w zwyczajnych programach, byłoby znacznie mniej.
Np. taki program do gry w szachy, który oblicza te ruchu na jakichś drzewach, wykazałby pewnie szybkość z 2GB/s, a może i mniej.

0

Mój netbook to MSI Wind U270, czyli AMD E-350 (2 x 1.6 GHz) + 4 GiB RAM 1333 Mhz single channel.

W ciągu 10s Java u mnie na netbooku jest w stanie stworzyć 26k wątków:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;


public class Main {

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>();
        long timeStart = System.nanoTime();
        final AtomicInteger sum = new AtomicInteger(0);
        while (System.nanoTime() - timeStart < 10000000000L) {
            Thread thread = new Thread() {

                @Override
                public void run() {
                    sum.incrementAndGet();
                }
            };
            thread.start();
        }
        System.out.println("Threads created: " + sum);
        long joiningStart = System.nanoTime();
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println("Joining time: " + (System.nanoTime() - joiningStart));
    }
}

Output z NetBeansa:

run:
Threads created: 26552
Joining time: 597812
BUILD SUCCESSFUL (total time: 10 seconds)

Na tym samym netbooku porównałem sobie szybkość działania kopiowania jednowątkowego i dwuwątkowego i chociaż mam single channel to i tak dwa wątki są szybsze niż jeden:

public class Main {
    
    static long[] a = new long[12345678];
    static long[] b = new long[12345678];
    static long[] c = new long[12345678];
    static long[] d = new long[12345678];

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {

            @Override
            public void run() {
                for (int i = 0; i < 123; i++) {
                    System.arraycopy(a, 0, b, 0, 12345678);
                    System.arraycopy(c, 0, d, 0, 12345678);
                }
            }
            
        };
        {
            long time = System.nanoTime();
            thread.start();
            thread.join();
            System.out.println("Single thread time: " + (System.nanoTime() - time));
        }
        Thread thread1 = new Thread() {

            @Override
            public void run() {
                for (int i = 0; i < 123; i++) {
                    System.arraycopy(a, 0, b, 0, 12345678);
                }
            }
            
        };
        Thread thread2 = new Thread() {

            @Override
            public void run() {
                for (int i = 0; i < 123; i++) {
                    System.arraycopy(c, 0, d, 0, 12345678);
                }
            }
        };
        {
            long time = System.nanoTime();
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println("Dual thread time: " + (System.nanoTime() - time));
        }
    }
}

Output w NetBeansie:

run:
Single thread time: 22287855059
Dual thread time: 19787626411
BUILD SUCCESSFUL (total time: 43 seconds)

Wynik dość słaby, ale to przecież nie jest wyczynowy sprzęt. Może w następnym tygodniu potestuję na stacjonarce jak sobie przypomnę. W każdym razie zysk z wielowątkowości jest nawet na jednokanałowej pamięci. A więc żeby maksymalnie wykorzystać przepustowość pamięci warto odpalić wiele wątków.

0

Tamte wszystkie wątki nie zostały uruchomione, znaczy większość z nich nie wykonało ani kawałeczka kodu - jedynie system sobie sobie je zarejestrował i one dopiero czekają na swój czas.

Już gotowe, wcześniej utworzone zadania, są przełączane co kilka mikrosekund, albo nawet są tu milisekundy, więc jest niemożliwe odpalenie wielu w 1s.
10s / 25000 = 0.0004 = 0.4 ms

Musiałbyś coś robić w samym wątku, np. zwiększać licznik: n++, i ta liczba pokaże ile faktycznie uruchomiono.

A w tym drugim algorytmie wyjdzie zwykle nieco szybciej, ale pod warunkiem, że masz kilka procesorów, czy rdzeni.
Na jednym będzie wolniej.

0
wil napisał(a):

Tamte wszystkie wątki nie zostały uruchomione, znaczy większość z nich nie wykonało ani kawałeczka kodu - jedynie system sobie sobie je zarejestrował i one dopiero czekają na swój czas.

Już gotowe, wcześniej utworzone zadania, są przełączane co kilka mikrosekund, albo nawet są tu milisekundy, więc jest niemożliwe odpalenie wielu w 1s.
10s / 25000 = 0.0004 = 0.4 ms

Musiałbyś coś robić w samym wątku, np. zwiększać licznik: n++, i ta liczba pokaże ile faktycznie uruchomiono.

Te wątki coś robią, a dokładnie to zwiększają licznik. Następnie czekam na zakończenie wszystkich tych wątków. Sumarycznie w ciągu 10s:

  • odpalam 26k wątków,
  • każdy z tych wątków zwiększa synchronizowany licznik o jeden,
  • czekam na zakończenie tych 26k wątków,
  • wszystko się dzieje z poziomu Javy,
  • na słabym netbooku,
wil napisał(a):

A w tym drugim algorytmie wyjdzie zwykle nieco szybciej, ale pod warunkiem, że masz kilka procesorów, czy rdzeni.
Na jednym będzie wolniej.

No to wiadomo, ale i tak jednordzeniowe procki są z reguły tak słabe, że dual-channela nie wykorzystają. No chyba, że z bardzo wolno taktowanymi pamięciami.

0

Poprawka:
W ciągu 10.5s, bo joinowanie, czyli czekanie na zakończenie wszystkich niezakończonych wątków i uśmiercenie wszystkich zabrało 0.5s.

0

Możliwe, i dlatego tak wolno to działa: 2500 inkrementów / s, czyli wychodzi z milion taktów procesora na jedną prostą operację,
co daje współczynnik efektywności 1/milion!

Ja miałem zdecydowanie więcej kodu w wątkach - z odczytem dysków i wiele różnych obliczeń, i robił to zwyczajny pentium 4, czyli praktycznie zero optymalizacji wielowątkowej, więc było znacznie gorzej.

Dual nie zależy od liczby rdzeni - na jednordzeniowym to samo otrzymasz.

Może w tych nowych amd apu wyjdzie więcej, bo tam jest zupełnie inna technologia.
Np. apu A8 ma 4 rdzenie, przepustowość ramu z 12GB/s, więc w dual 24GB/s, ale zakładając 100% równoległości, a praktyce jest przecież tylko do 5%!

Zatem dla gier 3D byłby to raczej marny wynik, a jednak te apu mają dość dobrą wydajność w grach, więc przepustowość musi być większa;
może tak tu jest: 4 x 12 = 48 GB/s, czyli 200% efektywności trybu dual, hehe!

http://www.tomshardware.com/reviews/memory-bandwidth-scaling-trinity,3419.html

0

No widzisz, przeszli z DDR3 1600 MHz na DDR3 2400 MHz i dało to wzrost wydajności na poziomie ledwo kilkunastu procent. I to bez zmiany ilości kanałów.

Jeden kanał DDR3 2133 MHz daje 17.1 GB/s, czyli więcej niż kontroler Trinity był w stanie osiągnąć w tym teście w ogóle, więc logiczne, że dual nie powinien tu nic dać przy takich prędkościach RAMu (skoro, jeszcze raz napiszę, kontroler nie nadąża nawet za single 2133 MHz). Najpierw trzeba poszukać procka z wydajnym kontrolerem pamięci, potem można testować czy dual na nim daje kopa. Albo zamiast tego obniż taktowanie pamięci do niskich wartości i wtedy testuj, bo prawdopodobnie wtedy kontroler sobie poradzi.

Możliwe, i dlatego tak wolno to działa: 2500 inkrementów / s, czyli wychodzi z milion taktów procesora na jedną prostą operację,
co daje współczynnik efektywności 1/milion!

Ja miałem zdecydowanie więcej kodu w wątkach - z odczytem dysków i wiele różnych obliczeń, i robił to zwyczajny pentium 4, czyli praktycznie zero optymalizacji wielowątkowej, więc było znacznie gorzej.

Dual nie zależy od liczby rdzeni - na jednordzeniowym to samo otrzymasz.

Zdecyduj się w końcu co chcesz testować.

Jak chcesz najwyższą wydajność to i tak zrównoleglasz algorytm i wtedy nie ma sensu testować na jednym rdzeniu, bo po co testować w innych warunkach niż docelowe?

Jeśli na procku X zysk z dual-channel wynosi 5%, a na procku Y zysk wynosi 30% to jaki z tego wniosek?

I milion taktów to narzut na stworzenie jednego wątku, a nie czas wykonania jednej instrukcji. Wcześniej zrobiłem kod który odpalał dwa wątki i tam narzut sumarycznie był pomijalny, natomiast wzrost wydajności odczuwalny.

0
Wibowit napisał(a):

No widzisz, przeszli z DDR3 1600 MHz na DDR3 2400 MHz i dało to wzrost wydajności na poziomie ledwo kilkunastu procent. I to bez zmiany ilości kanałów.

Jeden kanał DDR3 2133 MHz daje 17.1 GB/s, czyli więcej niż kontroler Trinity był w stanie osiągnąć w tym teście w ogóle, więc logiczne, że dual nie powinien tu nic dać przy takich prędkościach RAMu (skoro, jeszcze raz napiszę, kontroler nie nadąża nawet za single 2133 MHz). Najpierw trzeba poszukać procka z wydajnym kontrolerem pamięci, potem można testować czy dual na nim daje kopa. Albo zamiast tego obniż taktowanie pamięci do niskich wartości i wtedy testuj, bo prawdopodobnie wtedy kontroler sobie poradzi.

Przecież tam są coraz większe opóźnienie:
2400MHz = 10,12,11, 30
1600MHz = 9, 9, 9, 24

i to w sumie daje niewielki wzrost dla 2400MHz.
W zasadzie tam jest cały czas ten sam ram, ale tylko różnie konfigurowany, i lekko przetaktowywany...

Procesory Intela mają większą przepustowość samego ramu, ale to nic nie daje: grafika jest tam około dwa razy wolniejsza od tej z amd apu.

Intele stosują starszą technologię - grafika jest tam nadal na płycie, i tu pewnie pamięć jedzie tylko o te 5% w dualu,
a w apu wsadzili to do procesorów i tam zasuwa na 200% (dla 4 rdzeni - każdy ma swój dostęp do ramu, czyli tam są chyba wbudowane 4 układy pcie).

Wibowit napisał(a):

Zdecyduj się w końcu co chcesz testować.

Jak chcesz najwyższą wydajność to i tak zrównoleglasz algorytm i wtedy nie ma sensu testować na jednym rdzeniu, bo po co testować w innych warunkach niż docelowe?

Jeśli na procku X zysk z dual-channel wynosi 5%, a na procku Y zysk wynosi 30% to jaki z tego wniosek?

I milion taktów to narzut na stworzenie jednego wątku, a nie czas wykonania jednej instrukcji.
Wcześniej zrobiłem kod który odpalał dwa wątki i tam narzut sumarycznie był pomijalny, natomiast wzrost wydajności odczuwalny.

Nie, na wszystkich jest zwykle do 5%, ale można dużo więcej - poprzez zmianę samego kodu, a nie sprzętu (pomijając te graficzne nowinki amd).

0

Przecież tam są coraz większe opóźnienie:
2400MHz = 10,12,11, 30
1600MHz = 9, 9, 9, 24

i to w sumie daje niewielki wzrost dla 2400MHz.

2400 MHz = 0.42 ns
CL10 => 4.2 ns opóźnienie

1600 MHz = 0.625 ns
CL 9 => 5.625 ns opóźnienie

Ale i tak przecież obchodzi cię przepustowość, a nie opóźnienia, no nie? Możesz się zdecydować?

Ponadto smaczku dodają różne tryby dual channel: ganged i unganged. Tryb unganged jest zaprojektowany pod kątem wielowątkowości - każdy kanał jest oddzielnie obsługiwany i odpowiada połówce przestrzeni adresowej. Wobec tego w trybie unganged prędkość kopiowania sekwencyjnego nie ulega zmianie, ale już losowe dostępy do pamięci lub dostęp do różnych połówek przestrzeni adresowej z różnych wątków powinny znacznie przyspieszyć.

0
Wibowit napisał(a):

2400 MHz = 0.42 ns
CL10 => 4.2 ns opóźnienie

1600 MHz = 0.625 ns
CL 9 => 5.625 ns opóźnienie

Ale i tak przecież obchodzi cię przepustowość, a nie opóźnienia, no nie? Możesz się zdecydować?

Po to się zwiększa różne częstotliwości w komputerach żeby zmniejszać opóźnienia.

A obecnie produkują i reklamują niedrogi i szybki ram, np. 1600MHz, ale tę szybkość uzyskują wsadzając tam 11CL,
co jest po prostu oszustem, bo to jest zwykły 1333MHz, albo i trochę mniej, jedynie inaczej ustawiony.

0

Sprawdziłem na dwóch dość zasadniczo różnych pecetach szybkości w prostych obliczeniach na double w pamięci.

Nie ma praktycznie żadnej różnicy.

I. Na starym dziadu: Celeron 2.5GHz na DDR1 333MHz wykonuje około 400M operacji /s z mnożeniem lub dodawaniem;
na single i dual to samo.

A. Na nowym AMD 3.4GHz (z dwoma rdzeniami, ale to nie ma tu znaczenia ponieważ obliczenia są zwyczajnie - bez żadnych wątków),
wychodzi dość dziwnie:
mnożenie 800M/s, a dodawanie tylko 400M/s.

Na single, bo tam jest tylko jeden cały 4GB 1600 MHz.
Czyli tu wychodzi jakby ta faktyczna częstotliwość RAM: 800 MHz, przynajmniej z mnożeniem,
a z dodawaniem 2 razy gorzej - to samo co na starym Celeronie z 256KB cache L2.

Algorytm był taki:

c[i] = a[i] + b[i];

i podobnie z mnożeniem, czyli trzy różne tablice.

Zatem 2 odczyty i 1 zapis, co w sumie daje 24 bajty, to razy 400M więc otrzymamy:
24*400M / s = 9.6 GB/s;
a dla nowego amd z mnożeniem: 19.2 GB/s

No i nie wiem co to ma być - ani to ram, ani cache L2, ani procesor.
Dla amd chyba po prostu 800MHz z magistrali pamięci (czy raczej z jakiegoś wewnętrznego układu, który ma akurat taką samą freq);
ale ten celeron to cholera wie jak mógł taki wynik osiągnąć... może tam jest 400MHz wewnętrznie dla pamięci.

Morał z tego jest chyba taki, że te nowe procesory nie są wcale takie rewelacyjne w prostych obliczeniach.
One jedynie w multimediach są lepsze... ale też raczej niewiele (pomijając szyfrowanie i inne takie detale... mało użyteczne w praktyce).

A te ramy DDR3 to kompletny niewypał - opóźnienia: CL9 itp. są tu kilka razy większe od opóźnień prostego DDR,
więc to mało daje... sama częstotliwość z 4 większa (DDR 200MHz a DDR3 800MHz) ale te cykle są z 4 dłuższe, i wydajność w zasadzie taka sama.

Może jeszcze sprawdzę w wersji z tym rozwijaniem pętli - bardziej równoległa obróbka.

0
wil napisał(a):

A. Na nowym AMD 3.4GHz (z dwoma rdzeniami, ale to nie ma tu znaczenia ponieważ obliczenia są zwyczajnie - bez żadnych wątków),
wychodzi dość dziwnie:
mnożenie 800M/s, a dodawanie tylko 400M/s.

Jednak to samo tu wychodzi: 800.
Te 400 to tylko efekt oszczędzania energii - procesor chodzi na 1700MHz, czyli 2x wolniej i dopiero po obciążeniu wrzuca full.

Z dokładniejszy testów wychodzi mi że stary złom Celeron działa tylko 3x wolniej.
Celeron ma ram 333MHz, a amd 1600MHz, co sugeruje 1600/333 = 4.8 razy więcej, ale jest tylko 3, bo te ramy DDR3 są faktycznie równoważne ze zwykłym DDR 1000MHz.

Zatem szybkość ram w ostatnich 10 latach wzrosła chyba zaledwie z 2-3 razy.

0

Nie. Przepustowość samych kości RAM jest wprost proporcjonalna do szerokości szyny danych i taktowania. Wąskim gardłem jest częściej kontroler pamięci z procesorze czy na płycie głównej niż sama pamięć. Natomiast jeśli chodzi o opóźnienia RAMu to maleją one bardzo powoli. Z tym, że tutaj też nie zawsze są one wąskim gardłem, bo mogą być duże opóźnienia w samym procesorze, chodzi np o dostęp do pamięci podręcznej, opóźnienia na kontrolerze itd

Wewnętrzne taktowanie DRAMów to jakieś 200 - 250 MHz i generalnie bardzo powoli ono wzrasta. Aby zwiększyć wydajność trzeba iść (i idzie się) w poszerzanie szyn oraz w wielowątkowość, także na poziomie samych RAMów. Jeśli twój algorytm ma silne szeregowe zależności oraz polega mocno na losowym (tzn niesekwencyjnym) dostępie do pamięci, to niestety ale nowe generacje RAMów znaczącego zysku nie przyniosą.

0

Jak nie jak tak.

DDR3 niech jedzie na 1600MHz i timing 9,9,9, 27, co daje dostęp około 5-6ns.

A DDR1 333MHz jakiem ma te opóźnienia?
Chyba coś w stylu: 3,3,3, 8, czyli z trzy razy mniejsze.

3/333M = 9ns

Zatem teraz pamięci mają około 2/3 razy mniejsze opóźnienia, więc są tylko 3/2 razy szybsze,
no i taki powinien być wzrost wydajności w losowym dostępie do ramu (trzeba to sprawdzić).

Ja testowałem sekwencyjny dostęp, a te DDR3 łapią po kilka kęsów więcej od razu, więc w tej wersji wychodzi troszkę lepiej.

Ostateczny wynik będzie chyba gorszy: 1.5 góra 2 razy wzrost wydajności pamięci w ostatnich 10 latach.
Postęp jest tylko w ilości rdzeni, czyli w zasadzie żaden, bo co to za sztuka powiązać kilka procesorów...
no, trochę postępów jest w miniaturyzacji - pobór energii chyba nieco mniejszy (w przeliczeniu na rdzeń,
a w sumie to samo - zasilacze są takie same (pomijam karty graficzne, bo tu jest raczej odwrotnie)).

0

Aha! Szyny są takie same - zero postępów w 10 lat. hihi!

0

Wewnętrzne taktowanie pamięci (memory clock) prawie nic nie wzrosło:
http://en.wikipedia.org/wiki/DDR_SDRAM#Chips_and_modules
http://en.wikipedia.org/wiki/DDR3_SDRAM#JEDEC_standard_modules
Zdarzały się pamięci DDR1 400 MHz z CL2.5 lub nawet CL2.

Co do szerokości szyn danych - Intel poeksperymentował raz z Triple-Channel, który dawał kopa w specyficznych obciążeniach, ale ogólnie zysk był zbyt mały, żeby tracić na to tranzystory w procesorach konsumenckich.

0

Może ktoś sprawdzi na swoim i porównamy.

#define N  1000
#define K   0x20000  // 128K

void testFPU(char *s)
{
  int i,k;
  uint t, t0,t1,t2,t3;

  char *p = new char[(3*K+2)*8];
  double *a = (double*)((uint)p + 15 & ~15); // wyrównanie do 16B

  for(i = 0; i < 3*K; i++)
   {
     a[i++] = 7.61806657564534323*rndf(); // jakieś losowe liczby
     a[i++] = 7.61806657564534323*rndf();
   }


  t0 = GetTickCount();  // jakiś czas w ms
  for(k = N; --k >= 0; )
   for(i = 0; i < 3*K; i+=3)
    {
      a[i+2] = a[i] + a[i+1];
    }

  t1 = GetTickCount(); t0 = t1-t0;
  for(k = N; --k >= 0; )
   for(i = 0; i < 3*K; i+=3)
    {
      a[i+2] = a[i] * a[i+1];
    }

//  a[0] = x;
  t2 = GetTickCount(); t1 = t2-t1;
  for(k = N; --k >= 0; )
   for(i = 0; i < 3*K; i+=3)
    {
      a[i+2] = a[i] / a[i+1];
    }

  t3 = GetTickCount(); t2 = t3-t2;
  for(k = N; --k >= 0; )
   for(i = 0; i < 3*K; i+=3)
    {
      a[i+2] = a[i] + a[i+1]; i+=3;
      a[i+2] = a[i] * a[i+1];
    }

  t3 = GetTickCount() - t3;
  t = (t0+t1+t2+t3); // łączny czas 

  t0 = (N*K) * 0.001 / t0; // = mips / s
  t1 = (N*K) * 0.001 / t1;
  t2 = (N*K) * 0.001 / t2;
  t3 = (N*K) * 0.001 / t3;

  sprintf(s, "add: %d mul: %d; div: %d; + i *: %d; T = %d", t0,t1,t2,t3,t);

  delete []p;
}

Potem wyświetlamy czymś s, np.:

char s[256];

testFPU(s);
MessageBox(s, "FPU", MB_OK);

Mi wychodzi z tego około:
160, 160, 140, 170, i czas obliczeń z 3300ms = 3.3s.

Jakieś 160 milionów operacji /s, dzielenie trochę mniej, a te mieszane + i * trochę więcej, bo tam idzie trochę równolegle.
Tyle że to pewnie zależy od kompilatora - u mnie było praktycznie zero optymalizacji,
bo używałem Borlanda a tam nie ma żadnej optymalizacji - można sobie tam ustawiać różne opcje, ale to tylko takie dekoracje...

0

Nie zmieniam tematu.
Tu wychodzi z szybkość ram 4GB/s tylko, a powinno być przynajmniej 8GB/s.

3 * 8 * 160 = 3880 MB/s

0
  1. Cała tablica mieści się w pamięci podręcznej, RAM jest prawie niedotykany.
  2. Dzielenie lekko wolniejsze od dodawania? Przecież sama pojedyncza operacja dzielenia jest zwykle kilkanaście lub kilkadziesiąt razy wolniejsza.

Sprawdziłem taki kod u siebie:

import java.util.Arrays;


public class RawSpeed {
    public static void main(String[] args) {
        long[] tab = new long[125000000];
        Arrays.fill(tab, 45364);
        System.out.println(computeSum(tab));
        long time = System.currentTimeMillis();
        long sum = computeSum(tab);
        System.out.println("time: " + (System.currentTimeMillis() - time));
        System.out.println(sum);
    }

    private static long computeSum(long[] tab) {
        long sum = 0;
        for (int i = 0; i < tab.length; i++) {
            sum += tab[i];
        }
        return sum;
    }
}

Mam Core 2 Duo E8400 i 8 GiB DDR2 podkręcone na 1000 MHz.

Output w NetBeans to:

run:
5670500000000
time: 149
5670500000000
BUILD SUCCESSFUL (total time: 1 second)

Co daje 1 GB / 149 ms = 6.71 GB / s

Pojedynczy kanał RAMu daje u mnie 8 GB / s teoretycznie, więc 6.71 GB /s to nie tak daleko od przepustowości jednego kanału. Nie wiem czy pamięć jest w trybie ganged czy unganged (edit: sprawdziłem i jest w interleaved mode, czyli ganges z tego co rozumiem).

Jeśli zrobię 1000 razy mniejszą tablicę i zrobię na niej 1000 przebiegów, czyli kod będzie taki:

import java.util.Arrays;

public class RawSpeed {

    public static void main(String[] args) {
        long[] tab = new long[125000];
        Arrays.fill(tab, 45364);
        System.out.println(computeSum(tab));
        long time = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < 1000; i++) {
            sum += computeSum(tab);
        }
        System.out.println("time: " + (System.currentTimeMillis() - time));
        System.out.println(sum);
    }

    private static long computeSum(long[] tab) {
        long sum = 0;
        for (int i = 0; i < tab.length; i++) {
            sum += tab[i];
        }
        return sum;
    }
}

To dostanę taki wynik:

run:
5670500000
time: 63
5670500000000
BUILD SUCCESSFUL (total time: 0 seconds)

Co daje przepustowość:
1 GB / 63 ms = 15.87 GB / s
ale wszystko tutaj dzieje się w pamięci podręcznej.

Natomiast szybkość zapisu jest coś podejrzanie niska. Dla takiego kodu:

import java.util.Arrays;

public class RawSpeed {

    public static void main(String[] args) {
        long[] tab = new long[125000000];
        long time = System.currentTimeMillis();
        Arrays.fill(tab, 4239687);
        System.out.println("time: " + (System.currentTimeMillis() - time));
    }
}

Dostaję:

run:
time: 342
BUILD SUCCESSFUL (total time: 1 second)

Czyli jakieś 3 GB/s.

0
Wibowit napisał(a):
  1. Cała tablica mieści się w pamięci podręcznej, RAM jest prawie niedotykany.

Ja używałem tyle pamięci:
24 * 128K = 3880 KB, cache w tym amd: 1024KB, a w Celeronie 256KB.

Zatem to jedzie po ramie.
Wcześniejsze pomiary były dla niedużej tablicy i tam było ponad 800 M/s, czyli z 6 x szybciej na amd, a na Celeronie 400.

Wibowit napisał(a):
  1. Dzielenie lekko wolniejsze od dodawania? Przecież sama pojedyncza operacja dzielenia jest zwykle kilkanaście lub kilkadziesiąt razy wolniejsza.

I dlatego tam wychodzi trochę mniej dla dzielenia - ale niewiele, bo ram ma tak długie opóźnienia, że procesor nawet dzielenie zdąży wykonać prawie w całości... chociaż nie wiem jakim cudem on to robi równolegle - musi przecież najpierw ściągnąć argumenty z ramu... aha, one są już zwykle w cache, bo są do niego ładowane większymi porcjami po 64 bajty chyba, albo 128.

A tego kodu to ja raczej nie mogę przekompilować i sprawdzić - używam c++.
Zresztą tam masz tylko odczyt w tym sumowaniu, a pozostałe operacje są w zasadzie nieznane - być może zasuwa tam instrukcjami sse, lub nawet coś szybszego.

Samego wypełniania i odczytu osobno nie mierzyłem.

Na SEE może potem sprawdzę - pewnie pójdzie 2 razy szybciej.

Na tym sprawdzałem:
http://www.mikusite.de/pages/x86.htm
Na sse2 było 3 razy szybciej od fpu, a na sse4 prawie 4 razy.
Ale to są raczej same obliczenia, a nie ram.

Ten procesor ma również instrukcje FMA w obu wersjach 3 i 4 argumenty:
http://en.wikipedia.org/wiki/FMA_instruction_set
To chyba powinno dawać niezłego kopa w obliczeniach, no ale dopiero za jakieś 10 lat powstaną na to programy.

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