C++ Code Optimierung.

ZuseZ3

Lt. Commander
Registriert
Jan. 2014
Beiträge
1.659
Code:
// primefactors.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
//

#include "stdafx.h"
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <cmath>
#include <vector>
#include <stack> 
#include <list>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	// Erster Programmteil, erstellen der primelist
	int stop = 0, z = 0;
	const unsigned int border = 1000000;
	std::vector<int> list;
	//auto count = std::begin(list);
	for (int b = 0; b < border; b++) list.push_back(b);/*Liste von 0-border-1 anlegen*/
	int primenumber = 2;
	int counter = 2; unsigned int number = 2;
	while (primenumber <= border / 2 && stop == 0)/*vielfache der Primzahl nullen*/
	{
		while (primenumber <= border / 2 && counter*primenumber < border - 1)
		{
			list[primenumber * counter] = 0;
			counter += 1;
		}
		if (++number < border) { primenumber = list[++number]; } /*nächste Primzahl finden*/
		else { stop = 1; }
		counter = 2;
		while (primenumber == 0 && primenumber < border)
		{
			primenumber = list[++number];
		}
	}
	std::vector<int> primelist;
	for (auto it = std::begin(list); it != std::end(list); ++it)
		if (*it != 0) primelist.push_back(*it);
	while (!list.size()) list.pop_back();
	list.resize(0);
	//for (int i = 0; i < 10; i++) std::cout << primelist[i] << ';' ; /* Kontrolle *(
	std::cout << ',' << 'r' << 'e' << 'a' << 'd' << 'y' << ',' ;
	

	// Zweiter Teil, berechnen der Primeteiler
	unsigned long long int summe = 0, upperborder = pow(10, 8);
	int m = 0, psize = 0;
	/*for (int zahl = 1; zahl <= upperborder; m = pow(zahl, 15) + 1, zahl += 1)
	{
		psize = primelist.size();
		for (int rest = zahl, i = 0, counter = primelist[i]; rest > 0 && i < psize; i++)
		{
			if (rest % counter == 0) { for (; rest / counter == 0; rest /= counter); summe += counter; }
			counter += 1;
		}
	}*/

	// Zweiter Teil, neue herangehensweise:

	std::vector<int> result;
	for (int b = 0; b < upperborder; b++) result.push_back(b);/*Liste von 0-border-1 anlegen*/

	std::cout << 'f' << 'i' << 'n' << 'a' << 'l' << 'y' << ',' << summe ;

	for (int i = 0; i < 10; i++) std::cout << pow(i, 15) + 1 << ';';
	std::cin >> stop;
	return 0;

Code:
// primefactors.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
//

#include "stdafx.h"
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <cmath>
#include <vector>
#include <stack> 
#include <list>
#include <limits.h>
#include <chrono>
using namespace std;
using namespace std::chrono;
int _tmain(int argc, _TCHAR* argv[])
{
	// Erster Programmteil, erstellen der primelist
	int stop = 0, z = 0;
	unsigned int border = pow(10,6) /*muss spaeter pow(10,8) sein*/, newborder = pow(10,4); /*muss spaeter pow(10,11) sein*/
	std::vector<int> vektor;
	vektor.resize(border);
	high_resolution_clock h;
	float tone = static_cast<unsigned>((h.now() - system_clock::from_time_t(system_clock::to_time_t(h.now()))).count() * static_cast<float>(1000) / high_resolution_clock::period::den);
	for (int b = 0; b < border; b++) vektor[b] = b;/*Liste von 0-border-1 anlegen*/
	int primenumber = 2;
	int counter = 2; unsigned long long int number = 2;
	while (primenumber <= border / 2 && stop == 0)/*vielfache der Primzahl nullen*/
	{
		while (primenumber <= border / 2 && counter*primenumber < border - 1)
		{
			vektor[primenumber * counter] = 0;
			counter += 1;
		}
		if (++number < border) { primenumber = vektor[++number]; } /*nächste Primzahl finden*/
		else { stop = 1; }
		counter = 2;
		while (primenumber == 0 && primenumber < border)
		{
			primenumber = vektor[++number];
		}
	}
	int ttwo = static_cast<unsigned>((h.now() - system_clock::from_time_t(system_clock::to_time_t(h.now()))).count() * static_cast<float>(1000) / high_resolution_clock::period::den);
	vector<int> primelist;
	for (auto it = std::begin(vektor); it != std::end(vektor); ++it)
		if (*it > 1) primelist.push_back(*it);
	vektor.clear();
	std::cout << 'r' << 'e' << 'a' << 'd' << 'y' << ',';


	int zahl = 1, ueberlauf = 0;
	unsigned long long int summe = 0;
	while (zahl < newborder)
	{
		number = pow(zahl, 15) + 1;
		counter = 0;
		while (number > 1 && counter < primelist.size())
		{
			if (number % primelist[counter] == 0)
			{
				if (summe + primelist[counter] > 18446744073709551615) ueberlauf += 1;
				summe += primelist[counter];
				for (; number % primelist[counter] == 0;) { number = number / primelist[counter]; }
			}
			counter++;
		}
		zahl++;
	}
	std::cout << 's' << 'u' << 'm' << 'm' << 'e' << '=' << summe << ';';
	std::cout << 'f' << 'i' << 'n' << 'a' << 'l' << 'y'  /*<< summe << '/'*/;
	if (ueberlauf != 0)	std::cout << 'u' << 'e' << 'b' << 'e' << 'r' << 'l' << 'a' << 'u' << 'f' << '=' << ueberlauf;
	std::cout << '-' << '>' << ttwo - tone; //Zeit zum erstellen der liste?
	std::cin >> stop;
	return 0;
}

Hier ist die genaue Aufgabenstellung: https://projecteuler.net/problem=421
Ich möchte hier ausdrücklich keine Lösung sondern nur Hinweise wie ich mein Programm optimieren kann.
Ich bin selbst noch Programmier anfänger, lese zwar u.a. die c++ einführung von Ulrich Breymann aber habe nur wenig Erfahrung. So habe ich mich z.B. heute das erste mal mit Stacks beschäftigt weil ich mit den normalerweise für arrays vorgesehenen 4 MB nicht mal ansatzweise auskam. Seid also bitte nachsichtig wenn ich murks produziert habe ;)

Wie ich es bereits in anderen Aufgaben im Zusammenhang mit Primzahlen gemacht habe erstelle ich im ersten Teil eine liste aller benötigten primzahlen (hier unter 10^8) in 5 sek, dieser Teil funktioniert dank des Sieb des Eratosthenes schnell genug.

Der 2 Teil braucht jedoch deutlich länger. Meine erste Idee jede Zahl unter 10^11 einzeln zu erstellen, die primfaktoren mithilfe der im ersten Teil erstellten Liste zu finden und sich erst dann mit der nächsten Zahl zu beschäftigen habe ich erstmal verworfen.
Meine Überlegung war, dass ich erstmal eine liste aller zahlen unter 10^11 anlege und diese später in einem durchlauf abarbeite, dh. beim ersten schleifendurchlauf gucke welche zahlen durch 2 Teilbar sind, beim nächsten welche durch 3 Teilbar sind und so weiter... Die Abfrage müsste man später ja auch hervorragend parallelisieren können. Jedoch habe ich ein großes Problem, mein eigener PC hat nicht die beste Hardware und bisher habe noch keine Entwicklungsumgebung auf den Uni servern so verändert, dass ich darauf c++ programme schreiben kann. (Ich habe dort lediglich mit Python/bash gearbeitet) Bisher habe ich mich da auch nicht wirklich mit beschäftigt weil die Code optimierung eine viel größere Wirkung haben müsste.
Zurzeit überlaste ich meinen PC aber bereits damit eine liste aller Zahlen bis 10^9 anzulegen. Aufgrund meines Q6600 Prozessors, nur 6 GB DDR2 Ram und der tatsache das meine SSD nur über sata2 angeschlossen ist braucht mein PC mehr als 10 min bis ich die fertige liste aller zahlen bis 10^9 habe. Davon eine liste bis 10^11 anzulegen möchte ich gar nicht reden. Aufgrund der großen Zahlen die ich am ende habe (bis zu 100 Milliarden) sollte ich diese evtl doch direkt bearbeiten statt sie erst zwischenzuspeichern? Dafür müsste ich dann halt jede zahl einzeln durchrechnen. Das müsste aber nochmal um ein vielfaches länger dauern als das anlegen der liste oder habe ich da irgendeinen besonders schnellen befehl übersehen?
Anmerkung: ich habe gerade eine info über das Quadratisches Sieb gefunden aber selbst das müsste doch ewigkeiten brauchen um eine derartige Anzahl an Zahlen zu zerlegen, oder?

Im Zweifelsfall habe ich auch zugriff auf 4 oder 8 GeForce GTX 470 Grafikkarten und dank vs 2013 pro auch die entsprechenden librarys um solche programme zu erstellen. Jedoch dürfte das, so interresant es auch wirkt noch umfangreicher werden.

Ich würde mich dann jetzt daran machen doch jede Zahl nacheinander durchzurechnen, dies könnte man später ja ebenfalls paralellisieren. Aber so oder so dürfte das nicht mal annähernd schnell genug sein, Tips sind also gerne gesehen.
 
Zuletzt bearbeitet:
Also zuerst: Warum nicht die Primzahlen direkt in die Primelist speichern? Und die kannst du als std::set<int> machen, dann kannst du mit Count(zahl) direkt prüfen, obs ne Primzahl ist.

Dann: Ich denke, da kann man mit einer Cache einiges erreichen. n^15=n/2^30, ich denke, da stecken ein paar Eigenschaften drin, die dir Helfen können, die Primfaktoren schneller zu finden.

EDIT: Die Gleichung ist natürlich murks, aber die Richtung sollte klar sein.
 
Zuletzt bearbeitet:
Die Namensgebung ist für C++-Programmierer vielleicht ein wenig verwirrend: list ist kein Liste (also std::list) sondern ein Vektor. Der Vektor muss eventuell auch bei jedem push_back() neu allokiert werden. Da du die Größe der "list" aber im Vorfeld kennst, kannst du diesen Aufwand vermeiden:
Code:
list.resize(border);
Löschen/leeren kannst du den Vektor übrigens einfach per clear().
Wenn du später den Rest der Aufgabenstellung ausrechnest, dann solltest du auf den Wertebereich achten. 10^10 passt nicht mehr in den Datentyp int (auf den mir bekannten Architekturen).
 
danke für die tips, ich habe den Vektor schlicht und ergreifend in Vektor umzubenannt, der list kommt daher dass ich den ersten Teil bereits unter Python mithilfe einer Liste programmiert habe. Den clear() befehl habe ich ebenfalls eingefügt.
Außerdem habe ich die vektorgröße bei der Erstellung eingefügt, ich habe gerade erst gelesen, dass der vektor sonst evtl. mehrmals an andere Stellen koppiert werden muss was eigentlich logisch ist.
Was deinen ersten Tip angeht, Hancock, verstehe ich nicht ganz was du mit gleich in die primelist speichern meinst.
Da ich das Sieb des Eratosthenes nutze muss ich erst eine liste aller Zahlen unter der grenze anlegen und die vielfachen einer primzahl nullen.
Löschen geht nicht, da sich sonst die indexe aller folgenden Werte ändern würden. Somit habe ich am ende zwangsläufig eine liste bestehend aus primzahlen und nullen und muss nur noch die Primzahlen rauskopieren. die nullen zu löschen würde ja länger dauern oder irre ich mich?
Ich verstehe nicht ganz wie ich die Primzahlen früher in Primelist speichern sollte.
Was set genau für eigentschaften gucke ich mir gerade an, das ändere ich dann in kürze.
Und danke für den Tip, da habe ich mir bisher noch keine Gedanken zu gemacht weil ich erstmal ein evtl. auch langsameres grundprogramm schreiben wollte. Sobald ich den rest geändert habe denke ich mal drüber nach.
Ich änder das Programm auch nochmal so, dass ich im 2. Teil doch keine Liste anlege sondern gleich jede Zahl durchrechne, dann wird wenigstens nichts mehr auf der SSD zwischengespeichert und ich dürfte auch den Cach besser nutzen.

Sobald ich mein Programm überarbeitet habe stelle ich es wieder rein.
Ergänzung ()

Ich habe das Programm nochmal überarbeitet und number sicherheitshalber als ull deklariert.
Da die Geschwindigkeit noch nicht ausreicht mache ich mich mal daran die Primfaktoren etwas intelligenter rauszufischen.
Ich habe versucht über tone und ttwo die benötigte Zeit zum erstellen der Liste herauszufinden, die Ergebnisse passen aber nicht ganz, ich nehme an da muss ich nochmal was dran ändern.

@Hancock, bist du dir eigentlich bzgl. deines Hinweises sicher?
schlieslich addiere ich am ende noch +1.
 
Zuletzt bearbeitet:
Teil 1 ist nicht dein Problem, ist egal ob du das effizient löst oder nicht.
Überleg dir mal wie du für die größten Zahlen in Teil 2 überhaupt das Teilergebnis ausrechnen willst. Wie schon von aphex.matze angemerkt, mit int kommst du da gar nicht weiter (egal ob 32 oder 64 bit int), denn pow(zahl, 15) produziert dir wie nichts einen overflow (oder ist überhaupt gleich undefiniertes Verhalten).
(10^11)^15 passt auch nicht mehr in ein ('normales' 32-bit) float, ein double würde mal gehen. Aber ... du brauchst ja auch die entsprechende precision um auch die ganzen Operationen durchzuführen (Division durch eine Primzahl etc. - Fliesskommazahlberechnungen sind ja nicht exakt!), und da muss man mal testen ob sich das dann auch noch ausgeht. Ev. brauchst du einen eigenen high-precision Datentyp (gibts einige libraries dazu).
Krieg das ganze also erstmal für die paar höchsten Zahlen überhaupt mal korrekt zum laufen, und dann überleg dir wie man performance verbessern kann. Berichte mal wie schnell es für die z.B. 1000000 größten Zahlen ist um abzuschätzen ob da 'kleine' Codeoptimierungen oder Parallelisierung überhaupt was praktisches nutzen können, oder man das algorithmisch vereinfachen muss.
Welchen Compiler nutzt du?
 
Hey,

bevor du anfängst deinen Code zu optimieren solltest du erst einmal schauen, ob dein Algorithmus wirklich richtig ist. Ich habe deinen Code nicht direkt getestet sondern nur den "ersten Teil" übernommen. Meiner Meinung nach funktioniert dein zweiter Teil nicht so, wie er sollte. Die Primfaktorzerlegung ist glaube ich fehlerhaft. Du überprüfst auch nirgendwo, ob der Primfaktor unterhalb von m liegt. Außerdem überprüfst du nicht auf doppelte Primfaktoren (die sollen ja nur einmal addiert werden).

Dein Ansatz, eine Liste von allen Zahlen bis 10^11 zu machen ist auch nicht sinnvoll. Ob du jetzt nacheinander prüfst, ob alle Zahlen durch zwei teilbar sind oder ob du bei jeder Zahl gleich die Primfaktorzerlegung machst kommt auf das gleiche raus. Nur das letzterer Fall (fast) keinen Speicher braucht.

Das schöne an Project-Euler ist ja, dass auch immer ein paar beispiele gegeben sind. Hast du denn schonmal versucht, das S(10, 100) von dem Beispiel zu reproduzieren? Ich habe das Problem denke ich soweit gelöst, will dir aber nicht einfach die Lösung posten. Wenn du noch Schwierigkeiten hast, dann kannst du ja nochmal fragen.
 
Erstmal danke für die ganzen Hinweise, schön wie umfangreich einem hier geholfen wird.

Die erste entscheidende sache die mir gerade klargeworden ist, ist das long long keinen Einfluss auf int nimmt, sondern ein eigenständiger Typ ist. Ich habe mich bei früheren, nochmals deutlich einfachereren C++ Programmen nie sonderlich damit beschäftigt, damals hat es immer gereicht wenn ich long long vor das int gesetzt habe. Auch bei Python hatte ich nie probleme damit und habe mir das deshalb so angewöhnt. :rolleyes: Ich schaue mir das nacher nochmal genauer an.

was stwe angeht, die Liste nutze ich auch bei der neueren Variante nicht mehr da mir klar geworden ist wie langsam selbst eine SSD mit DDR2 RAM im verlgeich zum Prozessor (Cache) ist.

Was die grenze m angeht, die liegt bei 10^8. Da ich zum rausfinden der Primfaktoren die Primzahlenliste nutze und ich in diese nur primzahlen unter 10^8 reingeschrieben habe muss ich nicht nocheinmal zusätzlich kontrollieren ob der Primfaktor wirklich unter m liegt.

Und was die doppelten Primfaktoren angeht, wir hatten das ∑ zeichen in der Schule noch nicht. Jedoch bedeutet es soweit ich es aus dem Wikipedia Artikel rausgelesen habe grob gesagt, dass man die Ergebnisse der Zwischenschritte einfach addieren soll, daher:
s(1,10^8) + s(2,10^8) + s(3,10^8)+...
Dadurch muss man nur innerhalb der einzelnen Summanten dafür sorgen, dass jeder Primfaktor nur einfach zu dem Ergebniss addiert wird. Wenn 2^15 +1 beispielsweise durch 9 teilbar währe, und 3^15 +1 durch 3 teilbar währe würde die 3 in beiden Teilergebnissen einfach auftauchen und ich die summe insgesamt um 6 erhöhen, oder habe ich hier schon einen Fehler drin?

Das ein Primfaktor selbst wenn er mehrfach auftaucht nur einmal zum zwischenergebnis addiert wird habe ich durch folgende schleife erledigt:
Code:
	if (number % primelist[counter] == 0)
{
summe += primelist[counter];
for (; number % primelist[counter] == 0;) { number = number / primelist[counter]; 
}

Irgendeinen groben Fehler muss ich aber irgendwo beim Berechnen der Primteiler gemacht haben, die Primzahlenliste ist ja richtig, die habe ich bereits kontrolliert. Dennoch erhalte ich wenn ich die Primfaktoren einzelner Zahlen berechne noch falsche Ergebnisse.

Sobald ich den Algorithmus korrigiert habe, stelle ich ihn oben rein.
Heute werde ich wahrscheinlich nicht mehr dazu kommen das ganze auch noch für größere Zahlen funktionsfähig zu machen, da beschäftige ich mich dann in nächster Zeit mit.
 
aphex.matze schrieb:
Der Vektor muss eventuell auch bei jedem push_back() neu allokiert werden. Da du die Größe der "list" aber im Vorfeld kennst, kannst du diesen Aufwand vermeiden:
Code:
list.resize(border);

Sollte er seinen vector gleich zu Beginn mit resize() auf die gewünschte Größe setzen, dann sollte er aber neue Elemente auch nicht mit push_back() anfügen, sonst wird der vector am Ende doppelt so groß, wie er sein sollte. Besser: statt list.resize(border); lieber list.reserve(border);

Damit bleibt die Größe des vectors zu Beginn Null, aber seine Kapazität wird schon mal vorsorglich auf die zu erwartende Maximalkapazität gesetzt, so daß anschließend keine Reallokierungen mehr nötig sind.
 
in dem obigen Quellcode habe ich beim ersten Teil resize genutzt und dann jeden Wert mit seinem Indes gleichgesetzt:
Code:
std::vector<int> vektor;
vektor.resize(border);
for (int b = 0; b < border; b++) vektor[b] = b;

In der 3, neusten Version habe ich bei dem zweiten vektor, welcher die Primzahlen beinhaltet aber den resize befehl genutzt, danke für den Vorschlag.
Auch das erstellen der Primliste habe ich verbessert, das funktioniert nun endlich auch korrekt.
Ich werde gleich nochmal gucken warum es fehler beim bestellen der Primfaktoren gibt und den code sobald ich ihn verbessert habe wieder reinstellen.
 
Zuletzt bearbeitet:
Zurück
Oben