Ich hab es jetzt doch geschafft Threads einzubinden. Leider nicht optimal, aber bei 7 Thread erreiche ich jetzt die 0.75% in 3.59 Minuten. Also eine deutliche Steigerung.
Leider stehen sich die Threads etwas selbst in weg. Die Berechnungen erfolgen leider so, das der Thread die Zwischenergebnisse von "Vorgängerthread" braucht. Zumindest die Auswertung der Counts-Tabelle (sind ja auch mehrere Tausend Einträge, tendens steigend.) kann dann parallel erfolgen.
Und ich hab eine Menge Kommentare hinzugefügt. Die Thread-Verwaltung kann man sicher noch optimieren, bspw. das Dim durch einen Speicherblock ersetzen. Aber ich bin jetzt Müde
edit: Irgendein Trick muss es geben, den ich nicht kenne. ich würde ja gerne in Project Euler-Forum spicken, aber völlig unsinnigerweise kann man erst darauf zugreifen, wenn man die Aufgabe gelöst hat. Würde mich nicht wundern, wenn es eine Formel gibt, um die Anzahl der Nullstellen auszurechnen oder sowas.
Code: Alles auswählen
EnableExplicit
;in Counts werden die Werte und wieoft sie vorkommen gezählt
Structure counts
value.q
count.i
EndStructure
;Da die Werte in Counts "verteilt" sind, brauch ich einen Index, damit ich gezielt auf die Werte zugreifen kann.
Structure index
*count.counts
EndStructure
;Hier der Trick, ich berechne nicht für jedes A alle B zusammen, sondern nur solange, wie ich unter einen Limit bleibe. So erstelle ich Blöcke, wo ich die Zwischenergebnis in Counts halten und auswerten kann.
Structure limits
currentValue.q
b.i
a3.i
EndStructure
;Struktur für Threadübergabe
Structure th
aoldlimit.i
alimit.i
aup.i
amin.i
*aminlimit.limits
*prethreadA.integer
a.i
*index.index
*counts.counts
result.i
threadid.i
EndStructure
Define amin,maxa,aup,oldaup,alimit,aoldlimit
Define timer,oldtimer
Define result,i,a
;Define a,b,k
Define speed.d,eta.d
;Define maxcount
Define *limits.limits,*alimit.limits,*aminlimit.limits,*lim.limits
Global buffersize
;Define *counts.counts
Define *currentcount.counts
;Define *index.index,*writeindex.index
;Die Startbedingungen als Konstanten.
; 123456789012345
#n=1000000000000000
#r=6
;Wieviele Threads erstellt werden sollen. Ich hab ein 4Kern+Hyperthread. 7 gab beim Test die besten Ergebnisse.
#pro=7
;Hilfswert - größte mögliche wert
#setdone=9223372036854775807
OpenConsole()
PrintN(#PB_Compiler_File)
;Es werden hier die Maximale A berechnet, wenn k=N und b=1. Das ist sozusagen das absolute maximum
maxa=IntQ((-1.5 + Sqr(1.25+#n)))
;Buffergröße - für jeden Thread werden 2xBuffersize erstellt. Schlamping programmiert, bspw. braucht der Index bei weiten nicht so viel
buffersize=maxa+10000
;die Threaddaten vorstellen
Dim th.th(#pro)
th(0)\a=#setdone ;Thread Null gibt es nicht, aber da der Thread auf den Vorgänger immer warten muss, simuliere ich hier einen fiktiven Thread 0 der immer fertig ist.
For i=1 To #pro
th(i)\prethreadA=@th(i-1)\a; Den Thread zugriff auf das a des Vorgängerthreads gewähren. Wird wichtig, weil die Berechnungen immer von Vorgänger abhängig sind!
;Hier wird der Counts-Speicher reserviert
th(i)\counts=AllocateMemory(SizeOf(counts)*(buffersize+1))
If th(i)\counts=0
PrintN("Speicherfehler counts "+i)
Input()
End
EndIf
;Wir setzen eine "Endmarkierung", falls bei der Suche nach einen freien Platz man am limit kommt.
*currentcount=th(i)\counts+SizeOf(counts)*buffersize
*currentcount\value=#setdone ;endmarkierung
;wir reservieren Indexspeicher
th(i)\index=AllocateMemory(SizeOf(index)*(buffersize+1))
If th(i)\index=0
PrintN("Speicherfehler index")
Input()
End
EndIf
Next
; Das Ding hier ersetzt die Map. man könnte auch eine Prozedur machen, aber ich geh lieber auf Geschwindigkeit
Macro addcount(i)
;Wir errechnen ein Position. Ich nutze als Hash-Funktion einfach %.
*currentcount=*counts+((i)%buffersize)*SizeOf(counts)
Repeat
; Position ist mit genau unseren Wert besetzt - damit zählen wir ihn einfach hoch.
If *currentcount\value=i
*currentcount\count+1
Break
; an der Position ist ein wert <aoldlimit (das ist die alte Grenze für k). Der Bereich ist also leer und wir füllen ihn mit standard-werten.
ElseIf *currentcount\value<aoldlimit
*currentcount\value=i
*currentcount\count=1
;in Index wird die Counts-Position eingetragen. Wird zum Ergebnis-Feststellung benötigt.
*writeindex\count=*currentcount
*writeindex+SizeOf(index)
Break
Else
;blöderweise gabs eine Kollision, die Stelle ist besetzt. Wir schauen einfach auf die nächste Stelle. Falls wir das Ende des Speichers erreichen, springen wir zum anfang.
*currentcount+SizeOf(counts)
If *currentcount\value=#setdone
*currentcount=*counts
EndIf
EndIf
ForEver
EndMacro
;Hier wird eine Reihe runtergerechnet. Blöderweise können wir ein A nur berechnen, wenn er schon in der Vorinstanz berechnet wurde.
Procedure mythread(*th.th)
Protected a,b,k
Protected *lim.limits ; enthält das aktuelle Limits-element
Protected aoldlimit=*th\aoldlimit ; die Alte Grenze, wird zum erkennen einer leeren Stelle benötigt
Protected alimit=*th\alimit ; die aktuellen werte. K muss kleiner sein als dieser Wert, ansonsten wird er zwischengespeichert.
Protected aup=*th\aup ; die aktuelle obere Grenze von A. quasi das aMax für n.
Protected amin=*th\amin ; amin ist das minimum a - die a kleiner als der wert wurden schon vollständig berechnet.
Protected *aminlimit.limits=*th\aminlimit ; das Limits-Element für den amin-wert.
Protected *index.index=*th\index ; Indextabelle
Protected *counts.counts=*th\counts ; counts-ge-hashte-tabelle
Protected *currentcount.counts ; hilfsvariable - ein Element in der Counts-Tabelle
Protected *writeindex.index=*index ; Schreibposition in der Indextabelle.
*lim.limits=*aminlimit
For a=amin To aup-1
;wir erlauben den Thread nach uns, werte kleiner a zu berechnen
*th\a=a
;und warten bis der Thread vor uns fertig mit seiner Berechnung des aktuellen a ist
While a>=*th\prethreadA\i
Delay(0)
Wend
;Wenn der Grenzwert des bereits berechneten B kleiner als das limit ist, können wir weiterrechnen.
k=*lim\currentValue
If k<alimit
;in der Hashtabelle den wert k eintragen, bzw den zähler erhöhen (siehe macro)
addcount(k)
;wir markieren das aktuelle a als "berechnet in der limits-tabelle. Falls es doch nicht berechnet werden kann, setzen wir es einfach zurück.
*lim\currentValue=#setdone
;alle Bs durchgehen - sofern es unter den alimit bleibt
For b=*lim\b+1 To a-1
k+*lim\a3+B+b-1
;check, um sicher zu stellen, das kein Mist gebaut wurde :) sollte auskommentiert werden, wenn man scharf rechnet.
;If k<>a*a+3*a*b+b*b
; PrintN("Falscher wert")
;EndIf
;Wert überhaupt gültig? nein -> abbruch
If k>#n
Break
;wert kleiner als unser gewähltes Limit? -> in tabelle eintragen
ElseIf k<alimit
addcount(k)
;wert zu groß -> wert merken und aufhören
Else
*lim\b=b
*lim\currentValue=k
Break
EndIf
Next
EndIf
;a erhöht sich automatisch, wir erhöhen die Hilfsvariable *lim auf das nächste element.
*lim+SizeOf(limits)
Next
;wir sind jetzt fertig. Da der folgende Thread immer einen höheres a berechnet als wir, setzen wir ihn einfach aufs maximum.
*th\a=#setdone
;wir gehen die Index-tabelle rückwärts durch und berechnen somit das Ergebnis.
While *writeindex>*index
*writeindex-SizeOf(index)
If *writeindex\count\count=#r
*th\result+1
EndIf
Wend
EndProcedure
PrintN("n:"+#n)
PrintN("r:"+#r)
PrintN("maxA:"+maxa)
PrintN("Threads:"+#pro)
*limits=AllocateMemory(SizeOf(limits)*(maxa+1))
If *limits=0
PrintN("Speicherfehler limits")
Input()
End
EndIf
timer=ElapsedMilliseconds()
oldtimer=ElapsedMilliseconds()
result=0
;wir setzen einige Grenzwerte
*lim=*limits
For i=0 To 33
*lim\currentValue=i*i+3*i*1+1*1
*lim\b=1
*lim\a3=i*3
*lim+SizeOf(limits)
Next
;kleinst mögliches a und der limiteintrag für dieses a
amin=2
*aminlimit=*limits+SizeOf(limits)*amin
;aktuelle obere grenze - an Anfang ist ja alles noch klein, deshalb starten wir mit willkürlich 33
aup=33-1
;der passende limits-eintrag dafür
*alimit=*limits+SizeOf(limits)*aup
;der Grenzwert
alimit=aup*aup+3*aup-1
;das +1 ist wichtig!
While aup<maxa+1
;alle 5 sekunden eine Anzeige, wie lange schon berechnet wurde, wieviel % abgeschlossen wurde (stimmt nicht, weil je größer das a es mehr b gibt, aber egal) und die geschätze Gesamtdauer.
If ElapsedMilliseconds()>oldtimer+5000
If oldaup
speed.d=(aup-oldaup)/(ElapsedMilliseconds()-oldtimer)
eta.d=(maxa+1.0-aup)/speed+((ElapsedMilliseconds()-timer))
EndIf
oldtimer=ElapsedMilliseconds()
oldaup=aup
PrintN( ""+aup+" ("+StrF(aup/maxa*100,2)+"%) "+StrF((ElapsedMilliseconds()-timer)/60000,2) +" Min res:"+result+" spd:"+StrF(speed*1000,2)+" a/sek eta:"+StrF(eta/60000,2)+" Min = "+StrF(eta/60000/60,2)+" Std")
EndIf
;wir erstellen die threads
For i=1 To #pro
th(i)\result=0;wichtig :)
If aup<maxa+1; wir können theoretisch über das maximum kommen!
aoldlimit=alimit;altes Limit, um alte Einträge zu identifizieren
aup+1
alimit=aup*aup+3*aup*1+1*1 ;unter diesen wert müssen wir bleiben
*alimit+SizeOf(limits)
*alimit\currentValue=alimit
*alimit\b=1
*alimit\a3=aup*3
th(i)\aoldlimit=aoldlimit
th(i)\alimit=alimit
th(i)\aup=aup
th(i)\amin=amin
th(i)\aminlimit=*aminlimit
th(i)\a=0
th(i)\threadid=CreateThread(@mythread(),@th(i))
Else
th(i)\threadid=0
EndIf
Next
;wir warten bis alle Threads durch sind
For i=1 To #pro
If th(i)\threadid
WaitThread(th(i)\threadid)
EndIf
;und zählen die ergebnisse durch
result+th(i)\result
Next
;es ist möglich, das sich das minimum verschoben hat
While *aminlimit\currentValue=#setdone
amin+1
*aminlimit+SizeOf(limits)
Wend
Wend
;PrintN("maxcount:"+maxcount+" "+StrF(maxcount/maxa,2))
PrintN( "Ergebnis:"+result+" "+StrF((ElapsedMilliseconds()-timer)/60000)+"m")
Input()
End