gibt es NSOperationQueue Limitierungen in der Anzahl der operationen?

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • gibt es NSOperationQueue Limitierungen in der Anzahl der operationen?

    Guten Morgen

    Ich hab da mal ne Frage... :)

    Ich überhole grad ein älteres Tool von mir, welches unter anderem Bilder skaliert. Das arbeitete bisher vollständig NSThread basiert.

    Nun hab ich angefangen es auf NSOperationsQueue umzubauen und würde da wohl mal ein bisschen Tips zum besseren Verständnis benötigen…

    Momentan erzeuge ich mein eigenes Queue und lade es mit Blockoperations via addOperationWithBlock: Das können je nach Benutzung schnell mehrere 10'000 Blöcke werden. Die ich alle per Enumeration hintereinanderweg in das Queue schiebe.

    Apple empfiehlt ja das man NSOperationQueueDefaultMaxConcurrentOperationCount für den Max Concurrent Operation Count verwenden soll. Wenn ich das verwende, wird das System leicht "unresponsiv" bis dahin, daß selbst die ganzen Analysetools "stehen bleiben".
    Begrenze ich es auf "7" concurrent operations (core i7 mit 4/8 Kernen) scheint alles in Butter – allerdings bleibt die Systemlast deutlich unter den Möglichkeiten zurück (i.A. laufen nur 2 Kerne unter Last).

    Aufgefallen ist mir, daß das Queue sehr viele Threads erzeugt...

    Kann es sein das ich mit derartig vielen Operationen den Threadpool "exhauste" und NSOperationQueue das nicht steuert? Sprich ich selber darauf achten muß wieviel Operationen ich gleichzeitig ins Queue werfe?
    An sich würde das ja irgendwie der Idee zu wieder laufen wenn ich doch wieder ein eigenes Queue bauen muß?

    Zumal Apple in der Doku meint, das man bei " create 10,000 operation objects and submit them to an operation queue" auf sein Memory footprint achten muß - und der bleibt bei mir ziemlich klein: bei 15'000 Nodes die ich skaliere liegt der RAMverbrauch noch deutlich unter 1Gb... Ich hab nun auch nichts gefunden ob es anderweitig Limitierungen gibt?
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!
  • Also Max Concurrent Operation Count sagt ja nur aus, dass es die maximale Anzahl gleichzeitig abzuarbeitender Operationen darstellt.
    Klartext: Kann bis zu 8 Operationen gleichzeitig durchführen, kann aber auch nur 1 Operation zur Zeit ausführen.

    Du kannst also zunächst einmal austesten, wie groß NSOperationQueueDefaultMaxConcurrentOperationCount für Dein jeweiliges System eigentlich ist.

    Im Prinzip sollte '7' der korrekte Wert bei 8 Kernen sein, da ja ein Kern sicherlich das UI verwalten muss. Wenn der also auch noch was ausführt, kann das Ganze schon etwas 'unresponsiv' werden.

    Da Du bei 7 gleichzeitigen Prozessen 'unter den Möglichkeiten zurück' bleibst, scheinen die einzelnen Operations wohl eher nicht so aufwendig.

    Erfahrungsgemäß startet und beendet NSOperationQueue pro Operation einen Thread. Läuft dieser Thread dennoch ewig weiter, wurde er vermutlich nicht korrekt beendet. Sorgst Du auch für eine vernünftige Beendigung Deiner Operation im Fehlerfall?

    Meiner (geringen) Erfahrung nach zickt NSOperationQueue nur dann rum, wenn die Operations nicht vernünftig implementiert sind.
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • hm…

    Ich erzeuge die Operations ja nicht selbst sondern lasse diese von dem System generieren in dem ich ja nur Blocks dem Queue hinzufüge.

    Apple schreibt dazu auch Explizit das man an solcherart per Block automatisch generierten Operations nicht mehr rumspielen soll.
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!
  • Marco Feltmann schrieb:

    Also Max Concurrent Operation Count sagt ja nur aus, dass es die maximale Anzahl gleichzeitig abzuarbeitender Operationen darstellt.
    Klartext: Kann bis zu 8 Operationen gleichzeitig durchführen, kann aber auch nur 1 Operation zur Zeit ausführen.


    also davon hab ich nirgends was gelesen...
    ich würde eher sagen dass die anzahl sehr dynamisch ist und vom aktuellen systemzustand und den operations abhängt.
    wenn zb die operations auf netzwerk-traffik warten, kann er gerne mehr abarbeiten als nur 8 (weil ich 8 kerne zur verfügung habe).
    es kann auch von den energiespar-einstellungen (im batterie-modus ja/nein) abhängen aber das ist eben alles in apples hand und sie sagen nichts genaues drüber.
  • die frage ist doch: warum willst du das machen?
    du hast doch bereits eine implementation die läuft oder nicht?
    ist diese zu langsam oder willst du es einfach ändern nur halt um was geändert zu haben?

    ich gehe nämlich davon aus dass es schneller läuft wenn du X threads startest und dann jeder dieser threads seriell operationen abarbeitet bis alle operationen durch sind und dann der thread beendet wird. ansonsten werden ja zehntausende male threads erstellt und beendet.
  • Hallo,

    ich habe selbst ein Projekt, das grundsätzlich nur auf NSOperationQueue aufbaut.
    Der Anwender kann im Extremfall tausende Operationen starten. Das funktioniert dennoch tadellos.
    Allerdings verwende ich nicht die Block-Sache, sondern den "traditionellen" Weg.

    Dort ist es wichtig, dass die Erzeugung des jeweiligen Autorelasepools korrekt ist und auch der Aufruf auf den Main-Thread passt…

    Ohne Code/Beispiel ist meiner Meinung nach Deine Sache gar nicht zu bewerten.
    Text ist bei solchen komplexen Themen eher schwierig.

    Viele Grüße
  • Ich mach das um das Design etwas zu optimieren.

    Die skalierung mit meinen Workerthreads war recht gut nach dem die initiale Abarbeitung erfolgt ist, allerdings eben grade der initiale Schritt wurde auf Grund des etwas suboptimalen Designs nur von einem Single Worker ausgeführt was eben bei ein paar tausend zu skalierenden Nodes, die problemlos parallel gerechnet werden können, ziemlich doof war.

    Und ich dachte mir wenn ich's schon anfasse dann kann ich das gleich "richtig" machen :)

    Das parallelisieren mittels NSOperationQueue dieses 'initialen skalierens meiner Nodes' funktioniert jetzt auch recht gut und vor allem deutlich fixer als das serielle vorher. Nur verwirrt mich, daß ich wenn ich nach Apple Vorgabe arbeite und den Defaultwert nutze, dass es dann eben nicht wie angegeben die Last dynamisch anpasst sondern das System fast zum Stillstand bringt - vor allem ist es etwas nervig das grade die ganzen Analysetools davon betroffen sind.

    Darum meine frage ob es vllt irgendwelche Limitierungen gibt die ich nicht kenne – zum Beispiel um zu vermeiden den Threadpool zu 'exhausten' falls das überhaupt das problem ist?
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von chartus ()

  • der Code ist recht simpel

    Quellcode

    1. NSOperationQueue * scaleingQueue = [[NSOperationQueue alloc] init];
    2. [scaleingQueue setMaxConcurrentOperationCount: 7]; //NSOperationQueueDefaultMaxConcurrentOperationCount];
    3. [scaleingQueue setName:[NSString stringWithFormat:@"scalingQueue:%p",scaleingQueue]];
    4. while ( (node = [en nextObject])) {
    5. [scaleingQueue addOperationWithBlock:^{
    6. [node createScaledBitmapWithSize:newSize];
    7. [node setIterationStep:newScale];
    8. }];
    9. }
    Alles anzeigen


    bzw in der Node selbst:

    Quellcode

    1. - (void)createScaledBitmapWithSize:(NSSize)size
    2. {
    3. int x,y = 0;
    4. NSAutoreleasePool *memPool = [[NSAutoreleasePool alloc] init];
    5. NSImage *img = [[NSImage alloc] initWithContentsOfFile:picturePath];
    6. NSImage *scaleImg = [[NSImage alloc] initWithSize:size];
    7. // is not usefull anymore -> Deprecated: [scaleImg setCachedSeparately:YES];
    8. [scaleImg setCacheMode:NSImageCacheNever];
    9. [scaleImg lockFocus];
    10. [img drawInRect:NSMakeRect(0.0,0.0,size.width,size.height)
    11. fromRect:NSMakeRect(0.0,0.0,[img size].width,[img size].height)
    12. operation:NSCompositeCopy fraction:1.0];
    13. [scaleImg unlockFocus];
    14. NSBitmapImageRep *tempRep = [NSBitmapImageRep imageRepWithData:[scaleImg TIFFRepresentation]];
    15. if ( tempRep ) {
    16. [scaledBitmap release];
    17. scaledBitmap = [tempRep retain];
    18. for (x = 0 ; x < [tempRep pixelsWide] && !killallthespawn ; x++) {
    19. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    20. for (y = 0 ; y < [tempRep pixelsHigh] && !killallthespawn ; y++) {
    21. NSColor *tmpColor = [tempRep colorAtX:x y:y];
    22. ambientRed += [tmpColor redComponent];
    23. ambientGreen += [tmpColor greenComponent];
    24. ambientBlue += [tmpColor blueComponent];
    25. ambientBrg += [tmpColor brightnessComponent];
    26. }
    27. [pool release];
    28. }
    29. int pixelCount = [tempRep pixelsWide] * [tempRep pixelsHigh];
    30. ambientRed /= (double)(pixelCount);
    31. ambientGreen /= (double)(pixelCount);
    32. ambientBlue /= (double)(pixelCount);
    33. ambientBrg /= (double)(pixelCount);
    34. }
    35. [img release];
    36. [scaleImg release];
    37. [memPool release];
    38. }
    Alles anzeigen
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von chartus ()

  • also ich würd mal anfangen den code selbst zu optimieren.
    da ist ja noch einiges drin (die massen von neuen ARPs kannst du dir sparen (@autorelease verwenden oder die pools nur leeren, nicht neu erstellen), du kannst colorAtX:Y: (und die schleifen drum rum mit den überflüssigen pixelsWide und pixelsHeight) auch weglassen und direkt auf die bitmap-daten zugreifen.

    wozu der zweite teil jedoch immer gemacht wird ist fraglich (muss man ja nur einmal machen weil unabhängig von der bildgröße).

    und was machst du mit den ganzen scaledBitmap im speicher?
  • und was machst du mit den ganzen scaledBitmap im speicher?

    Genau das ist die Kernfrage!

    Entweder ist ein Konverter, oder die Bilder werden für irgend etwas vorbereitet…

    Ist es ein Konverter, dann verstehe ich den Sinn der Asynchronität nicht.
    Da könnte problemlos jedes Bild nach und nach abgearbeitet werden. Threading bringt da kein wirklichen Gewinn.

    Wenn die Daten für irgendetwas vorbereitet werden, dann ist Caching wohl klüger.
    Zumal das Scaling erst bei Bedarf durchgeführt werden sollte.

    Was ist die Aufgabe dieser Anwendung?

    Du verwendest -setIterationStep:… was macht das?
    Wird da was in der Oberfläche aktualisiert? Das sollte sicherlich auf den Main-Thread landen…

    Viele Grüße
  • Öhm ich sortiere damit Bilder

    Und ja das ist die Vorbereitung zum Vergleichen der Nodes gegeneinander.


    Und der iterationStep ist intern von der Node und muß nicht in den Mainthread. Damit merkt sich die Node nur das sie skaliert wurde bzw an welcher stelle der Iterationen zum sortieren sie sich befindet.
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!
  • trotzdem sind da noch jede menge sinnlose umwege drin.
    zb zeichnest du in ein image, holst dann ein tiff davon und machst daraus einen bitmap-context.
    das ganze sollte man doch in einem mittels CoreGraphics machen können.

    und die farbwerte müsstest du nur einmal auslesen (weil die ja unabhängig von der bildgröße sein sollten). und das am besten mit einer schnelleren methode als für jeden pixel eine methode aufzurufen die ein fettes objekt erstellt und in den ARP legt.
  • Ich geb dir ja Recht, daß sich das mittels CoreGraphics besser machen lies - das ist auch meine nächste Baustelle – damals war ich halt noch jung.

    Ändert ja nichts daran, das wenn ich zehntausende Nodes skaliere sich NSOperationQueue etwas 'strange' benimmt?
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!
  • Und der iterationStep ist intern von der Node und muß nicht in den Mainthread. Damit merkt sich die Node nur das sie skaliert wurde bzw an welcher stelle der Iterationen zum sortieren sie sich befindet.

    Und das funktioniert richtig?

    Du nimmst ne Variable von ausserhalb des Blocks und verwendest die im Block bei einer Methode als Parameter.
    Warum ist die Logik dann nicht in der -scale…, wenn die Instanz das wissen muss.

    Warum sollte man die Bilder erst skalieren bevor irgendetwas verglichen wird?
    Finde ich komisch, die unnötig im Speicher zu halten.

    Naja, mir fehlt da wohl leider der Bezug hier richtig helfen zu können…

    Viele Grüße
  • little_pixel schrieb:


    Und das funktioniert richtig?


    Ja - es ist nur ein Konstante bzw ein Enum was gesetzt wird.



    Ich hab jetzt nach 'gritsch' seinem Vorschlag die Autoreleasepools aus den Schleifen mal herausgenommen. Das sorgt zwar dafür das mein Memoryfootprint etwas dicker wird ändert aber nichts an dem Verhalten von "NSOperationQueueDefaultMaxConcurrentOperationCount"

    Das ganze auf CoreGraphic umzubauen wäre dann der nächste Schritt aber irgendwie glaub ich nicht das es was an dem Problem der OperationQueue ändert.
    snafu
    :() { :|: &};:
    sometimes i dream in hex
    Obey gravity! Because its a law!