tryb dual channel - wykorzystanie

tryb dual channel - wykorzystanie
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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:

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

teraz z rozwinięciem:

Kopiuj
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).

edytowany 2x, ostatnio: wil
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
0

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

Kopiuj
void copy(dst, src, n)
{
  fore(int i = 0 ; i < n; i++) 
   {
     dst[i] = src[i]; i++;
     dst[i] = src[i];      
   }
} 
Azarien
a jeśli n jest nieparzyste, kod się wysypie.
Wibowit
zależy czy src[n] i dst[n] mogą być odczytywane/ nadpisywane bez konsekwencji
Azarien
UB. (tak, jeśli kopiujemy tylko część tablicy, czyli [n] jest prawidłowym (choć niechcianym) indeksem, to kod zadziała, ale źle)
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Endrju
Ciekawe. Ktoś powinien tę bibliotekę włączyć do glibc, bo autor twierdzi, że jest nieco szybsza. Myślałem, że tam już wykorzystali SIMD do granic możliwości. ;-) Zauważ, że OP chodzi o dual channel z RAM, ale szczerze powiem, że ciężko mi zrozumieć czego on może konkretnie chcieć.
Wibowit
Mi się zdaje, że OP chce dowiedzieć się jak napisać program, w którym dual channel dałby mocno odczuwalny wzrost wydajności (względem single channel).
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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ę.

edytowany 1x, ostatnio: wil
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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ść.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
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.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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:

Kopiuj
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:

Kopiuj
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:

Kopiuj
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:

Kopiuj
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 2x, ostatnio: Wibowit
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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.

wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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

edytowany 1x, ostatnio: wil
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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).

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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ć.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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.

wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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:

Kopiuj
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.

edytowany 2x, ostatnio: wil
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.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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ą.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
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!

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
0

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

Kopiuj
#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.:

Kopiuj
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...

Wibowit
no ale tutaj to już zmieniasz temat
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
0

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

3 * 8 * 160 = 3880 MB/s

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 20 godzin
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:

Kopiuj
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:

Kopiuj
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:

Kopiuj
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:

Kopiuj
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:

Kopiuj
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ę:

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

Czyli jakieś 3 GB/s.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 3x, ostatnio: Wibowit
wil
  • Rejestracja:ponad 19 lat
  • Ostatnio:prawie 7 lat
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.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)