Merge pull request 'Wiki in English, Danish and Malay, AVX2 explanation, README' (#3) from docs/wiki-and-dist-build into main
This commit was merged in pull request #3.
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
# Byg og kør
|
||||||
|
|
||||||
|
🌐 [English](Build-and-Run) · **Dansk** · [Bahasa Melayu](Build-and-Run-ms)
|
||||||
|
|
||||||
|
`Makefile` ligger i repositoriets rod og lægger programmet i `dist/`. Du skal
|
||||||
|
bruge `gcc` (Linux, eller MinGW-w64 / MSYS2 på Windows) og en CPU med AVX2.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make run
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Sådan gemmer du primtallene og stadig ser tiden: omdiriger kun standard output.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dist/prime_avx2 > primes.txt
|
||||||
|
```
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Bina dan jalankan
|
||||||
|
|
||||||
|
🌐 [English](Build-and-Run) · [Dansk](Build-and-Run-da) · **Bahasa Melayu**
|
||||||
|
|
||||||
|
`Makefile` berada di akar repositori dan meletakkan fail boleh laksana dalam
|
||||||
|
`dist/`. Anda memerlukan `gcc` (Linux, atau MinGW-w64 / MSYS2 pada Windows) dan
|
||||||
|
CPU dengan AVX2.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make run
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Untuk menyimpan nombor perdana dan masih melihat masa, alihkan standard output
|
||||||
|
sahaja:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dist/prime_avx2 > primes.txt
|
||||||
|
```
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Build and run
|
# Build and run
|
||||||
|
|
||||||
**English**
|
🌐 **English** · [Dansk](Build-and-Run-da) · [Bahasa Melayu](Build-and-Run-ms)
|
||||||
|
|
||||||
The `Makefile` is in the repository root and puts the executable in `dist/`.
|
The `Makefile` is in the repository root and puts the executable in `dist/`.
|
||||||
It needs `gcc` (Linux, or MinGW-w64 / MSYS2 on Windows) and a CPU with AVX2.
|
It needs `gcc` (Linux, or MinGW-w64 / MSYS2 on Windows) and a CPU with AVX2.
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Beskrivelse
|
||||||
|
|
||||||
|
🌐 [English](Description) · **Dansk** · [Bahasa Melayu](Description-ms)
|
||||||
|
|
||||||
|
## Formål
|
||||||
|
|
||||||
|
Programmet viser, hvor hurtig en CPU er. Hvor hurtig den ser ud til at være,
|
||||||
|
afhænger af, hvordan programmet er skrevet, så tallet måler CPU'en og koden
|
||||||
|
sammen, ikke CPU'en alene.
|
||||||
|
|
||||||
|
## Hvad det gør
|
||||||
|
|
||||||
|
Programmet tester hvert tal fra 0 til 1.000.000 med `is_prime_avx2`, som bruger
|
||||||
|
AVX2-prøvedivision med 6k ± 1 (fire divisorer pr. trin). Det udskriver hvert
|
||||||
|
primtal på sin egen linje og skriver en opsummering til standard error:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Found 78498 primes from 0 to 1000000 in 13.806 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
78.498 er det kendte antal primtal op til 1.000.000, så opsummeringen virker
|
||||||
|
også som kontrol af, at resultatet er rigtigt.
|
||||||
|
|
||||||
|
## Hvorfor tiden afhænger af koden
|
||||||
|
|
||||||
|
Den samme CPU giver meget forskellige tider for forskellige programmer:
|
||||||
|
|
||||||
|
- algoritmen (prøvedivision, en si, en segmenteret si),
|
||||||
|
- instruktionssættet (skalar kode, AVX2, AVX-512),
|
||||||
|
- compiler og flag (`-O3 -mavx2`, MSVC `/O2 /arch:AVX2`),
|
||||||
|
- hvad der måles (kun søgningen, eller også udskrivning til en konsol).
|
||||||
|
|
||||||
|
Her måles kun søgningen. Resultatet samles først og udskrives bagefter, så
|
||||||
|
konsollens hastighed ikke påvirker tallet.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Penerangan
|
||||||
|
|
||||||
|
🌐 [English](Description) · [Dansk](Description-da) · **Bahasa Melayu**
|
||||||
|
|
||||||
|
## Tujuan
|
||||||
|
|
||||||
|
Program ini menunjukkan betapa lajunya sesebuah CPU. Kelajuan yang kelihatan
|
||||||
|
bergantung pada cara program ditulis, jadi angka itu mengukur CPU dan kod
|
||||||
|
bersama-sama, bukan CPU sahaja.
|
||||||
|
|
||||||
|
## Apa yang dilakukannya
|
||||||
|
|
||||||
|
Program menguji setiap nombor dari 0 hingga 1,000,000 dengan `is_prime_avx2`,
|
||||||
|
yang menggunakan pembahagian cuba-cuba AVX2 dengan 6k ± 1 (empat pembahagi
|
||||||
|
setiap langkah). Ia mencetak setiap nombor perdana pada barisnya sendiri dan
|
||||||
|
menulis baris ringkasan ke standard error:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Found 78498 primes from 0 to 1000000 in 13.806 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
78,498 ialah bilangan nombor perdana yang diketahui hingga 1,000,000, jadi baris
|
||||||
|
ringkasan juga berfungsi sebagai semakan ketepatan.
|
||||||
|
|
||||||
|
## Mengapa masa bergantung pada kod
|
||||||
|
|
||||||
|
CPU yang sama memberi masa yang sangat berbeza untuk program yang berbeza:
|
||||||
|
|
||||||
|
- algoritma (pembahagian cuba-cuba, ayak, ayak bersegmen),
|
||||||
|
- set arahan yang digunakan (kod skalar, AVX2, AVX-512),
|
||||||
|
- pengkompil dan bendera (`-O3 -mavx2`, MSVC `/O2 /arch:AVX2`),
|
||||||
|
- apa yang diukur (carian sahaja, atau juga pencetakan ke konsol).
|
||||||
|
|
||||||
|
Di sini hanya carian diukur. Keputusan dikumpul dahulu dan dicetak kemudian,
|
||||||
|
supaya kelajuan konsol tidak mengubah angka.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Description
|
# Description
|
||||||
|
|
||||||
**English**
|
🌐 **English** · [Dansk](Description-da) · [Bahasa Melayu](Description-ms)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# prime-numbers-c
|
||||||
|
|
||||||
|
🌐 [English](Home) · **Dansk** · [Bahasa Melayu](Home-ms)
|
||||||
|
|
||||||
|
Et lille C-program, der udskriver alle primtal fra 0 til 1.000.000 og viser,
|
||||||
|
hvor lang tid det tog.
|
||||||
|
|
||||||
|
- [Beskrivelse](Description-da): hvad programmet skal bruges til
|
||||||
|
- [Sådan virker AVX2](How-AVX2-Works-da): koden, trin for trin
|
||||||
|
- [Resultater](Results-da): målte tider på rigtig hardware
|
||||||
|
- [Byg og kør](Build-and-Run-da): `make`, `make run`
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# prime-numbers-c
|
||||||
|
|
||||||
|
🌐 [English](Home) · [Dansk](Home-da) · **Bahasa Melayu**
|
||||||
|
|
||||||
|
Program C kecil yang menyenaraikan semua nombor perdana dari 0 hingga 1,000,000
|
||||||
|
dan melaporkan berapa lama ia mengambil masa.
|
||||||
|
|
||||||
|
- [Penerangan](Description-ms): kegunaan program ini
|
||||||
|
- [Cara AVX2 berfungsi](How-AVX2-Works-ms): kod, langkah demi langkah
|
||||||
|
- [Keputusan](Results-ms): masa yang diukur pada perkakasan sebenar
|
||||||
|
- [Bina dan jalankan](Build-and-Run-ms): `make`, `make run`
|
||||||
+2
-1
@@ -1,10 +1,11 @@
|
|||||||
# prime-numbers-c
|
# prime-numbers-c
|
||||||
|
|
||||||
**English**
|
🌐 **English** · [Dansk](Home-da) · [Bahasa Melayu](Home-ms)
|
||||||
|
|
||||||
A small C program that lists every prime number from 0 to 1,000,000 and
|
A small C program that lists every prime number from 0 to 1,000,000 and
|
||||||
reports how long it took.
|
reports how long it took.
|
||||||
|
|
||||||
- [Description](Description): what the program is for
|
- [Description](Description): what the program is for
|
||||||
|
- [How AVX2 works](How-AVX2-Works): the code, step by step
|
||||||
- [Results](Results): measured times on real hardware
|
- [Results](Results): measured times on real hardware
|
||||||
- [Build and run](Build-and-Run): `make`, `make run`
|
- [Build and run](Build-and-Run): `make`, `make run`
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Sådan virker AVX2
|
||||||
|
|
||||||
|
🌐 [English](How-AVX2-Works) · **Dansk** · [Bahasa Melayu](How-AVX2-Works-ms)
|
||||||
|
|
||||||
|
AVX2 er et sæt CPU-instruktioner, der udfører den samme operation på flere tal
|
||||||
|
på én gang (SIMD: én instruktion, flere data). `is_prime_avx2` bruger det til at
|
||||||
|
teste **fire divisorer pr. trin** i stedet for én.
|
||||||
|
|
||||||
|
## Idéen
|
||||||
|
|
||||||
|
Et tal `n` er et primtal, hvis intet tal fra 2 til sqrt(n) går op i det. To
|
||||||
|
trin mindsker arbejdet:
|
||||||
|
|
||||||
|
1. **Spring de fleste kandidater over.** Hvert primtal over 3 har formen 6k - 1
|
||||||
|
eller 6k + 1 (5, 7, 11, 13, 17, 19, 23, 25, ...). Lige tal og multipla af 3
|
||||||
|
afvises før løkken, så kun disse divisorer prøves.
|
||||||
|
2. **Test fire ad gangen.** Ét 256-bit AVX2-register rummer fire `double`-værdier,
|
||||||
|
så én division, én multiplikation og én sammenligning klarer fire divisorer.
|
||||||
|
|
||||||
|
Hver gennemgang af løkken tester divisorerne `d, d+2, d+6, d+8` og går derefter
|
||||||
|
12 frem: `5,7,11,13` så `17,19,23,25` så `29,31,35,37` og så videre.
|
||||||
|
|
||||||
|
## Koden, trin for trin
|
||||||
|
|
||||||
|
Kommentarerne i koden er på engelsk, ligesom koden selv.
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Only divisors up to sqrt(n) need testing: a factor above it pairs with one below it.
|
||||||
|
const double limit = floor(sqrt((double)n));
|
||||||
|
|
||||||
|
// Broadcast n and the limit into all 4 double lanes of a 256-bit register.
|
||||||
|
__m256d v_n = _mm256_set1_pd((double)n);
|
||||||
|
__m256d v_limit = _mm256_set1_pd(limit);
|
||||||
|
|
||||||
|
// Divisors 5, 7, 11, 13 (6k +/- 1); each iteration advances by 12.
|
||||||
|
// Kept as 4 x int32 in a 128-bit register, converted to double each pass.
|
||||||
|
__m128i v_div_i = _mm_setr_epi32(5, 7, 11, 13);
|
||||||
|
const __m128i v_step = _mm_set1_epi32(12);
|
||||||
|
|
||||||
|
// base is the first divisor of the group; stop when it passes sqrt(n).
|
||||||
|
for (double base = 5.0; base <= limit; base += 12.0) {
|
||||||
|
// Convert the 4 int32 divisors to 4 doubles.
|
||||||
|
__m256d v_d = _mm256_cvtepi32_pd(v_div_i);
|
||||||
|
// q = floor(n / d) for all 4 divisors at once.
|
||||||
|
__m256d v_q = _mm256_floor_pd(_mm256_div_pd(v_n, v_d));
|
||||||
|
// rem = n - q * d, which is n mod d.
|
||||||
|
__m256d v_rem = _mm256_sub_pd(v_n, _mm256_mul_pd(v_q, v_d));
|
||||||
|
|
||||||
|
// Lane mask: all bits set where the remainder is 0 (d divides n).
|
||||||
|
__m256d is_zero = _mm256_cmp_pd(v_rem, _mm256_setzero_pd(), _CMP_EQ_OQ);
|
||||||
|
// Lane mask: all bits set where the divisor is <= sqrt(n).
|
||||||
|
__m256d in_range = _mm256_cmp_pd(v_d, v_limit, _CMP_LE_OQ);
|
||||||
|
|
||||||
|
// A lane only counts if it divides n and the divisor is <= sqrt(n).
|
||||||
|
// movemask packs the sign bit of each lane into 4 bits; non-zero = a hit.
|
||||||
|
if (_mm256_movemask_pd(_mm256_and_pd(is_zero, in_range)) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Move to the next four divisors: 5,7,11,13 -> 17,19,23,25 -> ...
|
||||||
|
v_div_i = _mm_add_epi32(v_div_i, v_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No divisor found up to sqrt(n): n is prime.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hvorfor `in_range`-masken er nødvendig
|
||||||
|
|
||||||
|
Den sidste gruppe kan indeholde divisorer over sqrt(n). Uden masken kunne en
|
||||||
|
bane melde en "faktor", der ikke er en. For `n = 25` er grænsen 5, så kun bane 0
|
||||||
|
(divisor 5) tæller, og 25 afvises korrekt. For `n = 29` går intet op i tallet,
|
||||||
|
og 29 er et primtal.
|
||||||
|
|
||||||
|
## Hvorfor `double` og ikke `float` eller heltal
|
||||||
|
|
||||||
|
AVX2 har ingen heltalsdivision, så divisionen foregår i flydende tal. En
|
||||||
|
`double` rummer ethvert 32-bit heltal præcist, og IEEE-division er korrekt
|
||||||
|
afrundet, så `floor(n / d)` er præcis for `n` op til `UINT32_MAX`. En 32-bit
|
||||||
|
`float` har kun 24 bits præcision og ville give forkerte kvotienter. Det er også
|
||||||
|
grunden til, at funktionen kun understøtter `n` op til 4.294.967.295.
|
||||||
|
|
||||||
|
## Brugte intrinsics
|
||||||
|
|
||||||
|
| Intrinsic | Hvad den gør |
|
||||||
|
| --- | --- |
|
||||||
|
| `_mm256_set1_pd` | kopierer én double til alle 4 baner |
|
||||||
|
| `_mm_setr_epi32` | bygger 4 int32-værdier i rækkefølge |
|
||||||
|
| `_mm256_cvtepi32_pd` | konverterer 4 int32 til 4 double |
|
||||||
|
| `_mm256_div_pd` / `_mm256_mul_pd` / `_mm256_sub_pd` | dividerer, multiplicerer, trækker fra i 4 baner |
|
||||||
|
| `_mm256_floor_pd` | runder 4 doubles ned |
|
||||||
|
| `_mm256_cmp_pd` | sammenligner 4 baner og giver en maske med enere eller nuller pr. bane |
|
||||||
|
| `_mm256_and_pd` | bitvis OG af to masker |
|
||||||
|
| `_mm256_movemask_pd` | samler de 4 banemasker til et 4-bit heltal |
|
||||||
|
| `_mm_add_epi32` | lægger 4 int32-værdier sammen |
|
||||||
|
|
||||||
|
De kommer fra `<immintrin.h>`. Byg med `-mavx2` (gcc/clang) eller `/arch:AVX2`
|
||||||
|
(MSVC). På en CPU uden AVX2 ville programmet gå ned med en ugyldig instruktion.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Cara AVX2 berfungsi
|
||||||
|
|
||||||
|
🌐 [English](How-AVX2-Works) · [Dansk](How-AVX2-Works-da) · **Bahasa Melayu**
|
||||||
|
|
||||||
|
AVX2 ialah set arahan CPU yang melakukan operasi yang sama pada beberapa nombor
|
||||||
|
serentak (SIMD: satu arahan, banyak data). `is_prime_avx2` menggunakannya untuk
|
||||||
|
menguji **empat pembahagi setiap langkah** dan bukan satu.
|
||||||
|
|
||||||
|
## Idea
|
||||||
|
|
||||||
|
Nombor `n` ialah nombor perdana jika tiada nombor dari 2 hingga sqrt(n) yang
|
||||||
|
membahaginya. Dua langkah mengurangkan kerja:
|
||||||
|
|
||||||
|
1. **Langkau kebanyakan calon.** Setiap nombor perdana melebihi 3 berbentuk
|
||||||
|
6k - 1 atau 6k + 1 (5, 7, 11, 13, 17, 19, 23, 25, ...). Nombor genap dan
|
||||||
|
gandaan 3 ditolak sebelum gelung, jadi hanya pembahagi ini dicuba.
|
||||||
|
2. **Uji empat sekali gus.** Satu daftar AVX2 256-bit memuatkan empat nilai
|
||||||
|
`double`, jadi satu pembahagian, satu pendaraban dan satu perbandingan
|
||||||
|
mengendalikan empat pembahagi.
|
||||||
|
|
||||||
|
Setiap pusingan gelung menguji pembahagi `d, d+2, d+6, d+8` kemudian maju 12:
|
||||||
|
`5,7,11,13` kemudian `17,19,23,25` kemudian `29,31,35,37`, dan seterusnya.
|
||||||
|
|
||||||
|
## Kod, langkah demi langkah
|
||||||
|
|
||||||
|
Komen dalam kod ditulis dalam bahasa Inggeris, seperti kod itu sendiri.
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Only divisors up to sqrt(n) need testing: a factor above it pairs with one below it.
|
||||||
|
const double limit = floor(sqrt((double)n));
|
||||||
|
|
||||||
|
// Broadcast n and the limit into all 4 double lanes of a 256-bit register.
|
||||||
|
__m256d v_n = _mm256_set1_pd((double)n);
|
||||||
|
__m256d v_limit = _mm256_set1_pd(limit);
|
||||||
|
|
||||||
|
// Divisors 5, 7, 11, 13 (6k +/- 1); each iteration advances by 12.
|
||||||
|
// Kept as 4 x int32 in a 128-bit register, converted to double each pass.
|
||||||
|
__m128i v_div_i = _mm_setr_epi32(5, 7, 11, 13);
|
||||||
|
const __m128i v_step = _mm_set1_epi32(12);
|
||||||
|
|
||||||
|
// base is the first divisor of the group; stop when it passes sqrt(n).
|
||||||
|
for (double base = 5.0; base <= limit; base += 12.0) {
|
||||||
|
// Convert the 4 int32 divisors to 4 doubles.
|
||||||
|
__m256d v_d = _mm256_cvtepi32_pd(v_div_i);
|
||||||
|
// q = floor(n / d) for all 4 divisors at once.
|
||||||
|
__m256d v_q = _mm256_floor_pd(_mm256_div_pd(v_n, v_d));
|
||||||
|
// rem = n - q * d, which is n mod d.
|
||||||
|
__m256d v_rem = _mm256_sub_pd(v_n, _mm256_mul_pd(v_q, v_d));
|
||||||
|
|
||||||
|
// Lane mask: all bits set where the remainder is 0 (d divides n).
|
||||||
|
__m256d is_zero = _mm256_cmp_pd(v_rem, _mm256_setzero_pd(), _CMP_EQ_OQ);
|
||||||
|
// Lane mask: all bits set where the divisor is <= sqrt(n).
|
||||||
|
__m256d in_range = _mm256_cmp_pd(v_d, v_limit, _CMP_LE_OQ);
|
||||||
|
|
||||||
|
// A lane only counts if it divides n and the divisor is <= sqrt(n).
|
||||||
|
// movemask packs the sign bit of each lane into 4 bits; non-zero = a hit.
|
||||||
|
if (_mm256_movemask_pd(_mm256_and_pd(is_zero, in_range)) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Move to the next four divisors: 5,7,11,13 -> 17,19,23,25 -> ...
|
||||||
|
v_div_i = _mm_add_epi32(v_div_i, v_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No divisor found up to sqrt(n): n is prime.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mengapa topeng `in_range` diperlukan
|
||||||
|
|
||||||
|
Kumpulan terakhir boleh mengandungi pembahagi yang melebihi sqrt(n). Tanpa
|
||||||
|
topeng, satu lorong boleh melaporkan "faktor" yang bukan faktor. Untuk `n = 25`
|
||||||
|
had ialah 5, jadi hanya lorong 0 (pembahagi 5) dikira dan 25 ditolak dengan
|
||||||
|
betul. Untuk `n = 29` tiada yang membahagikannya dan 29 ialah nombor perdana.
|
||||||
|
|
||||||
|
## Mengapa `double` dan bukan `float` atau integer
|
||||||
|
|
||||||
|
AVX2 tiada pembahagian integer, jadi pembahagian dibuat dalam titik terapung.
|
||||||
|
`double` memuatkan setiap integer 32-bit dengan tepat dan pembahagian IEEE
|
||||||
|
dibundarkan dengan betul, jadi `floor(n / d)` tepat untuk `n` sehingga
|
||||||
|
`UINT32_MAX`. `float` 32-bit hanya mempunyai ketepatan 24 bit dan akan memberi
|
||||||
|
hasil bahagi yang salah. Ini juga sebabnya fungsi ini hanya menyokong `n`
|
||||||
|
sehingga 4,294,967,295.
|
||||||
|
|
||||||
|
## Intrinsic yang digunakan
|
||||||
|
|
||||||
|
| Intrinsic | Fungsinya |
|
||||||
|
| --- | --- |
|
||||||
|
| `_mm256_set1_pd` | salin satu double ke semua 4 lorong |
|
||||||
|
| `_mm_setr_epi32` | bina 4 nilai int32 mengikut urutan |
|
||||||
|
| `_mm256_cvtepi32_pd` | tukar 4 int32 kepada 4 double |
|
||||||
|
| `_mm256_div_pd` / `_mm256_mul_pd` / `_mm256_sub_pd` | bahagi, darab, tolak dalam 4 lorong |
|
||||||
|
| `_mm256_floor_pd` | bundarkan 4 double ke bawah |
|
||||||
|
| `_mm256_cmp_pd` | banding 4 lorong, beri topeng semua-satu atau semua-sifar setiap lorong |
|
||||||
|
| `_mm256_and_pd` | DAN bitwise bagi dua topeng |
|
||||||
|
| `_mm256_movemask_pd` | gabungkan 4 topeng lorong menjadi integer 4-bit |
|
||||||
|
| `_mm_add_epi32` | tambah 4 nilai int32 |
|
||||||
|
|
||||||
|
Ia datang daripada `<immintrin.h>`. Bina dengan `-mavx2` (gcc/clang) atau
|
||||||
|
`/arch:AVX2` (MSVC). Pada CPU tanpa AVX2 program akan ranap dengan arahan tidak
|
||||||
|
sah.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# How AVX2 works
|
||||||
|
|
||||||
|
🌐 **English** · [Dansk](How-AVX2-Works-da) · [Bahasa Melayu](How-AVX2-Works-ms)
|
||||||
|
|
||||||
|
AVX2 is a set of CPU instructions that do the same operation on several
|
||||||
|
numbers at once (SIMD: single instruction, multiple data). `is_prime_avx2`
|
||||||
|
uses it to test **four divisors per step** instead of one.
|
||||||
|
|
||||||
|
## The idea
|
||||||
|
|
||||||
|
A number `n` is prime if no number from 2 to sqrt(n) divides it. Two steps
|
||||||
|
shrink the work:
|
||||||
|
|
||||||
|
1. **Skip most candidates.** Every prime above 3 has the form 6k - 1 or 6k + 1
|
||||||
|
(5, 7, 11, 13, 17, 19, 23, 25, ...). Even numbers and multiples of 3 are
|
||||||
|
rejected before the loop, so only these divisors are tried.
|
||||||
|
2. **Test four at a time.** One 256-bit AVX2 register holds four `double`
|
||||||
|
values, so one divide, one multiply and one compare handle four divisors.
|
||||||
|
|
||||||
|
Each loop pass tests the divisors `d, d+2, d+6, d+8` and then moves 12 forward:
|
||||||
|
`5,7,11,13` then `17,19,23,25` then `29,31,35,37`, and so on.
|
||||||
|
|
||||||
|
## The code, step by step
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Only divisors up to sqrt(n) need testing: a factor above it pairs with one below it.
|
||||||
|
const double limit = floor(sqrt((double)n));
|
||||||
|
|
||||||
|
// Broadcast n and the limit into all 4 double lanes of a 256-bit register.
|
||||||
|
__m256d v_n = _mm256_set1_pd((double)n);
|
||||||
|
__m256d v_limit = _mm256_set1_pd(limit);
|
||||||
|
|
||||||
|
// Divisors 5, 7, 11, 13 (6k +/- 1); each iteration advances by 12.
|
||||||
|
// Kept as 4 x int32 in a 128-bit register, converted to double each pass.
|
||||||
|
__m128i v_div_i = _mm_setr_epi32(5, 7, 11, 13);
|
||||||
|
const __m128i v_step = _mm_set1_epi32(12);
|
||||||
|
|
||||||
|
// base is the first divisor of the group; stop when it passes sqrt(n).
|
||||||
|
for (double base = 5.0; base <= limit; base += 12.0) {
|
||||||
|
// Convert the 4 int32 divisors to 4 doubles.
|
||||||
|
__m256d v_d = _mm256_cvtepi32_pd(v_div_i);
|
||||||
|
// q = floor(n / d) for all 4 divisors at once.
|
||||||
|
__m256d v_q = _mm256_floor_pd(_mm256_div_pd(v_n, v_d));
|
||||||
|
// rem = n - q * d, which is n mod d.
|
||||||
|
__m256d v_rem = _mm256_sub_pd(v_n, _mm256_mul_pd(v_q, v_d));
|
||||||
|
|
||||||
|
// Lane mask: all bits set where the remainder is 0 (d divides n).
|
||||||
|
__m256d is_zero = _mm256_cmp_pd(v_rem, _mm256_setzero_pd(), _CMP_EQ_OQ);
|
||||||
|
// Lane mask: all bits set where the divisor is <= sqrt(n).
|
||||||
|
__m256d in_range = _mm256_cmp_pd(v_d, v_limit, _CMP_LE_OQ);
|
||||||
|
|
||||||
|
// A lane only counts if it divides n and the divisor is <= sqrt(n).
|
||||||
|
// movemask packs the sign bit of each lane into 4 bits; non-zero = a hit.
|
||||||
|
if (_mm256_movemask_pd(_mm256_and_pd(is_zero, in_range)) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Move to the next four divisors: 5,7,11,13 -> 17,19,23,25 -> ...
|
||||||
|
v_div_i = _mm_add_epi32(v_div_i, v_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No divisor found up to sqrt(n): n is prime.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why the `in_range` mask is needed
|
||||||
|
|
||||||
|
The last group can contain divisors above sqrt(n). Without the mask such a
|
||||||
|
lane could report a "factor" that is not one. For `n = 25` the limit is 5, so
|
||||||
|
only lane 0 (divisor 5) counts and 25 is correctly rejected; for `n = 29`
|
||||||
|
nothing divides it and 29 is prime.
|
||||||
|
|
||||||
|
## Why `double` and not `float` or integers
|
||||||
|
|
||||||
|
AVX2 has no integer divide, so the division is done in floating point. A
|
||||||
|
`double` holds every 32-bit integer exactly and IEEE division is correctly
|
||||||
|
rounded, so `floor(n / d)` is exact for `n` up to `UINT32_MAX`. A 32-bit
|
||||||
|
`float` has only 24 bits of precision and would give wrong quotients. This is
|
||||||
|
also why the function supports `n` only up to 4,294,967,295.
|
||||||
|
|
||||||
|
## Intrinsics used
|
||||||
|
|
||||||
|
| Intrinsic | What it does |
|
||||||
|
| --- | --- |
|
||||||
|
| `_mm256_set1_pd` | copy one double into all 4 lanes |
|
||||||
|
| `_mm_setr_epi32` | build 4 int32 values in order |
|
||||||
|
| `_mm256_cvtepi32_pd` | convert 4 int32 to 4 double |
|
||||||
|
| `_mm256_div_pd` / `_mm256_mul_pd` / `_mm256_sub_pd` | divide, multiply, subtract in 4 lanes |
|
||||||
|
| `_mm256_floor_pd` | round 4 doubles down |
|
||||||
|
| `_mm256_cmp_pd` | compare 4 lanes, giving an all-ones or all-zeros mask per lane |
|
||||||
|
| `_mm256_and_pd` | bitwise AND of two masks |
|
||||||
|
| `_mm256_movemask_pd` | collapse the 4 lane masks into a 4-bit integer |
|
||||||
|
| `_mm_add_epi32` | add 4 int32 values |
|
||||||
|
|
||||||
|
They come from `<immintrin.h>`. Build with `-mavx2` (gcc/clang) or
|
||||||
|
`/arch:AVX2` (MSVC); on a CPU without AVX2 the program would crash with an
|
||||||
|
illegal instruction.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Resultater
|
||||||
|
|
||||||
|
🌐 [English](Results) · **Dansk** · [Bahasa Melayu](Results-ms)
|
||||||
|
|
||||||
|
Tid til at finde alle primtal fra 0 til 1.000.000. Kun søgningen måles.
|
||||||
|
|
||||||
|
| CPU | Resultat |
|
||||||
|
| --- | --- |
|
||||||
|
| 13th Gen Intel(R) Core(TM) i5-13600K | Found 78498 primes from 0 to 1000000 in 20.977 ms |
|
||||||
|
|
||||||
|
Et resultat kan kun sammenlignes med et andet, når koden, compileren og flagene
|
||||||
|
er de samme. Sådan tilføjer du et resultat: kør programmet på din maskine (se
|
||||||
|
[Byg og kør](Build-and-Run-da)) og tilføj en række med dit CPU-navn og
|
||||||
|
opsummeringslinjen.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Keputusan
|
||||||
|
|
||||||
|
🌐 [English](Results) · [Dansk](Results-da) · **Bahasa Melayu**
|
||||||
|
|
||||||
|
Masa untuk mencari semua nombor perdana dari 0 hingga 1,000,000. Hanya carian
|
||||||
|
diukur.
|
||||||
|
|
||||||
|
| CPU | Keputusan |
|
||||||
|
| --- | --- |
|
||||||
|
| 13th Gen Intel(R) Core(TM) i5-13600K | Found 78498 primes from 0 to 1000000 in 20.977 ms |
|
||||||
|
|
||||||
|
Keputusan hanya boleh dibandingkan dengan yang lain apabila kod, pengkompil dan
|
||||||
|
bendera sama. Untuk menambah keputusan, jalankan program pada mesin anda (lihat
|
||||||
|
[Bina dan jalankan](Build-and-Run-ms)) dan tambah satu baris dengan nama CPU
|
||||||
|
anda dan baris ringkasan.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Results
|
# Results
|
||||||
|
|
||||||
**English**
|
🌐 **English** · [Dansk](Results-da) · [Bahasa Melayu](Results-ms)
|
||||||
|
|
||||||
Time to find all primes from 0 to 1,000,000. Only the search is timed.
|
Time to find all primes from 0 to 1,000,000. Only the search is timed.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
|
**prime-numbers-c**
|
||||||
|
|
||||||
- [Home](Home)
|
- [Home](Home)
|
||||||
- [Description](Description)
|
- [Description](Description)
|
||||||
|
- [How AVX2 works](How-AVX2-Works)
|
||||||
- [Results](Results)
|
- [Results](Results)
|
||||||
- [Build and run](Build-and-Run)
|
- [Build and run](Build-and-Run)
|
||||||
|
|
||||||
|
**Languages**
|
||||||
|
|
||||||
|
- English: [Home](Home)
|
||||||
|
- Dansk: [Hjem](Home-da)
|
||||||
|
- Bahasa Melayu: [Utama](Home-ms)
|
||||||
|
|||||||
+13
-1
@@ -18,30 +18,42 @@ bool is_prime_avx2(uint64_t n) {
|
|||||||
if (n % 2 == 0 || n % 3 == 0) return false;
|
if (n % 2 == 0 || n % 3 == 0) return false;
|
||||||
if (n > UINT32_MAX) return false; // out of supported range
|
if (n > UINT32_MAX) return false; // out of supported range
|
||||||
|
|
||||||
|
// Only divisors up to sqrt(n) need testing: a factor above it pairs with one below it.
|
||||||
const double limit = floor(sqrt((double)n));
|
const double limit = floor(sqrt((double)n));
|
||||||
|
|
||||||
|
// Broadcast n and the limit into all 4 double lanes of a 256-bit register.
|
||||||
__m256d v_n = _mm256_set1_pd((double)n);
|
__m256d v_n = _mm256_set1_pd((double)n);
|
||||||
__m256d v_limit = _mm256_set1_pd(limit);
|
__m256d v_limit = _mm256_set1_pd(limit);
|
||||||
|
|
||||||
// Divisors 5, 7, 11, 13 (6k +/- 1); each iteration advances by 12.
|
// Divisors 5, 7, 11, 13 (6k +/- 1); each iteration advances by 12.
|
||||||
|
// Kept as 4 x int32 in a 128-bit register, converted to double each pass.
|
||||||
__m128i v_div_i = _mm_setr_epi32(5, 7, 11, 13);
|
__m128i v_div_i = _mm_setr_epi32(5, 7, 11, 13);
|
||||||
const __m128i v_step = _mm_set1_epi32(12);
|
const __m128i v_step = _mm_set1_epi32(12);
|
||||||
|
|
||||||
|
// base is the first divisor of the group; stop when it passes sqrt(n).
|
||||||
for (double base = 5.0; base <= limit; base += 12.0) {
|
for (double base = 5.0; base <= limit; base += 12.0) {
|
||||||
|
// Convert the 4 int32 divisors to 4 doubles.
|
||||||
__m256d v_d = _mm256_cvtepi32_pd(v_div_i);
|
__m256d v_d = _mm256_cvtepi32_pd(v_div_i);
|
||||||
|
// q = floor(n / d) for all 4 divisors at once.
|
||||||
__m256d v_q = _mm256_floor_pd(_mm256_div_pd(v_n, v_d));
|
__m256d v_q = _mm256_floor_pd(_mm256_div_pd(v_n, v_d));
|
||||||
|
// rem = n - q * d, which is n mod d.
|
||||||
__m256d v_rem = _mm256_sub_pd(v_n, _mm256_mul_pd(v_q, v_d));
|
__m256d v_rem = _mm256_sub_pd(v_n, _mm256_mul_pd(v_q, v_d));
|
||||||
|
|
||||||
|
// Lane mask: all bits set where the remainder is 0 (d divides n).
|
||||||
__m256d is_zero = _mm256_cmp_pd(v_rem, _mm256_setzero_pd(), _CMP_EQ_OQ);
|
__m256d is_zero = _mm256_cmp_pd(v_rem, _mm256_setzero_pd(), _CMP_EQ_OQ);
|
||||||
|
// Lane mask: all bits set where the divisor is <= sqrt(n).
|
||||||
__m256d in_range = _mm256_cmp_pd(v_d, v_limit, _CMP_LE_OQ);
|
__m256d in_range = _mm256_cmp_pd(v_d, v_limit, _CMP_LE_OQ);
|
||||||
|
|
||||||
// A lane only counts if it divides n and the divisor is <= sqrt(n)
|
// A lane only counts if it divides n and the divisor is <= sqrt(n).
|
||||||
|
// movemask packs the sign bit of each lane into 4 bits; non-zero = a hit.
|
||||||
if (_mm256_movemask_pd(_mm256_and_pd(is_zero, in_range)) != 0)
|
if (_mm256_movemask_pd(_mm256_and_pd(is_zero, in_range)) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Move to the next four divisors: 5,7,11,13 -> 17,19,23,25 -> ...
|
||||||
v_div_i = _mm_add_epi32(v_div_i, v_step);
|
v_div_i = _mm_add_epi32(v_div_i, v_step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No divisor found up to sqrt(n): n is prime.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user