none
Parallel.ForEach Problem

    Frage

  • Ich hab ein paar richtig große Bilder (400MB JPegs - 1,5GB Tifs) in viele Teile zu zulegen.

    Jetzt hatte ich das erst in normalen foreach-Schleifen. Allerdings dauert es für eine eher kleine Bilddatei mit 35000x7000 Pixeln schon seh lange. OK ich hatte mal was von Parallelisierung gelesen. Also alles im Parallel.Foreach gepackt. Allerding funktioniert das so nicht:

    Parallel.ForEach(row.Elements, element =>
      {
          string file = "bildpfad";
          using (image = img.Clone(
              new Rectangle(element.X, element.Y,
                            element.Width, element.Height),
              img.PixelFormat);
              try { image.Save(file, jgpEncoder, myEncoderParameters); }
              catch { }
          }
      }
    );

    Jetzt klapp das allerdings nicht da img (ist eine Bitmap-Instanz) irgendwann bereits in Verwendung ist.

    Hatte ich gedacht, das ich einfach mit look(img) { … } das auschließnen könnte. Allerdings ohne Erfolg. Es läuft damit zwar einige Minuten, aber irgendwann ist da auch sense.

    Jetzt stellt sich mir die Frage wo ich ansetzen muss um das Problem zu beheben.

    Hat jemand nen Tipp für mich?

    Grüße

    Sonntag, 10. November 2013 19:49

Antworten

  • Es kann manchmal so einfach sein, wenn man nur auf die Problemlösung kommt.

    Falls mal jemand in eine solche Situation kommt.

    Die Parallelisierung habe ich jetzt ganz weggelassen und bin trotzdem um einiges schneller fertig wie mit Parallelisierung bisher.

    Der Trick lag hier darin, einfach vor foreach(var element in row.Elements)
    den Bereich der jeweiligen Zeile aus dem großen Bild auszuschneiden und nun alle Bildelemente dieser Zeile nur aus diesem Ausschnitt zu extrahieren. Bisher hab ich ja immer den jeweiligen Bildteil aus dem Großen Bild extrahiert, was eindeutig der Flaschenhals hier war.

    Das Speichern der Bildteile mit Image.Save(…) spielt hier zeitlich keine entscheidente Rolle da die resultierenden Bildteile eine maximale Seitenlänge von 512 Pixeln haben und damit recht fix weggeschrieben sind.

    Danke an alle für evtl. Hilfestellungen. Aber manchmal findet man den besten Weg wohl selbst. ;-)

    Grüße, André

    • Als Antwort markiert André Reiner Montag, 11. November 2013 13:49
    Montag, 11. November 2013 13:49

Alle Antworten

  • Hi

    du solltest jedes Bild immer neu Laden und danach auch den Speicher wieder freigenen, ehe du das nächste Bild lädst.

    Das kannst du dann natürlich Asynchron machen, einen BackgroundWorker oder einen eigenen Thread verwenden.

    MFG

    Björn

    Sonntag, 10. November 2013 20:21
  • Hallo,
    ich habe das mal getestet. Ich habe versucht mehrere Milionen mal Teile eines Bildes in ForEach auszuschneiden. Bei mir funktioniert alles, solang ich lock(img) aufrufe.

    Eventuell liegt es auch daran, das du immer die selbe Encoder-Instanz (bzw. die Parameter) benutzt. In welcher Zeile tritt der Fehler denn genau auf?

    Da du jedes Teil des Bildes abspeicherst (ich denke mal, das du den Pfad auch irgendwie aus dem Element ausließt und nicht jedesmal den selben Pfad verwendest), glaube ich nicht, das es sehr viel schneller wird. Festplatten sind meistens nicht die schnellsten und selbst SSD's können kaum so schnell speichern wie eine CPU/GPU die Daten erzeugt.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Sonntag, 10. November 2013 20:24
  • du solltest jedes Bild immer neu Laden und danach auch den Speicher wieder freigenen, ehe du das nächste Bild lädst.

    Servus,

    Selbst bei kleineren Bildern wie 35000x7000 Pixel müste ich dann das Bild 897 mal neuladen. weil soviele Teile (69 Bilder je Zeile und das mal 13) ergibt diese Größe. Größer dürfen die Ausschnitte nicht sein!

    Aber meist sind die Bilder im Gigapixelbereich und da kommen durchaus mal 2500 - 4000 Einzelteile zusammen.

    Wenn man bedenkt das das laden eines solchen Bildes durchaus mal 15-30 Sekunden dauert, wär das sicher eine schlechte Möglichkeit jedesmal das Bild neu zu laden.

    Nur zum Verständnis. Es handelt sich bei den Bildern um Flache Panoramafotos die zur Webanzeige in kleine Schnipsel zerlegt werden, die dann in einem HTML5 Canvas-Element angezeigt werden. Dazu muss eine sogenannte Bild-Pyramide erstellt werden, die verschiedene Zoombereiche abdeckt und dynamisch immer nur die Bildteile in der Webanwendung per JS lädt die auch im Canvas sichtbar sind.

    Sonntag, 10. November 2013 21:18
  • Hi

    da hab ich mich woh missverständlich ausgedrückt. Jedes Bild sollte natürlich nur einmal geladen werden. Und dann verarbeit werden bevor du ein neues Bild bearbeitest. An diesen Punkt solltest du auch den Speicher freigeben.

    MFG

    Björn

    Sonntag, 10. November 2013 21:40
  • da hab ich mich woh missverständlich ausgedrückt. Jedes Bild sollte natürlich nur einmal geladen werden. Und dann verarbeit werden bevor du ein neues Bild bearbeitest. An diesen Punkt solltest du auch den Speicher freigeben.

    Hi,

    logisch, die verschiedenen Bildebenen die erzeugt werden, werden immer nach ihrer Nutzung freigegeben.

    Es wird auch alles im eigenen Thread ausgeführt und nur Benachrichtigungen ans GUI geschickt (Progressbar + RichTextbox für textuelle Infos)

    Gruß André

    Montag, 11. November 2013 06:55
  • Hallo,
    ich habe das mal getestet. Ich habe versucht mehrere Milionen mal Teile eines Bildes in ForEach auszuschneiden. Bei mir funktioniert alles, solang ich lock(img) aufrufe.

    Eventuell liegt es auch daran, das du immer die selbe Encoder-Instanz (bzw. die Parameter) benutzt. In welcher Zeile tritt der Fehler denn genau auf?

    Hi,

    Der Fehler tritt mit look(img) seltsamer weise im "Loader" der Anwendung auf (Programm.cs) und das nur sporadisch. Das letzte mal ist das passiert als ich während der Ausführung Strg+Alt+Entf gedrückt habe um mir über den Taskmanager die CPU Auslastung anzusehen. :-) Ohne lock tritt es in der Zeile using(var image = …)

    Allerdings kann ich es ohnehin nicht so lassen, da hiermit die Fortschrittsanzeige außerhalb des Threads in dem alles ausgeführt wird total durcheinander kommt. Da muss ich wohl damit leben, das alles ohne Parallelisierung zu nutzen. :-(

    Gruß, André

    Montag, 11. November 2013 07:04
  • Hallo André,

    Allgemein gilt, das man beim Threading möglichst lokale Variable verwenden sollte, jeder Zugriff auf gemeinsame Variable muss synchronisiert werden. GDI+ ist für sich nicht durchgängig thread safe, siehe GDI+ Thread Synchronization und auch Bitmap.Clone() thread safety.

    Anzeigen auf Steuerelementen müssen wiederum auf dem GUI Thread erfolgen, was man über  separate Task via TaskScheduler.FromCurrentSynchronizationContext erreicht. In How to: Schedule Work on the User Interface (UI) Thread gibt es ein WPF Beispiel (gilt aber in Windows Forms entsprechend).

    Da die Methode relativ I/O lastig ist - sprich das Image.Save dürfte einen guten Teil ausmachen - und Du von mehreren Bildern schreibst, wäre eine alternative Strategie, mehrere Bilder parallel zu zerlegen. Vorausgesetzt der Speicher reicht, würde das weniger / keine Überschneidungen verursachen.

    Gruß Elmar

    Montag, 11. November 2013 08:19
  • Es kann manchmal so einfach sein, wenn man nur auf die Problemlösung kommt.

    Falls mal jemand in eine solche Situation kommt.

    Die Parallelisierung habe ich jetzt ganz weggelassen und bin trotzdem um einiges schneller fertig wie mit Parallelisierung bisher.

    Der Trick lag hier darin, einfach vor foreach(var element in row.Elements)
    den Bereich der jeweiligen Zeile aus dem großen Bild auszuschneiden und nun alle Bildelemente dieser Zeile nur aus diesem Ausschnitt zu extrahieren. Bisher hab ich ja immer den jeweiligen Bildteil aus dem Großen Bild extrahiert, was eindeutig der Flaschenhals hier war.

    Das Speichern der Bildteile mit Image.Save(…) spielt hier zeitlich keine entscheidente Rolle da die resultierenden Bildteile eine maximale Seitenlänge von 512 Pixeln haben und damit recht fix weggeschrieben sind.

    Danke an alle für evtl. Hilfestellungen. Aber manchmal findet man den besten Weg wohl selbst. ;-)

    Grüße, André

    • Als Antwort markiert André Reiner Montag, 11. November 2013 13:49
    Montag, 11. November 2013 13:49