Nežádám, abych se ptal, jak funguje matematický koncept modulu, nebo jak 20 % 3 == 2 tomu rozumím. Jsem zvědavější, jak to určuje překladač / tlumočník. Napadá mě velmi naivní způsob, jak to udělat, například:

int modulus(int a, int b) { while (a > b) { a -= b; } return a; } 

(Je zřejmé, že to vyžaduje několik dalších případů, protože a může být menší než b, ale máte představu.) Ale to by mělo nějaké vážné problémy s výkonem. Hodil jsem dohromady C ++, abych otestoval účinnost obou modulů operátor a moje naivní funkce.

#include <iostream> #include <chrono> #include <limits> #include "chrono_io" using namespace std; typedef chrono::high_resolution_clock Clock; int mod(int a, int b) { while (a > b) { a -= b; } return a; } int main() { auto start = Clock::now(); cout << 20 % 3 << endl; auto end = Clock::now(); cout << "Modulus on a small number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << numeric_limits<int>::max() % 3 << endl; end = Clock::now(); cout << "Modulus on a large number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << mod(20, 3) << endl; end = Clock::now(); cout << "My modulus function on a small number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << mod(numeric_limits<int>::max(), 3) << endl; end = Clock::now(); cout << "My modulus function on a large number took " << end - start << " seconds to calculate.\n"; } 

a výstup je:

2 Modulus on a small number took 43629 nanoseconds seconds to calculate. 1 Modulus on a large number took 5644 nanoseconds seconds to calculate. 2 My modulus function on a small number took 3682 nanoseconds seconds to calculate. 1 My modulus function on a large number took 2153824554 nanoseconds seconds to calculate. 

The operátor modulu se pohyboval od 4 000 do 70 000 nanosekund. Moje funkce vždy zvítězila u malých čísel, ale u velkého počtu to trvalo asi 2 sekundy. Protože operátor modulu je mnohem konzistentnější, předpokládám, že porovnává číslo na bitové úrovni.

Jak tedy funguje operátor modulu? Napadají mě některé případy, které by bylo velmi snadné určit. Například u modu 2 se stačí podívat na velmi poslední bajt. Nebo s modem 4 se můžete podívat na předposlední b yte. Ale ne všechna čísla mají tak jednoduchý vzor.

Komentáře

  • V počítačích se to obvykle provádí v hardwaru (x86 a IDIV, které provádějí celočíselné dělení, také zbytek uloží do registru). Ptáte se, jak to hardware dělá?
  • Související čtení: Assembly Language – How to Do Modulo?
  • Dlouhé dělení funguje stejně jako u binárních čísel, tak jako u desetinných míst. Vyzkoušejte to a pak si rozmyslete, jak to můžete kódovat (nápověda: ‚ využijete spoustu směn).
  • @DJMcMayhem: Ano. Někdy musí kompilátor provést určité úpravy výsledků, aby vyhovoval specifikaci jazyka pro aritmetiku se znaménkem. Obecně je rychlost hardwarového dělení dostatečně dobrá na to, že když neznáte vstupní hodnoty v době kompilace, je lepší nechat to udělat hardware místo softwarové emulace. Pokud je však dělitel známý v době kompilace, existují triky, které by mohly běžet rychleji. Viz: toto a toto .
  • Všimněte si, že způsob, jakým jsou načasování věcí, které opravdu jen měříte čas potřebný na cout << většinu operací. Pochybuji, že byste mohli skutečně změřit čas, který procesor potřebuje 20 % 3, i když kompilátor to ‚ stejně neudělal .

Odpověď

Téměř všechny CPU mají jedinou instrukci, která vrátí modul hodnoty. Zvažte například tento program:

int main() { int i = 10; return i % 3; } 

Pokud to zkompiluji na svém stroji Intel OS X pomocí g ++ -S, výsledkem bude nějaký typický štítek a toto:

movl $3, %eax movl $0, -4(%rbp) movl $10, -8(%rbp) movl -8(%rbp), %ecx movl %eax, -12(%rbp) ## 4-byte Spill movl %ecx, %eax cltd movl -12(%rbp), %ecx ## 4-byte Reload idivl %ecx movl %edx, %eax 

Skutečný modul se stane s touto instrukcí: idivl %ecx. Po provedení této instrukce bude kvocient umístěn do %eax a zbytek do %edx.

Vzhledem k tomu, že to ve skutečnosti znamená, že operace % bude trvat jen několik hodinových cyklů, z nichž většina ve skutečnosti přesouvá data do správného registru. Všimněte si také, že přinejmenším u Intel najde stejná operace kvocient i zbytek, takže s odkazem na váš komentář / a % trvat přesně stejnou dobu. Je to stejná operace. Jediná věc, která se mění, je to, z jakého registru získá kompilátor výsledky.

U všech procesorů provedených v posledních několika desetiletích můžete předpokládat, že každá základní matematická operace (včetně věcí, které vypadají jako funkce knihovny jako sqrt nebo cos) je ve skutečnosti jediná strojová instrukce a obvykle trvá maximálně několik hodinových cyklů.

[UPDATE]

Jak již lidé uvedli v komentářích, abyste viděli, jak se něco blíží správnému načasování, je třeba odebrat výstup z časované sekce takto:

int i; auto start = Clock::now(); i = 20 % 3; auto end = Clock::now(); cout << i << endl; 

Ale ani to pravděpodobně není přesné, protože skutečná zrnitost časování může překročit to, čím jste snaží se načasovat. Místo toho možná budete chtít udělat toto:

int i=0; int x; auto start = Clock::now(); for(i=0;i<1000000;i++) x = i % 3; auto end = Clock::now(); cout << i << endl; 

Poté vydělte své načasování o 1 000 000. (To bude o něco vyšší, protože zahrnuje čas potřebný pro přiřazení, srovnání a přírůstek.) Na mém počítači to dává čas 5 nanosekund.

Komentáře

  • Opravdu? Bylo mi řečeno, že sqrt je velmi pomalá instrukce.
  • Viz stackoverflow.com / questions / 7724061 / …
  • @StevenBurnap je tato odpověď na základě architektury v roce 2011 trochu zastaralá. Podle tabulka Agner Fog ‚ s aktualizací alespoň jednou ročně , pokyny Haswell SSE2 SQRTSS / SQRTPS (32bitový float, s 23 bity významnosti) má latenci 11 cyklů, SQRTSD / SQRTPD (64bitový float) má latenci 16 cyklů. Tyto pokyny jsou přesné, tj. Nikoli z variant rychlé aproximace. Ve starších architekturách je to opravdu mnohem pomalejší. Nezapomeňte také, že volání std::sqrt() vyvolá volání knihovny ve většině překladačů C ++.
  • Překladače mohou ve zvláštních případech navíc použít optimalizaci snížení pevnosti. Například x% 2 může být optimalizováno na bitovou masku, pokud je ‚ rychlejší na konkrétní architektuře.
  • @rwong Myslel bych si, že std:sqrt by obvykle byly vložené (a používaly vložené sestavení.) Ale ‚ jsem to nikdy nekontroloval.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *