[java] Threads - Snelheid bij verschillende objecten

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Alain
  • Registratie: Oktober 2002
  • Niet online
Ik ben wat met threads in java aan het spelen. Ik heb een testcase gemaakt met:

TaskManager: Doet op dit moment niks, behalve wat tasks afschieten
Task: Basis voor een taak.
SomeTask: Een taak, subclass van task
AnotherTask: Een andere taak, subclass van task

Het is slechts een testcase zodat ik threads leer begrijpen en in de toekomst toe kan passen voor nuttige dingen.

Het valt me op dat wanneer overgeschakeld moet worden van SomeTask naar AnotherTask, dat er erg veel tijd verspilt wordt. De output staat gewoon 2 seconden stil.

Tijd voor de testcase code:

Task.java:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.Thread;
import java.util.concurrent.Semaphore;

public class Task extends Thread {

  protected int id;
  protected String TaskName;
  private static int numTasks = 0;
  private static Semaphore lockId = new Semaphore(1);

  public Task() {
    try {
      lockId.acquire();
      id = numTasks++;
      lockId.release();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    
    // Now we are alive, but not running.
    // Make your own run function
    System.out.println("Task id: " + id + " Task name: " + TaskName + " I'm alive");
  }
  
  public void printToConsole() {
    System.out.println("Task id: " + id + " Task name: " + TaskName);
  }
  
};


De code van SomeTast en AnotherTask (de code is gelijk, op de naam na. Ik plaats het dus 1 keer):

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.Semaphore;

public class SomeTask extends Task {

  public SomeTask() {
    TaskName = "SomeTask";
  }

  public void run() {
    doNothing();
    doNothing();
    doNothing();
    doNothing();
    doNothing();
  }
  
  public void doNothing() {
    int i;
    for (i = 0; i < 100000000; i++) {
    
    }
    printToConsole();
    
  }
}


De taskmanager die nog niets doet:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.Vector;

public class TaskManager {

  private Vector<Task> tasks;
  private int maxTasks;

  public TaskManager(int maxTasks) {
    tasks = new Vector<Task>();
    this.maxTasks = maxTasks;
  }
  
  public void addTasks() {
    int i;
    for (i = 0; i < 10; i++) {
      tasks.add(new SomeTask());
      tasks.lastElement().start();
    }
    for (i = 0; i < 10; i++) {
      tasks.add(new AnotherTask());
      tasks.lastElement().start();
    }

  }
 }


Is er iemand die me uit kan leggen wat er hier gebeurd waardoor het overschakelen van SomeTask naar AnotherTask zo lang duurt? Als ik meer SomeTasks maak, wordt het overschakelen naar SomeTasks langer. Hetzelfde voor AnotherTasks. Wat gaat hier mis?

You don't have to be crazy to do this job, but it helps ....


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 20:04
Je print 1e8 keer iets naar de console, per task. Daar zou ik ook langzaam van worden.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nou dat niet, printToConsole() staat buiten de loop. Maar waar dient die loop überhaupt voor? Bedenk dat meerdere threads alleen tegelijk kunnen lopen als je meerdere cores hebt. Als je gewoon wilt moet je Thread.sleep() gebruiken, dan kunnen andere threads in tussentijd gescheduled worden.

[ Voor 4% gewijzigd door .oisyn op 01-02-2009 03:37 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Gorion3
  • Registratie: Februari 2005
  • Laatst online: 24-08 08:28

Gorion3

ABC++

Jep, hier heeft oisyn helemaal gelijk :), De thread is namelijk druk bezig, je code werkt dan ook perfect, want 1e8 loopen duurt een enkele secondes (in jouw geval dus ongeveer 2). Als je een dualcore hebt zouden beide threads een aparte core toegewezen kunnen krijgen, aangezien dit door het OS word beheerd. Maar gelukkig zal dit bijna altijd het geval zijn als 1 core op 100% werkt en de 2de core niks aan het doen is.

Awesomenauts! Swords & Soldiers


Acties:
  • 0 Henk 'm!

  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 01:28

Gerco

Professional Newbie

Let wel op dat je de JVM in de juiste mode moet draaien. Op de sun JVM bijvoorbeeld worden alle Java threads op 1 enkele native thread gemapt in de client VM (de default). Wanneer je de VM met -server draait krijg je wel echte native threads en kun je pas gebruik maken van alle CPUs. Zie ook Using all the cores (en mijn persoonlijke ervaring, maar die kun je nergens nalezen :) ).

Daarnaast zie ik in je code dat je thread class extend van Thread, dat is iets wat je beter niet meer kunt doen sinds JDK-langgeleden. Tegenwoordig hebben we Runnable en Callable interfaces voor dat doel. Je kunt met deze interfaces concurrency veel makkelijker maken door de classes in java.util.concurrent toe te passen, vooral de Executors maken het een stuk makkelijker.

[ Voor 4% gewijzigd door Gerco op 01-02-2009 11:11 ]

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


Acties:
  • 0 Henk 'm!

  • 2playgames
  • Registratie: Februari 2005
  • Laatst online: 01-06 15:19
Op de sun JVM bijvoorbeeld worden alle Java threads op 1 enkele native thread gemapt in de client VM (de default).
Niet dat ik je niet geloof, maar als ik een Java-programma (zonder -server) in taakbeheer bekijk zie ik daar vaak een thread of 16. Waar komen die dan vandaan?

Acties:
  • 0 Henk 'm!

  • Alain
  • Registratie: Oktober 2002
  • Niet online
.oisyn schreef op zondag 01 februari 2009 @ 03:38:
Maar waar dient die loop überhaupt voor?
Die loop simuleert dat de taak erg druk bezig is.
Gerco schreef op zondag 01 februari 2009 @ 11:10:
Let wel op dat je de JVM in de juiste mode moet draaien. Op de sun JVM bijvoorbeeld worden alle Java threads op 1 enkele native thread gemapt in de client VM (de default). Wanneer je de VM met -server draait krijg je wel echte native threads en kun je pas gebruik maken van alle CPUs. Zie ook Using all the cores (en mijn persoonlijke ervaring, maar die kun je nergens nalezen :) ).
Ik heb nu even geen dual core, maar op mijn eigen dual core draaide de taken wel op beide cores. Wanneer ik maar 1 thread aanmaak, wordt ook maar 1 core gebruikt, dus dat leek mij wel goed te gaan. Ik zal me eens wat verdiepen in de verschillende modes.
Daarnaast zie ik in je code dat je thread class extend van Thread, dat is iets wat je beter niet meer kunt doen sinds JDK-langgeleden. Tegenwoordig hebben we Runnable en Callable interfaces voor dat doel. Je kunt met deze interfaces concurrency veel makkelijker maken door de classes in java.util.concurrent toe te passen, vooral de Executors maken het een stuk makkelijker.
Hier ga ik me verder in verdiepen. Bedankt. :)




Waar het mij om gaat is de tijd die het duurt tussen het omschakelen van een SomeTask thread naar een AnotherTask en andersom. Voorbeeld uit de output:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Task id: 7 Task name: SomeTask
Task id: 8 Task name: SomeTask
Task id: 9 Task name: SomeTask <-- Na deze regel staat de output even stil
Task id: 10 Task name: AnotherTask
Task id: 11 Task name: AnotherTask
Task id: 12 Task name: AnotherTask
Task id: 13 Task name: AnotherTask
Task id: 14 Task name: AnotherTask
Task id: 15 Task name: AnotherTask
Task id: 16 Task name: AnotherTask
Task id: 17 Task name: AnotherTask
Task id: 18 Task name: AnotherTask
Task id: 19 Task name: AnotherTask <-- Na deze regel staat de output even stil
Task id: 0 Task name: SomeTask <-- Na deze regel staat de output even stil
Task id: 12 Task name: AnotherTask
Task id: 11 Task name: AnotherTask
Task id: 10 Task name: AnotherTask <-- Na deze regel staat de output even stil
Task id: 9 Task name: SomeTask
Task id: 8 Task name: SomeTask


Iemand een idee waar die tijd aan verspilt wordt?

[ Voor 23% gewijzigd door Alain op 01-02-2009 12:58 ]

You don't have to be crazy to do this job, but it helps ....


Acties:
  • 0 Henk 'm!

  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 01:28

Gerco

Professional Newbie

AlainS schreef op zondag 01 februari 2009 @ 12:52:
Ik heb nu even geen dual core, maar op mijn eigen dual core draaide de taken wel op beide cores. Wanneer ik maar 1 thread aanmaak, wordt ook maar 1 core gebruikt, dus dat leek mij wel goed te gaan. Ik zal me eens wat verdiepen in de verschillende modes.
Op een dual-core is het mij ook niet gelukt om verschil te merken tussen beide modes. Op een 16-core machine was er een zeer groot verschil te merken tussen server en client VM.

Met exact dezelfde .class files: Zonder -server leek het alsof geen enkele core erg druk was, met -server waren alle 16 cores druk bezig en ging de test tussen de 10 en 12 keer sneller. De test was een hele simpele, iets van deze strekking (ik heb de exacte code niet meer bij de hand):
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService ex = Executors.newFixedThreadPool(Integer.parseInt(args[0]));
        
        System.out.println("Running test " + args[1] + " times on " + args[0] + " threads");
        
        long startTime = System.currentTimeMillis();
        for(int i=0; i<Integer.parseInt(args[1]); i++) {
            ex.execute(new Test());
        }
        ex.shutdown();
        ex.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
        
        System.out.println("Test took " + (System.currentTimeMillis() - startTime) + "ms");
    }

    public void run() {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("key", "value");
        map.put("key1", "value");
        map.put("key2", "value");
        map.put("key3", "value");

        for(int i=0; i<10000000; i++) {
            String value = map.get("key");
        }
    }
}


Ik dacht eerst dat het aan 32 bit vs 64 bit lag, maar het blijkt dat de 64 bit VM van Sun standaard in server mode draait. Met de 32 bit VM en -server kreeg ik dezelfde resultaten als de 64 bit VM zonder parameters.

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


Acties:
  • 0 Henk 'm!

Verwijderd

Voor dergelijk simpel gebruik kan je trouwens ook gebruikmaken van Reentrant locks, die zijn in mijn ogen wat makkelijker dan Semaforen.

En komen in deze gebruikte context (je eerste code voorbeeld) op precies hetzelfde neer :).

Acties:
  • 0 Henk 'm!

  • bomberboy
  • Registratie: Mei 2007
  • Laatst online: 01:33

bomberboy

BOEM!

AlainS schreef op zaterdag 31 januari 2009 @ 23:58:
Ik ben wat met threads in java aan het spelen. Ik heb een testcase gemaakt met:

TaskManager: Doet op dit moment niks, behalve wat tasks afschieten
Task: Basis voor een taak.
SomeTask: Een taak, subclass van task
AnotherTask: Een andere taak, subclass van task
Er zijn een aantal (mogelijke) probleempjes met de code die je hier presenteert. Als eerste tip, vaak is het handiger om je Thread als Runnable te implementeren en bij het eigenlijk afvuren ervan het Thread object aan te maken met die runable als argument. Dat is niet altijd "beter" maar vaker wel dan niet. Dat maakt het later bijvoorbeeld ook veel makkelijker om met Threadpools en dergelijke te werken.

maxtasks wordt nergens gebruikt in je TaskManager

Het is netter om in Je SomeTask en AnotherTask de super-constructor aan te roepen, dat gebeurt nu (als speciaal geval) wel impliciet, maar persoonlijk ben ik niet zo voor die stijl. Bovendien krijg je waarschijnlijk telkens een "null" te zien in de Alive-messages. Een constructor met naam als argument lost dit wat netter op.

De for-loop die je gebruikt om load te genereren is leeg. De JVM is mogelijks slim genoeg om die eigenlijk gewoon over te slaan en helemaal niet uit te voeren.
Het valt me op dat wanneer overgeschakeld moet worden van SomeTask naar AnotherTask, dat er erg veel tijd verspilt wordt. De output staat gewoon 2 seconden stil.
Dat zie ik hier niet als ik het even snel test. Zeker dat het niet toevallig is door het bufferen van de output? Zeker indien je je code in een IDE uitvoert durft dit wel eens te gebeuren. (System.out.println() impliceert wel een .flush(), maar een IDE buffert dit soms nog wel eens apart).

Tot slot wordt een semafoor niet zo vaak voor iets als dit gebruikt. Sinds Java 1.5 heb je daar een AtomicInteger voor. En voordien werd dit meestal opgevangen door een synchronized block te gebruiken. Semaforen worden typisch gebruikt bij het managen van object/thread pools of externe resources. (Uiteraard, om er iets van te leren is een use case als deze zeker geschikt)

Die static semaphore zou ik eigenlijk ook afraden. Het gene je hier wil bereiken zou ik zelf eerder oplossen door synchronized factory method te gebruiken.

[ Voor 7% gewijzigd door bomberboy op 01-02-2009 17:22 . Reden: verwijderen verkeerde info ]


Acties:
  • 0 Henk 'm!

  • Neverwinterx
  • Registratie: December 2005
  • Laatst online: 22-09 11:32
Gerco schreef op zondag 01 februari 2009 @ 11:10:
Let wel op dat je de JVM in de juiste mode moet draaien. Op de sun JVM bijvoorbeeld worden alle Java threads op 1 enkele native thread gemapt in de client VM (de default). Wanneer je de VM met -server draait krijg je wel echte native threads en kun je pas gebruik maken van alle CPUs. Zie ook Using all the cores (en mijn persoonlijke ervaring, maar die kun je nergens nalezen :) ).
In mijn ervaring klopt dat toch niet wat je zegt. Ik heb een dual-core en als ik in java 2 threads start en wat werk laat verrichten zie ik duidelijk dat de 2 cores aan het werk zijn. Misschien dat het technisch wat verschilt hoe server en client jvm native threads gebruiken, maar ik zie hier toch duidelijk dat de client jvm wel twee dingen tegelijk kan laten werken. Wat wel klopt is dat de server jvm ontwikkeld is om zo snel mogelijk te werken (ten na dele van memory gebruik en opstart tijd) en dat je vaak (niet altijd, hangt af van wat je precies doet) zal zien dat een programma sneller uitvoert op de server jvm dan de client jvm.

Ik heb de code even getest en ik zie ook een kleine vertraging tussen een reeks SomeTask's en AnotherTask's. Dit is mogelijk te verklaren door een soort van context switch. Merk trouwens wel op dat je 20 threads tegelijk opstart, wat een vrij zware last is; als je maar 2 cores hebt, gaat het nauwelijks sneller vooruitgaan als je 20 threads of 2 threads opstart (integendeel mogelijk trager zelfs met 20 threads).

edit: Getest op java 6 update 11. En ik heb in die for-loop wat dingen opgeteld om te vermijden dat de jvm slim genoeg is om dat over te slaan (zoals de post hierboven suggereert).

[ Voor 5% gewijzigd door Neverwinterx op 01-02-2009 18:19 . Reden: toevoeging ]


Acties:
  • 0 Henk 'm!

  • Alain
  • Registratie: Oktober 2002
  • Niet online
Verwijderd schreef op zondag 01 februari 2009 @ 13:25:
Voor dergelijk simpel gebruik kan je trouwens ook gebruikmaken van Reentrant locks, die zijn in mijn ogen wat makkelijker dan Semaforen.

En komen in deze gebruikte context (je eerste code voorbeeld) op precies hetzelfde neer :).
Ik wordt een beetje gek van java omdat je 1000 classes hebt om tot exact dezelfde oplossing te komen. Dit is niet de laatste versie van mijn testcase en ik wil problemen als het producer/consumer probleem e.d. helder te hebben voor mezelf. Ik kan het niet gebruiken om dan voor elke lock een bijpassende class te zoeken.

Je informatie is zeker nuttig en ga ik ook zeker toepassen, maar niet in dit stadium.
bomberboy schreef op zondag 01 februari 2009 @ 17:21:
[...]


Er zijn een aantal (mogelijke) probleempjes met de code die je hier presenteert. Als eerste tip, vaak is het handiger om je Thread als Runnable te implementeren en bij het eigenlijk afvuren ervan het Thread object aan te maken met die runable als argument. Dat is niet altijd "beter" maar vaker wel dan niet. Dat maakt het later bijvoorbeeld ook veel makkelijker om met Threadpools en dergelijke te werken.
Het is mij duidelijk dat ik Runnable en Callable moet gebruiken. Ik ga dat aanpassen.
maxtasks wordt nergens gebruikt in je TaskManager
Klopt. Dat moet er in de toekomst voor zorgen dat er maximaal maxTasks draaien, maar daar ben ik nog niet aan toe. :)
Het is netter om in Je SomeTask en AnotherTask de super-constructor aan te roepen, dat gebeurt nu (als speciaal geval) wel impliciet, maar persoonlijk ben ik niet zo voor die stijl. Bovendien krijg je waarschijnlijk telkens een "null" te zien in de Alive-messages. Een constructor met naam als argument lost dit wat netter op.
Noted. Ik zie trouwens geen NULL in de alive messages. Nu zie ik ze wel. :P
De for-loop die je gebruikt om load te genereren is leeg. De JVM is mogelijks slim genoeg om die eigenlijk gewoon over te slaan en helemaal niet uit te voeren.
Het geeft wel vertraging op mijn systemen, dus er helemaal overheen stappen doet de JVM niet.
Dat zie ik hier niet als ik het even snel test. Zeker dat het niet toevallig is door het bufferen van de output? Zeker indien je je code in een IDE uitvoert durft dit wel eens te gebeuren. (System.out.println() impliceert wel een .flush(), maar een IDE buffert dit soms nog wel eens apart).
Ik test het niet in een IDE. Dat zou inderdaad wat uit kunnen maken, maar ik test de applicatie op CML.
Tot slot wordt een semafoor niet zo vaak voor iets als dit gebruikt. Sinds Java 1.5 heb je daar een AtomicInteger voor. En voordien werd dit meestal opgevangen door een synchronized block te gebruiken. Semaforen worden typisch gebruikt bij het managen van object/thread pools of externe resources. (Uiteraard, om er iets van te leren is een use case als deze zeker geschikt)
Zoals ik in deze post op een ander reply wil ik wel uitzoeken welk atomic type het best geschikt is in java, maar ik wordt gek van alle classes die min of meer hetzelfde kunnen. :)
Die static semaphore zou ik eigenlijk ook afraden. Het gene je hier wil bereiken zou ik zelf eerder oplossen door synchronized factory method te gebruiken.
Ik zal me er eens in verdiepen. Ik vind de voorbeelden die ik op internet vind van synchronize vaak slecht te volgen. De semafoor is zo lekker recht toe recht aan. :)

You don't have to be crazy to do this job, but it helps ....


Acties:
  • 0 Henk 'm!

Verwijderd

Dag AlainS,

Dat Java zoveel verschillende manieren heeft om dingen te doen komt ook omdat er steeds nieuwe en betere zaken bijkomen. Voor het standaard producer/consumer probleem zou ik allang geen locks meer gebruiken, maar gewoon een blocking queue gebruiken.

Wat betreft scheduling: je observeert in een heel kunstmatige omgeving dat de output stil staat. Weet je zeker dat er geen andere zaken draaien die je processor vreten? Virus scanners? :) Het is erg lastig om dit soort dingen goed te doen. Ken je de scheduling policy die toegepast wordt? Misschien besluit Java wel een van je processen tot het eind te laten lopen en andere weer niet. Weet jij veel.

Of Java wel of geen meerdere processoren gebruikt is ontzettend afhankelijk van je configuratie. De JVM neemt een aantal tuning beslissingen bij het opstarten (welke garbage collectors, welke JIT compiler). Je hebt daar wel wat invloed op, maar je hebt het niet voor 't zeggen. Om je een idee te geven, als je -server opgeeft gaat Java een heel andere compiler gebruiken dan met -client (of niks). Effectief heb je dan gewoon een andere JVM die zich min of meer toevallig net zo gedraagt als de andere. ;-) Volgens mij is het tegenwoordig zo dat de meeste JVM's wel meerdere cpu's gebruiken, ook in client mode (Mac OS X z'n JVM doet dat bijvoorbeeld).

Dit is natuurlijk allemaal uitstekend gedocumenteerd, echter in de programmeertaal C en niet in het Engels.

Wat jouw plan om te leren met locks om te gaan betreft: uitstekend idee. Zorg dat je wait()/notify() goed kent en Dijkstra's werk over semaforen. Schakel daarna zo snel mogelijk om naar library classes die het locken voor je doen, scheelt hopen bugs en dagen zoeken. ;)

Kees Jan

PS. Vergeet niet om de locks vrij te geven in een finally{} clausule. Anders loop je snel tegen deadlocks aan.

Acties:
  • 0 Henk 'm!

  • Alain
  • Registratie: Oktober 2002
  • Niet online
Neverwinterx schreef op zondag 01 februari 2009 @ 18:01:
Ik heb de code even getest en ik zie ook een kleine vertraging tussen een reeks SomeTask's en AnotherTask's. Dit is mogelijk te verklaren door een soort van context switch.
Fijn dat ik niet de enige ben die het tegen komt. :)

De objecten zijn exact gelijk, maar ik snap dat de compiler het als verschillende dingen ziet. Ik snap alleen niet waarom dit zoveel vertraging oplevert dat het merkbaar is.
Merk trouwens wel op dat je 20 threads tegelijk opstart, wat een vrij zware last is; als je maar 2 cores hebt, gaat het nauwelijks sneller vooruitgaan als je 20 threads of 2 threads opstart (integendeel mogelijk trager zelfs met 20 threads).
De overhead van de 20 threads is niet merkbaar in mijn testcase. Het levert alleen wat verschillende output op en dat is altijd leuk. :P
Bedankt voor de input. It makes sense. :)

You don't have to be crazy to do this job, but it helps ....


Acties:
  • 0 Henk 'm!

  • bomberboy
  • Registratie: Mei 2007
  • Laatst online: 01:33

bomberboy

BOEM!

AlainS schreef op zondag 01 februari 2009 @ 19:48:
[...]
De objecten zijn exact gelijk, maar ik snap dat de compiler het als verschillende dingen ziet. Ik snap alleen niet waarom dit zoveel vertraging oplevert dat het merkbaar is.
Ik heb het hier eens op een single-core cpu getest met iets meer rekenwerk in de loop van de threads en merk nu ook meer echte vertragingen. Maar een vertraging in de output gebeurt niet noodzakelijk tussen de verschillende typen objecten en kan dus even goed tussen twee printjes van een AnotherTask zitten.

Dus ik vermoed dat deze vertragingen vooral aan de scheduler van de JVM te danken zijn en hoe deze fairness implementeert.

Acties:
  • 0 Henk 'm!

  • Alain
  • Registratie: Oktober 2002
  • Niet online
bomberboy schreef op maandag 02 februari 2009 @ 09:38:
Dus ik vermoed dat deze vertragingen vooral aan de scheduler van de JVM te danken zijn en hoe deze fairness implementeert.
Ik vermoed dat je gelijk hebt. Ik ben wat aan het spelen geweest en met meerdere threads wordt de vertraging vrij random (zoals ik verwacht had). :)

edit:

Op zich is de keuze wel te verklaren. De JVM ziet een aantal threads van type A en een aantal threads van type B. Op een single core CPU is het waarschijnlijk makkelijker om zoveel mogelijk threads van hetzelfde type uit te voeren en dan volledig overschakelen op een ander type. Met een dual core merk je dat niet. :)

[ Voor 29% gewijzigd door Alain op 02-02-2009 22:05 ]

You don't have to be crazy to do this job, but it helps ....


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Gerco schreef op zondag 01 februari 2009 @ 11:10:
Let wel op dat je de JVM in de juiste mode moet draaien. Op de sun JVM bijvoorbeeld worden alle Java threads op 1 enkele native thread gemapt in de client VM (de default). Wanneer je de VM met -server draait krijg je wel echte native threads en kun je pas gebruik maken van alle CPUs. Zie ook Using all the cores (en mijn persoonlijke ervaring, maar die kun je nergens nalezen :) ).
Dat is al jaren niet zo, sterker nog: onder Windows is dat nooit zo geweest: in alle VM modi wordt een userthread gekoppeld aan een kernel-thread.
Onder Solaris werden threads in java 1.0 en 1.1 wel gemultiplexed met zogenaamde 'green threads'. Tegenwoordig wordt in principe* voor iedere thread in Java ook een kernel-thread aangemaakt.

Voornaamste reden dat je soms observeert dat er slechts één core belast wordt, is dat als de verschillende threads veel data (gesynchroniseerd) delen, dat Java (iig in client mode, misschien wordt er in server anders geoptimaliseerd) die threads aan de dezelfde core toewijst om synchronisatie en context-switch overhead te beperken (dat is nl makkelijker op één core dan over meerdere cores).

*: Implementatie afhankelijk, sommige OSsen (waaronder Solaris) bieden faciliteiten om meerdere user-threads op een enkele kernel-thread af te beelden. De JLS laat JVM leveranciers vrij om daar gebruik van te maken.

[ Voor 15% gewijzigd door Remus op 03-02-2009 11:29 ]


Acties:
  • 0 Henk 'm!

  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 01:28

Gerco

Professional Newbie

Remus schreef op dinsdag 03 februari 2009 @ 11:21:
Voornaamste reden dat je soms observeert dat er slechts één core belast wordt, is dat als de verschillende threads veel data (gesynchroniseerd) delen, dat Java (iig in client mode, misschien wordt er in server anders geoptimaliseerd) die threads aan de dezelfde core toewijst om synchronisatie en context-switch overhead te beperken (dat is nl makkelijker op één core dan over meerdere cores).
Apart dan dat een testcase zoals ik die hierboven gepost heb (die helemaal geen data deelt tussen threads) zo anders presteert op een veel core machine in client en server mode. Het verschil zat hem niet in de 32 of 64 bit VM, maar in -server of niet.

32 bit VM client mode: weinig tot geen activiteit op de cores merkbaar
32 bit VM server mode: alle cores volop bezig
64 bit VM default mode (server): alle cores volop bezig

Ik heb het nog even gechecked en alhoewel het starten van de test iets anders geimplementeerd was is de code in de Runnable exact gelijk aan wat ik heb gepost. Zijn hier nog andere verklaringen voor dan het niet gebruiken van native threads? Het OS was overigens Windows 2003 Server 64 bit.

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Gerco schreef op woensdag 04 februari 2009 @ 16:59:
[...]

Apart dan dat een testcase zoals ik die hierboven gepost heb (die helemaal geen data deelt tussen threads) zo anders presteert op een veel core machine in client en server mode. Het verschil zat hem niet in de 32 of 64 bit VM, maar in -server of niet.

32 bit VM client mode: weinig tot geen activiteit op de cores merkbaar
32 bit VM server mode: alle cores volop bezig
64 bit VM default mode (server): alle cores volop bezig

Ik heb het nog even gechecked en alhoewel het starten van de test iets anders geimplementeerd was is de code in de Runnable exact gelijk aan wat ik heb gepost. Zijn hier nog andere verklaringen voor dan het niet gebruiken van native threads? Het OS was overigens Windows 2003 Server 64 bit.
In beide gevallen worden native threads gebruikt (kijk maar naar de kolom 'Threads' in de taskmanager). In -server worden echter behoorlijk andere optimalisaties (qua compilatie, inlining ed) en een andere garbagecollection strategie toegepast (al lijkt dat laatste in jouw testcase niet relevant), dat kan een behoorlijke impact hebben op de throughput van de applicatie en de manier waarop de native threads op één of meerdere cores gescheduled worden.

Ik zal er vanavond nog eens naar kijken (al heb ik helaas enkel een dualcore tot mijn beschikking).

Acties:
  • 0 Henk 'm!

  • Wirf
  • Registratie: April 2000
  • Laatst online: 04-09 08:21
Volgens mij is het zo dat de standaard JVM controleert of de machine waar hij op draait een "server-class" machine is, en aan de hand daarvan bepaald of de client of server jvm gebruikt moet worden.

Volgens mij zijn een aantal van de factoren in het bepalen van "server-class"-iness:
- Dual of meer cores
- 2 GB ram of meer
- Unix-y operating system (ongeveer alles behalve windows :+ )
Remus schreef op donderdag 05 februari 2009 @ 13:26:
Ik zal er vanavond nog eens naar kijken (al heb ik helaas enkel een dualcore tot mijn beschikking).
Kun je voor de test niet de processor-affiniteit zetten? (dus dat je tegen het OS zegt dat het desbetreffende process maar op 1 CPU mag draaien)

Heeft sinds kort zijn wachtwoord weer terug gevonden!


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Wirf schreef op donderdag 05 februari 2009 @ 21:50:
Volgens mij is het zo dat de standaard JVM controleert of de machine waar hij op draait een "server-class" machine is, en aan de hand daarvan bepaald of de client of server jvm gebruikt moet worden.

Volgens mij zijn een aantal van de factoren in het bepalen van "server-class"-iness:
- Dual of meer cores
- 2 GB ram of meer
- Unix-y operating system (ongeveer alles behalve windows :+ )

[...]

Kun je voor de test niet de processor-affiniteit zetten? (dus dat je tegen het OS zegt dat het desbetreffende process maar op 1 CPU mag draaien)
Ik heb gisteravond geen tijd gehad om er naar te kijken, dat wordt waarschijnlijk ergens dit weekend. Processor affiniteit zetten heeft natuurlijk niet zoveel zin als je juist de multi-threading en multi-processor gebruik wil controleren (al kan het wel nut hebben voor de vergelijking tussen single en dual core performance).

Of de server of client VM wordt gebruikt heeft geen invloed op het gebruiken van native threads voor Java threads (en idd onder Windows 32 moet je expliciet voor server VM kiezen, anders wordt client gebruikt). Waar het wel invloed op heeft is de selectie van garbage collection strategie en instellingen, compilatie-thresholds, inlining strategie en nog een aantal andere zaken. Het is goed mogelijk dat die verschillen invloed hebben op de scheduling van threads op één of meerdere processoren.

Acties:
  • 0 Henk 'm!

  • Mr_Light
  • Registratie: Maart 2006
  • Niet online

Mr_Light

Zo-i-Zo de gekste.

Wirf schreef op donderdag 05 februari 2009 @ 21:50:
Volgens mij is het zo dat de standaard JVM controleert of de machine waar hij op draait een "server-class" machine is, en aan de hand daarvan bepaald of de client of server jvm gebruikt moet worden.
...
Zie http://java.sun.com/docs/hotspot/gc5.0/ergo5.html

[quote]AlainS schreef op zondag 01 februari 2009 @ 18:23:
Ik wordt een beetje gek van java omdat je 1000 classes hebt om tot exact dezelfde oplossing te komen.
[/qoute]
exact dezelfde Soort gelijke oplossing

Meuh als je hier al kort door de bocht gaat, gaat programmeren met concurency helemaal een feest worden. Gezien daar een stuk minder bescherming is voro de programmeur tegen fouten.

Bovendien '1000' oplossingen nog niet eens genoeg, zie de uitgebreide toevoegingen van apache en google aan Collections.

Gelukkig is er hoop: meeste zijn gewoon sensible defaults voor: ArrayList, HashSet etc. Welke functies vervullen die in de literatuur uitgebreid afgebakend zijn. (Zeker in jouw geval met Dijkstra's semaphore) Alternatieven hoef je alleen aan te raken als de profiler je dat vertelt.

Verder: heb je Thread.yield() al een keer geprobeerd?

Mbt InterruptedException en het alleen printen van de stack trace werp eens een blik op het opniew setten van de interrupt status. (Welke je overigens zou moeten checken voor dat je dat loopje uitvoert(waar je de scope van i ook wel mag verkleinen), maar het is test code dus dat zien we dan maar door de vingers - alhoewel het misschien geen gek idee is om te kijken waarom je dat zou moeten doen.)

Ook lijkt me het nummeren van de task meer wat voor de TaskManager.

Als laatste twijvel ik nog over of numTasks en lockId niet ook volatile moeten zijn(of by lockId 'final') ivm visability - het probleem is echter dat ik nog nooit een static volatile nodig gehad heb. 8)7 Uberhaupt een static variable dat geen constant is gebruik is zelden tot nooit.

IceManX schreef: sowieso


Acties:
  • 0 Henk 'm!

  • Wirf
  • Registratie: April 2000
  • Laatst online: 04-09 08:21
Mr_Light schreef op vrijdag 06 februari 2009 @ 12:08:
[...]
Alternatieven hoef je alleen aan te raken als de profiler je dat vertelt.
Redelijk offtopic, maar welke profiler gebruik jij?
Ikzelf gebruik JProfiler, die is geweldig, alleen wel aan de prijzige kant, waardoor ik wel geintresseerd ben in een alternatief.

Heeft sinds kort zijn wachtwoord weer terug gevonden!


Acties:
  • 0 Henk 'm!

  • Mr_Light
  • Registratie: Maart 2006
  • Niet online

Mr_Light

Zo-i-Zo de gekste.

Als ik heel eerlijk ben heb ik maar heel zelden performance problemen, ik kon altijd wel uit de voeten met de profiler die bij netbeans komt. (tegenwoordig gebruik ik dus VisualVM(https://visualvm.dev.java.net/) omdat ik de rest niet echt nodig heb) Free, Opensource etc.

Voor de rest dtrace/btrace en wat ik ver nog heb om inzicht te krijgen in waarin ik inzicht wil hebben.

Heap walker van JProfiler ziet er wel cute uit + die JEE support. Maar de bottlenecks die het blootlegt komen denk ik ook wel met mindere sophisticated tools naar voren.

Ik denk dat ik je beter had kunnen helpen als ik die laaste 5%(zal wel meer zijn tbh) er soms uit wou persen maar dat laat ik doorgaans aan andere mensen over. Profilen doe ik zelf alleen tot op het niveau dat ik kan checken/aantonen dat mijn algoritmes/pattern's enigszins koosjer zijn. O-)

IceManX schreef: sowieso


Acties:
  • 0 Henk 'm!

  • Alain
  • Registratie: Oktober 2002
  • Niet online
Mr_Light schreef op vrijdag 06 februari 2009 @ 12:08:
Meuh als je hier al kort door de bocht gaat, gaat programmeren met concurency helemaal een feest worden. Gezien daar een stuk minder bescherming is voro de programmeur tegen fouten.
Ik vind debuggen wel leuk, maar als ik een bepaalde oplossing zoek en ik zie een aantal voorbeelden die allemaal andere classes gebruiken om hetzelfde te bereiken raak ik confused. Ik heb btw een elektrotechnische achtergrond en ben naast mijn werk informatica aan het studeren. Java ken ik nog niet echt, daarom ben ik er aan begonnen. Later in de opleiding zal dat van pas komen. :)
Verder: heb je Thread.yield() al een keer geprobeerd?
Nee. Mijn idee toen ik bovenstaande code schreef was om te simuleren dat de cpu druk bezig was. Ik ben inmiddels iets verder en heb jullie tips toegepast in een projectje van mij waar ik het nodig had. Het doet precies wat ik wil. :)
Mbt InterruptedException en het alleen printen van de stack trace werp eens een blik op het opniew setten van de interrupt status. (Welke je overigens zou moeten checken voor dat je dat loopje uitvoert(waar je de scope van i ook wel mag verkleinen), maar het is test code dus dat zien we dan maar door de vingers - alhoewel het misschien geen gek idee is om te kijken waarom je dat zou moeten doen.)
Dit is inderdaad iets waar ik me nog in moet verdiepen.
Ook lijkt me het nummeren van de task meer wat voor de TaskManager.
Klopt. Die staat in de task zodat ik behoefte had aan een semaphore. ;)
Als laatste twijvel ik nog over of numTasks en lockId niet ook volatile moeten zijn(of by lockId 'final') ivm visability - het probleem is echter dat ik nog nooit een static volatile nodig gehad heb. 8)7 Uberhaupt een static variable dat geen constant is gebruik is zelden tot nooit.
Ook hier moet ik me verder in verdiepen. Bedankt voor de input. :)

You don't have to be crazy to do this job, but it helps ....


Acties:
  • 0 Henk 'm!

  • Wirf
  • Registratie: April 2000
  • Laatst online: 04-09 08:21
Mr_Light schreef op vrijdag 06 februari 2009 @ 20:49:
Als ik heel eerlijk ben heb ik maar heel zelden performance problemen, ik kon altijd wel uit de voeten met de profiler die bij netbeans komt. (tegenwoordig gebruik ik dus VisualVM(https://visualvm.dev.java.net/) omdat ik de rest niet echt nodig heb) Free, Opensource etc.
Bedankt voor de link naar VisualVM, die kende ik nog niet en zet er toch interessant uit.
Heap walker van JProfiler ziet er wel cute uit + die JEE support. Maar de bottlenecks die het blootlegt komen denk ik ook wel met mindere sophisticated tools naar voren.
Nou, ik heb al aardig wat profilers geprobeerd, maar een heleboel waren of niet handig in gebruik, of deden het niet in combinatie met onze applicatie (waarschijnlijk raken die profilers in de war van het gebruik van reflection door Spring, of ander wel door een combinatie van Spring, Hibernate, SOAP, woodstox, HP-UX, tapestry, HibernateSpatial, JTS of een van de andere vage dependencies)
Ik denk dat ik je beter had kunnen helpen als ik die laaste 5%(zal wel meer zijn tbh)[..]
Ik heb ongeveer een verdubbeling van de snelheid nodig om aan mijn verplichtingen te voldoen, en ongeveer een vertienvoudiging in snelheid om aan de verwachtingen te voldoen :o

Eigenlijk moet de database-laag een keer op de schop, want onze bottleneck zit nu in de verwerkingssnelheid van Oracle

Heeft sinds kort zijn wachtwoord weer terug gevonden!


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Wirf schreef op zaterdag 07 februari 2009 @ 11:03:
[...]

Bedankt voor de link naar VisualVM, die kende ik nog niet en zet er toch interessant uit.
VisualVM wordt sinds Java 6 Update 7 overigens meegeleverd door Sun (apart downloaden is dan dus niet nodig).

Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Ik heb - met wat aanpassingen - de testcase van Gerco uitgevoerd (Windows Vista 64, dualcore). Met de 64 bit server, 32 bit server en 32 bit client VM wordt bij het uitvoeren met 2 (of meer) threads beide cores gebruikt.
De server VM is wel ongeveer 2x zo snel tov de client VM voor deze test.

Acties:
  • 0 Henk 'm!

  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 01:28

Gerco

Professional Newbie

En dan de hamvraag: Met welke JVM? Ik heb 1.5.0_14 gebruikt. Ik heb niet gekeken of het op een recentere VM anders werkt. Apart trouwens dat de server VM twee keer zo snel is, maar dat het niet aan de threading lijkt te liggen. Zo'n complexe case is het nu ook weer niet, daar valt weinig aan te optimaliseren zou je zeggen.

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Gerco schreef op zaterdag 07 februari 2009 @ 17:29:
En dan de hamvraag: Met welke JVM? Ik heb 1.5.0_14 gebruikt. Ik heb niet gekeken of het op een recentere VM anders werkt. Apart trouwens dat de server VM twee keer zo snel is, maar dat het niet aan de threading lijkt te liggen. Zo'n complexe case is het nu ook weer niet, daar valt weinig aan te optimaliseren zou je zeggen.
Java 6 Update 11, maar ik heb genoeg met Java 5 gewerkt om te weten dat er ook daar altijd native threads worden gebruikt. Maar ik zal zo even een Java 5 installeren en kijken wat er gebeurt.

Het snelheidsverschil kan ook juist komen doordat het zo'n simpele case is. Toen ik de compilatie tijden uitprintte werd duidelijk dat de server VM meer tijd bezig was geweest met compileren (0,031s tov 0,002 seconden). Waarschijnlijk is er door de server VM meer gecompileerd, of bij het compileren beter geoptimaliseerd.

Net even geprobeerd met Java 5 Update 17 (32 bit), maar de resultaten zijn vergelijkbaar met Java 6 Update 11.

[ Voor 5% gewijzigd door Remus op 08-02-2009 09:58 . Reden: Ook Java 5 geprobeerd ]


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Remus schreef op zondag 08 februari 2009 @ 09:36:
Toen ik de compilatie tijden uitprintte werd duidelijk dat de server VM meer tijd bezig was geweest met compileren (0,031s tov 0,002 seconden). Waarschijnlijk is er door de server VM meer gecompileerd, of bij het compileren beter geoptimaliseerd.
Wat dan ook precies het verschil tussen de server en client VM's is, dus dat zal inderdaad de reden zijn :)

Ik heb zelf iig al geregeld gehad dat ik een groot verschil tussen twee ogenschijnlijk gelijke VM's ontdekte en dat dan bleek dat de ene als client gestart werd; bijvoorbeeld door dat "fijne" jsvc van debian/ubuntu dat ze gebruiken om tomcat te starten, maar dat niet dezelfde checks uitvoert op je systeem als een kale vm en dus doodleuk een client-vm start.

En het andere belangrijke verschil is voor Gerco waarschijnlijk dat de server-vm standaard met de parallelle garbage collector gestart wordt. En dat levert wat betere throughput op dan de single-threaded versie ;)

[ Voor 15% gewijzigd door ACM op 08-02-2009 11:33 ]

Pagina: 1