1919import java .util .concurrent .CountDownLatch ;
2020import java .util .concurrent .ThreadLocalRandom ;
2121import java .util .concurrent .TimeUnit ;
22+ import java .util .concurrent .atomic .AtomicInteger ;
2223import javafx .application .Platform ;
2324import javafx .scene .Scene ;
2425import javafx .scene .input .KeyCode ;
@@ -39,10 +40,31 @@ public static void main(String[] args) throws InterruptedException {
3940 pt .insertMany ();
4041 }
4142
43+ /**
44+ * This test measures performance of the combination {add character; render UI}
45+ * It is important to measure the combination, as a performance enhancements in
46+ * one part could lead to a degradation in the other part.
47+ * In this test, after a warmup of 1000 characters, we add 1000 characters (to the
48+ * already existing 1000 ones). After a character is added, a Pulse is requested.
49+ * Ideally, we render at 60 FPS and a character insert takes much less than 16ms,
50+ * so that not every requestPulse results in a separate pulse.
51+ * We use a prelayoutListener to keep track of how many pulses are executed, and use
52+ * that to calculate the FPS.
53+ * We also calculate the average time that it takes to add and render a character. This
54+ * number needs to be interpreted with caution, as it strongly depends on the performance of the
55+ * individual parts, and on the number of additions that can be done within 16ms.
56+ *
57+ * Note that currently lots of Nodes are created in both parts, and GC activity is expected,
58+ * which can influence the measured values.
59+ *
60+ * Run this test before and after a PR, to check for regression.
61+ * @throws InterruptedException
62+ */
4263 public void insertMany () throws InterruptedException {
43-
44- final int WARMUP_CNT = 100 ; // how many chars to warmup
45- final int TEST_CNT = 100 ; // how many chars to test
64+ AtomicInteger pulseCounter = new AtomicInteger (0 );
65+ Runnable prelayout = () -> pulseCounter .incrementAndGet ();
66+ final int WARMUP_CNT = 1000 ; // how many chars to warmup
67+ final int TEST_CNT = 1000 ; // how many chars to test
4668 final int SLEEP_MS = 5000 ; // sleep between warmup and test
4769 CountDownLatch cdl = new CountDownLatch (1 );
4870 Platform .startup (() -> {
@@ -63,7 +85,6 @@ public void insertMany() throws InterruptedException {
6385 ThreadLocalRandom tlr = ThreadLocalRandom .current ();
6486 System .err .println ("Will test inserts of " + TEST_CNT + " after warmup of " + WARMUP_CNT + " chars, and sleep of " + SLEEP_MS + " ms." );
6587
66- long mem0 = getUsedMemory ();
6788 long startTime = System .nanoTime ();
6889 for (int i = 0 ; i < WARMUP_CNT ; i ++) {
6990 char c = (char ) ('a' + tlr .nextInt (26 ));
@@ -72,7 +93,17 @@ public void insertMany() throws InterruptedException {
7293 }
7394 String k = String .valueOf (c );
7495 KeyEvent evt = new KeyEvent (KeyEvent .KEY_TYPED , k , "" , KeyCode .UNDEFINED , false , false , false , false );
75- Platform .runLater (() -> skin .keyTypedListener (evt ));
96+ CountDownLatch testRun = new CountDownLatch (1 );
97+ Platform .runLater (() -> {
98+ skin .keyTypedListener (evt );
99+ Platform .requestNextPulse ();
100+ testRun .countDown ();
101+ });
102+ boolean await = testRun .await (1 , TimeUnit .SECONDS );
103+ if (!await ) {
104+ System .err .println ("ERROR warming up" );
105+ System .exit (1 );
106+ }
76107 }
77108 CountDownLatch cdl3 = new CountDownLatch (1 );
78109 Platform .runLater (() -> cdl3 .countDown ());
@@ -82,6 +113,9 @@ public void insertMany() throws InterruptedException {
82113 System .err .println ("warmup: total time = " + dur + ", average = " + (dur / (1e6 * WARMUP_CNT )) + ", now sleep for " + SLEEP_MS );
83114 Thread .sleep (SLEEP_MS );
84115 System .err .println ("resume" );
116+ pulseCounter .set (0 );
117+ Platform .runLater (() -> rta .getScene ().addPreLayoutPulseListener (prelayout ));
118+
85119 startTime = System .nanoTime ();
86120 final int cnt = TEST_CNT ;
87121 for (int i = 0 ; i < cnt ; i ++) {
@@ -91,28 +125,26 @@ public void insertMany() throws InterruptedException {
91125 }
92126 String k = String .valueOf (c );
93127 KeyEvent evt = new KeyEvent (KeyEvent .KEY_TYPED , k , "" , KeyCode .UNDEFINED , false , false , false , false );
94- Platform .runLater (() -> skin .keyTypedListener (evt ));
128+ CountDownLatch prodRun = new CountDownLatch (1 );
129+ Platform .runLater (() -> {
130+ skin .keyTypedListener (evt );
131+ Platform .requestNextPulse ();
132+ prodRun .countDown ();
133+ });
134+ boolean await = prodRun .await (1 , TimeUnit .SECONDS );
135+ if (!await ) {
136+ System .err .println ("ERROR running test" );
137+ System .exit (1 );
138+ }
95139 }
96140 CountDownLatch cdl4 = new CountDownLatch (1 );
97141 Platform .runLater (() -> cdl4 .countDown ());
98142 cdl4 .await (10 , TimeUnit .SECONDS );
99143 endTime = System .nanoTime ();
100144 dur = endTime - startTime ;
101- long mem1 = getUsedMemory ( );
102- System .err .println ("total time = " + dur + ", average = " + (dur / (1e6 * cnt )) + " and used mem = " + ( mem1 - mem0 ) );
145+ System . err . println ( "RefreshRate = " + 1.e9 * pulseCounter . get ()/ dur + " FPS" );
146+ System .err .println ("Average duration of a character addition = " + (dur / (1e6 * cnt ))+ "ms" );
103147 Platform .exit ();
104148 }
105149
106- long getUsedMemory () {
107- System .gc ();
108- try {
109- Thread .sleep (50 );
110- } catch (InterruptedException ex ) {
111- System .getLogger (PerformanceTests .class .getName ()).log (System .Logger .Level .ERROR , (String ) null , ex );
112- }
113- Runtime runtime = Runtime .getRuntime ();
114- long val = runtime .totalMemory () - runtime .freeMemory ();
115- return val ;
116- }
117-
118150}
0 commit comments