Goldbach Vermutung
-
@Volkard, Bengo, Arcoth: Habe ich alle wichtigen Ideen zur Beschleunigung inkorporiert oder habe ich noch etwas Wichtiges übersehen? Was könnte man sinnvoll parallel laufen lassen? Zehn Zahlen gleichzeitig testen?
Die Klasse BigInteger bietet auch einen Algo für modexp:
BigUnsigned modexp(const BigInteger &base, const BigUnsigned &exponent, const BigUnsigned &modulus) { BigUnsigned ans = 1, base2 = (base % modulus).getMagnitude(); BigUnsigned::Index i = exponent.bitLength(); // For each bit of the exponent, most to least significant... while (i > 0) { i--; // Square. ans *= ans; ans %= modulus; // And multiply if the bit is a 1. if (exponent.getBit(i)) { ans *= base2; ans %= modulus; } } return ans; }
-
-
Wie gesagt, wuerde ich es nochmal mit einer loopenden Tabelle pobieren, die in den Cache passt um die Teilbarkeit durch die ersten Primzahlen zu pruefen. Das wuerde dann mehrere Divisionen durch eine Division und ein Lookup ersetzen. Gleichzeitig kann man das dann eben auch nutzen um den primes vector viel kleiner zu machen.
-
Ab dem Bereich 10^240 sind wir im Bereich ca. 1h/Geradzahl angelangt:
10^240, erste Primzahl 2351, t: 2215 s
10^241, erste Primzahl 2069, t: 3929 sHier findet man eine erste zum Paar passende Primzahl erst über 1000.
-
Das sind etwa 800bit, scheint mir so, als ob man das noch sehr optimieren kann, da mein Computer auch 4096 bit Primzahlen generieren kann.(In weniger als einer Stunde) bei SSL
-
Bengo schrieb:
Das sind etwa 800bit, scheint mir so, als ob man das noch sehr optimieren kann, da mein Computer auch 4096 bit Primzahlen generieren kann.(In weniger als einer Stunde) bei SSL
Welchen Zweck hätte eine 4096-Bit-Primzahl bei SSL? Sicher, daß Du da 4096-Bit-Primzahlen berechnen läßt?
Naja, SEHR optimieren nenne ich das nicht. Klar handoptimiertes asm an manche Stellen, MMX/SSE, Multitreading, damit kann man noch was holen. Mit der riesigen Gefahr, dass man sich dabei verzettelt und einen läppischen Faktor 10 kriegt und dafür Einbußen an der Einfachheit und Wartbarkeit hat und deswegen am Algo spart und billiardenfach lahmer ist.
-
Bitte nicht übersehen, dass wir bei 10^241 mit der ersten Primzahl 2069 in den 3929 Sekunden bei allen Primzahlen von 3 bis 2069 alle entsprechenden Partner "Geradzahl minus erste Primzahl" auf ihre Primzahlfähigkeit prüfen. Im Lookup findet da schon lange nichts mehr, man ist somit auf den kleinen Divisionstest (z.Z. 3 ... 101) angewiesen und auf den Milli-Miller-Rabin. Den Lügnertest kann man natürlich gar nicht erst laden und abschalten über 2^64.
Ich werde im nächsten Schritt das C++ 11 std::thread einbauen und verschiedene parallele Strategien testen.
Beispiele:
- mehrere Geradzahlen parallel auf Goldbach-Vermutung prüfen
- mehrere erste Primzahlen parallel auf ihren möglichen Primzahl-Partner prüfen (hier findet man aber nicht mehr die erste kleine Primzahl, sondern irgendein Paar)
-
volkard schrieb:
Bengo schrieb:
Das sind etwa 800bit, scheint mir so, als ob man das noch sehr optimieren kann, da mein Computer auch 4096 bit Primzahlen generieren kann.(In weniger als einer Stunde) bei SSL
Welchen Zweck hätte eine 4096-Bit-Primzahl bei SSL? Sicher, daß Du da 4096-Bit-Primzahlen berechnen läßt?
Naja, SEHR optimieren nenne ich das nicht. Klar handoptimiertes asm an manche Stellen, MMX/SSE, Multitreading, damit kann man noch was holen. Mit der riesigen Gefahr, dass man sich dabei verzettelt und einen läppischen Faktor 10 kriegt und dafür Einbußen an der Einfachheit und Wartbarkeit hat und deswegen am Algo spart und billiardenfach lahmer ist.
Stimmt hast recht, das RSA Modul hat 4096bit, die Primzahlen dann aber immernoch mehr als 2048bit.
-
Die Thread-Unterstützung habe ich mal eingebaut (noch nicht ideal) und im Bereich 10^120 bis 10^120 + 20 getestet (11 Geradzahlen, 11 Threads). Das läuft gut, man er hält die Ergebnisse in der gleichen Zeit wie vorher die langsamste Zahl in diesem Bereich. 11 Threads sind bei mir mit 12 virtuellen Kernen optimal. Hier das Programm, falls es jemand ausprobieren/optimieren will:
#include "stdafx.h" // MS VC++ #define NOMINMAX // due to conflict with max() #include <windows.h> // warning: no C++ standard! #include <iostream> #include <iomanip> #include <cstdint> #include <cstdlib> #include <cassert> #include <algorithm> #include <cmath> #include <ctime> #include <vector> #include <unordered_set> #include <fstream> #include <thread> #include <mutex> #include "BigIntegerLibrary.hh" // https://mattmccutchen.net/bigint/ //#define MILLER_RABIN_LIAR_CHECK using namespace std; void setConsole() // windows.h { _CONSOLE_SCREEN_BUFFER_INFO info; HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(handle, &info); COORD c; c.X = std::max<SHORT>(info.dwSize.X, 150); c.Y = std::max<SHORT>(info.dwSize.Y, 1000); SetConsoleScreenBufferSize(handle, c); SMALL_RECT RECT; RECT.Left = 0; RECT.Top = 0; RECT.Bottom = std::max(info.srWindow.Bottom - info.srWindow.Top, 40 - 1); RECT.Right = std::max(c.X - 1, 100 - 1); SetConsoleWindowInfo(handle, TRUE, &RECT); } void textcolor(unsigned short color = 15) // windows.h { SetConsoleTextAttribute(::GetStdHandle(STD_OUTPUT_HANDLE), color); } void wait() { cout << "Press any key to continue." << endl; cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cin.get(); } mutex mutex_; // for cout unordered_set<uint64_t> setLiar; uint64_t lastLiar = 0; uint64_t convertBigToU64(BigUnsigned num) { BigUnsigned ZweiHochVierUndSechzig = stringToBigUnsigned("18446744073709551616"); assert(num < ZweiHochVierUndSechzig); uint64_t jHi = num.getBlock(1); uint64_t jLo = num.getBlock(0); return (jHi << 32 | jLo); } void convertU64ToBig(uint64_t num, BigUnsigned& b) { uint32_t numLo = (uint32_t)(num & 0xFFFFFFFF); uint32_t numHi = (uint32_t)((num >> 32) & 0xFFFFFFFF); b.setBlock(0, numLo); b.setBlock(1, numHi); } //calculates (a * b) % c BigUnsigned mulmod(BigUnsigned a, BigUnsigned b, BigUnsigned mod) { BigUnsigned x = 0; BigUnsigned y = a % mod; while (b > 0) { if (b % 2 == 1) { x = (x + y) % mod; } y = (y * 2) % mod; b /= 2; } return x % mod; } BigUnsigned powmod_Volkard(BigUnsigned base, BigUnsigned exp, BigUnsigned modul) { //rechnet 'base hoch exp mod modul' BigUnsigned a1 = base, z1 = exp, x = 1, m = modul; while (z1 != 0) { while ((z1 % 2) == 0) { z1 /= 2; a1 = mulmod(a1, a1, m); } --z1; x = mulmod(x, a1, m); } return x; } bool IsProbablyPrime_MillerRabinOptimized(BigUnsigned n) { /* example: step: "calculate s and d" n = 1001 n-1 = 1000 s = 3 d = 125 1000 / 2^3 = 125 step: "test with base a" ad = a^d mod n (cf. function powmod) ... */ if (n == 2) return true; // "s" is the logarithm to base base 2 of the biggest number 2^s dividing n-1 // d = (n-1)/(2^s) // calculate d and s BigUnsigned d = n - 1; BigUnsigned s = 0; while ((d & 1) == 0) { ++s; d >>= 1; } // We do not use random base a = {2 ... n-1}, but fix it to a = 2 BigUnsigned a = 2; // base ("witness") BigUnsigned ad = (powmod_Volkard(a%n, d, n)); // (a^d) mod n if (ad == 1) return true; // a^d mod n == 1 if ((s > 0) && (ad == (n - 1))) return true; // a^d mod n == -1 (tests (a^d)^(2^0)) // test for r = {0 ... s-1} for (BigUnsigned r = 1; r < s; ++r) { // ad is currently ad^(2^(r-1)), thus we square it to get ad^(2^r)) ad = mulmod(ad, ad, n); if (ad == (n - 1)) return true; // tests (a^d)^(2^(r+1))) } return 0; // false, composite } bool IsPrimeDivisionTest(BigUnsigned number) { if (number<2) return false; if (number == 2) return true; if (number % 2 == 0) return false; for (BigUnsigned i = 3; i*i <= number; i += 2) { if (number%i == 0) return false; } return true; } bool IsPrime(vector<bool>& primes, BigUnsigned number, BigUnsigned primeLimit, unordered_set<uint64_t>& setLiar) { // lookup from bitset (in the RAM) if (number <= primeLimit) return primes[number.toUnsignedLong()]; // division test with first prime numbers if ( /*(number % 2 == 0) || */ (number % 3 == 0) || (number % 5 == 0) || (number % 7 == 0) || (number % 11 == 0) || (number % 13 == 0) || (number % 17 == 0) || (number % 19 == 0) || (number % 23 == 0) || (number % 29 == 0) || (number % 31 == 0) || (number % 37 == 0) || (number % 41 == 0) || (number % 43 == 0) || (number % 47 == 0) || (number % 53 == 0) || (number % 59 == 0) || (number % 61 == 0) || (number % 67 == 0) || (number % 71 == 0) || (number % 73 == 0) || (number % 79 == 0) || (number % 83 == 0) || (number % 89 == 0) || (number % 97 == 0) || (number % 101 == 0) ) { return false; } // Miller-Rabin, not perfect, but fast if (!IsProbablyPrime_MillerRabinOptimized(number)) { return false; // } else { if (lastLiar == 0) { return true; } BigUnsigned bigLastLiar; BigUnsigned& refBigLastLiar = bigLastLiar; convertU64ToBig(lastLiar, refBigLastLiar); if (number > bigLastLiar) { return true; } else { if (setLiar.find(convertBigToU64(number)) != setLiar.cend()) { // cout << "ALARM: Miller-Rabin-Liar found: " << number << " "; // wait(); return false; // function tells the truth by failure list (0 ... 2^64), above lastLiar there will be "Prime Hell" again. } return true; } } } void PrimePairFound(vector<bool>& primes, BigUnsigned i, const BigUnsigned primeLimit, unordered_set<uint64_t>& setLiar, bool* retVal) { *retVal = false; BigUnsigned grenze = i / 2; BigUnsigned a = 0; for (a = 3; a <= grenze; a += 2) { if (IsPrime(primes, a, primeLimit, setLiar) && IsPrime(primes, i - a, primeLimit, setLiar)) { *retVal = true; break; } } mutex_.lock(); cout << i << " " << setw(5) << a << endl; mutex_.unlock(); } int main() { setConsole(); // windows.h // big console for large numbers! ^^ textcolor(14); // windows.h // 10 = light green, 14 = light yellow BigUnsigned startNumber = stringToBigUnsigned("1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); BigUnsigned endNumber = stringToBigUnsigned("1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020"); const int numberOfThreads = 11; // count of even numbers from start to end = (end - start)/2 + 1 uint64_t const primeLimit = 2000000; //200000000000; cout << "We generate vector<bool>(primes) up to: " << primeLimit << endl; // (this amount depends on the capability of your RAM) vector<bool> primes(primeLimit); // processed numbers; time_t startTime, endTime; // measuring execution time uint64_t global_count = 0; // total number of primes time(&startTime); // initialize the number array primes[0] = false; primes[1] = false; for (uint64_t i = 2; i<primeLimit + 1; ++i) { primes[i] = true; } // sieving loop uint64_t iMax = (uint64_t)sqrt((double)primeLimit) + 1; for (uint64_t i = 2; i<iMax; ++i) { if (primes[i]) { for (uint64_t j = 2 * i; j<primeLimit + 1; j += i) { primes[j] = false; } } } time(&endTime); // count the number of primes for (uint64_t i = 0; i<primeLimit + 1; ++i) { if (primes[i]) global_count++; } cout << "In " << difftime(endTime, startTime) << " s we found " << global_count << " prime numbers (2 to " << primeLimit << " )." << endl << endl; #ifdef MILLER_RABIN_LIAR_CHECK // Prepare Miller-Rabin "Liar-Liste" and detect Goldbach prime pairs with Miller-Rabin test ifstream fileLiar("F:\\Daten\\Miller_Rabin_Liar\\2-SPRP-2-to-64.txt", fstream::in); // https://miller-rabin.appspot.com/ cout << "Miller-Rabin Liar file starts to be read from file." << endl; uint64_t count = 0; uint64_t u64LiarNumber = 0; while (!fileLiar.eof()) { fileLiar >> u64LiarNumber; // cout << u64LiarNumber << "\t"; setLiar.insert(u64LiarNumber); count++; } fileLiar.close(); lastLiar = u64LiarNumber; cout << "Miller-Rabin Liar file with " << count << " liars is read." << endl << "Last liar: " << u64LiarNumber << endl << endl; #endif // MILLER_RABIN_LIAR_CHECK unordered_set<uint64_t>& refSetLiar = setLiar; #ifdef MILLER_RABIN_LIAR_CHECK cout << "Now Goldbach first prime number pair will be detected:" << endl; #else cout << "Now Goldbach probable (no liar list available) first prime number pair will be detected:" << endl; #endif clock_t zeit1, zeit2, zeit2_old, delta; zeit2 = zeit1 = clock(); uint64_t sumTime = 0; for (BigUnsigned i = startNumber; i <= endNumber; i += (2 * numberOfThreads)) { bool retVal[numberOfThreads]; vector<thread> t; for (int x = 0; x<numberOfThreads; ++x) { retVal[x] = false; if ((i+2*x) <= endNumber) t.emplace_back(PrimePairFound, primes, i+2*x, BigUnsigned((unsigned long)primeLimit), refSetLiar, retVal + x); } for (int x = 0; x < numberOfThreads; ++x) { if ((i + 2 * x) <= endNumber) { t[x].join(); // we have to wait for the result of the thread! if (retVal[x] == false) cout << "Counterevidence found for this number: " << i + 2 * x << endl; } } zeit2_old = zeit2; zeit2 = clock(); delta = zeit2 - zeit2_old; sumTime += delta; cout << "time: " << 1000 * delta / CLOCKS_PER_SEC << " ms" << endl << endl; } cout << "total time: " << sumTime/1000 << " s" << endl << endl; wait(); }
`We generate vector<bool>(primes) up to: 2000000
In 0 s we found 148933 prime numbers (2 to 2000000 ).
Now Goldbach probable (no liar list available) first prime number pair will be detected:
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018 191
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 173
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 179
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008 181
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016 733
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004 613
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020 193
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010 619
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012 719
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002 709
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014 1153
time: 237940 ms
total time: 237 s`
-
Erhard Henkes schrieb:
Die Thread-Unterstützung habe ich mal eingebaut (noch nicht ideal) und im Bereich 10^120 bis 10^120 + 20 getestet (11 Geradzahlen, 11 Threads). Das läuft gut, man er hält die Ergebnisse in der gleichen Zeit wie vorher die langsamste Zahl in diesem Bereich. 11 Threads sind bei mir mit 12 virtuellen Kernen optimal. Hier das Programm, falls es jemand ausprobieren/optimieren will:
Hab nur 2 Kerne
-
Hab nur 2 Kerne
Dann stellst Du die numberOfThreads auf 2, 3 oder 4 (dein OS kümmert sich um das MT Scheduling) ein. Ich baue das noch um, sodass die richtige Threadanzahl verwendet wird und diese auch immer etwas zu schaffen haben.
Probiere momentan verschiedene Abläufe aus.Wichtige Frage: Kann man die Gesamtauslastung (vom Prg aus) des gesamten Programms auf einen gewissen Wert (z.B. 50 oder 80%) einstellen?
-
Hier ein erster Test für 10^240 (!):
`Now Goldbach probable (no liar list available) first prime number pair will be detected:
...04 97
...16 109
...10 103
...08 101
...14 107
...20 113
...06 2357
...18 3527
...00 2351
...02 2039
...12 5279
total time: 3654 s`
Zeitbestimmend bei der Primzahl-Paarsuche ist die Zahl 10^240 + 12, da wir erst bei 5279 fündig werden. Hier könnten vlt. noch mehr Primzahlen (bisher: 3 ... 101) beim Pre-Divisions-Check helfen.
Man sieht, dass nun die Primzahlabstände (nach unten) von der untersuchten Geradzahl nun auch mehrfach im Bereich über 1000 liegen (bei 10^120 war es nur eine von elf Zahlen). Gibt es hier eine Regel für den Abstand zwischen zwei Primzahlen in Abhängigkeit vom untersuchten Zahlenbereich?
Dieser Zahlenbereich 10^240 stellt somit in etwa die Grenze dar für das aktuelle Programm. Ich gehe davon aus, dass Goldbach mit seiner Vermutung recht behalten wird. Das Programm ist auch ein hübscher CPU-Stresstest zum Testen neuer PCs. Einfach den Bereich X bis X + 100 (X: sehr große Zahl) berechnen lassen und Zahl der Threads entsprechend auf 51 einstellen.
-
Wichtige Frage: Kann man die Gesamtauslastung (vom Prg aus) des gesamten Programms auf einen gewissen Wert (z.B. 50 oder 80%) einstellen?
macht man ja üblicherweiße über OS. alternativ kann man es ja mit nem sleep machen und zeit/tickcount nehmen
edit: so gehts ein wenig bequemer, wenn ichs richtig überflogen habe:
https://technet.microsoft.com/en-us/library/ff384148.aspx?f=255&MSPPError=-2147217396
-
Ich habe doch noch mal 10^300 geprüft:
`..04 73
..10 79
..14 83
..02 71
..20 89
..06 353
..12 359
..00 347
..16 6427
..08 3767
..18 12149
total time: 14226 s
`
Da in dieser Region der Abstand zur nächsten Primzahl (nach unten) in einem Fall (10^300 + 18) auf fünfstellige Zahlen anwächst, braucht die Berechnung fast 4 Std. Das ruft nun wirklich neue Algos auf den Plan.
-
Irgendwas stimmt mit deinem Multithreading nicht richtig. Ich hab die Diskussion hier nur so halb verfolgt, wollte aber mal das Programm ausprobieren. Neuere gcc Versionen (4.9) beschweren sich über diese Zeile:
t.emplace_back(PrimePairFound, primes, i+2*x, BigUnsigned((unsigned long)primeLimit), refSetLiar, retVal + x);
Mit einer älteren Version (4.4) compiliert es immerhin. Ausschnitt der Fehlermeldung:
main.cpp:336:125: required from here /opt/rh/devtoolset-3/root/usr/include/c++/4.9.2/functional:1665:61: error: no type named ‘type’ in ‘class std::result_of<void (*(std::vector<bool>, BigUnsigned, BigUnsigned, std::unordered_set<long unsigned int>, bool*))(std::vector<bool>&, BigUnsigned, BigUnsigned, std::unordered_set<long unsigned int>&, bool*)>’ typedef typename result_of<_Callable(_Args...)>::type result_type;
Ich denke das Problem ist, dass deine
PrimePairFound
Funktion einige Parameter als Referenz nimmt. Wenn ich den Code so ändere compiliert es auch mit gcc 4.9:t.emplace_back(PrimePairFound, std::ref(primes), i+2*x, BigUnsigned((unsigned long)primeLimit), std::ref(refSetLiar), retVal + x);
Aber selbst wenn es compiliert kriege ich einen Segfault in genau dieser Zeile.
-
Im MS VS 2015 läuft es ohne Probleme. Daher ändere ich da gar nix.
-
Erhard Henkes schrieb:
Im MS VS 2015 läuft es ohne Probleme. Daher ändere ich da gar nix.
Nicht gerade die beste Einstellung. Ein Segfault kommt auch nicht einfach so. Irgendwas geht dort wohl ziemlich schief. Warum es bei VS2015 nicht passiert oder man nichts bemerkt weiß ich nicht. Vielleicht versuche ich nacher mal den Fehler einzugrenzen.
-
Ok sorry. Das Problem saß mal wieder vorm Bildschirm. Seit wann muss man pthread dazu linken wenn man
std::thread
benutzt? Naja läuft jetzt jedenfalls wobei das mit demstd::ref
trotzdem nötig war.
-
OK, über den wrapper std::ref kann man reden. Das ist eher eine Verbesserung. Danke für den Hinweis.
t.emplace_back(PrimePairFound, std::ref(primes), i+2*x, BigUnsigned((unsigned long)primeLimit), std::ref(refSetLiar), retVal + x);
-
sebi707 schrieb:
Ok sorry. Das Problem saß mal wieder vorm Bildschirm. Seit wann muss man pthread dazu linken wenn man
std::thread
benutzt?Immer.
Naja läuft jetzt jedenfalls wobei das mit dem
std::ref
trotzdem nötig war.Jo. Die
result_of
-Zeile hätte sonst nicht kompiliert, schließlich werden die Argumente mitdeclval
simuliert, welches standardmäßig rvalues produziert.