Higher `scale` in `BigDecimal#divide()` is faster? - java

I came up with a question that initially was going to be a Q/A style question.
The original question:
How much does a higher scale in BigDecimal#divide() affect performance?
So, I created this SSCCE:
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String args[]) {
int[] scales = new int[] {1, 10, 50, 100, 500, 1000, 5000, 100000, 1000000};
for(Integer scale : scales) {
long start = System.nanoTime();
BigDecimal.ONE.divide(BigDecimal.valueOf(7), scale, RoundingMode.HALF_UP);
long end = System.nanoTime();
long elapsed = end - start;
String elapsed_str = String.format("%d mins, %d secs, %d millis, %d nanos",
TimeUnit.NANOSECONDS.toMinutes(elapsed),
TimeUnit.NANOSECONDS.toSeconds(elapsed) - TimeUnit.MINUTES.toSeconds(TimeUnit.NANOSECONDS.toMinutes(elapsed)),
TimeUnit.NANOSECONDS.toMillis(elapsed) - TimeUnit.SECONDS.toMillis(TimeUnit.NANOSECONDS.toSeconds(elapsed)),
elapsed - TimeUnit.MILLISECONDS.toNanos(TimeUnit.NANOSECONDS.toMillis(elapsed))
);
System.out.println("Time for scale = " + scale + ": " + elapsed_str);
}
}
}
The output was thus:
Time for scale = 1: 0 mins, 0 secs, 2 millis, 883903 nanos
Time for scale = 10: 0 mins, 0 secs, 0 millis, 13995 nanos
Time for scale = 50: 0 mins, 0 secs, 1 millis, 138727 nanos
Time for scale = 100: 0 mins, 0 secs, 0 millis, 645636 nanos
Time for scale = 500: 0 mins, 0 secs, 1 millis, 250220 nanos
Time for scale = 1000: 0 mins, 0 secs, 4 millis, 38957 nanos
Time for scale = 5000: 0 mins, 0 secs, 15 millis, 66549 nanos
Time for scale = 100000: 0 mins, 0 secs, 500 millis, 873987 nanos
Time for scale = 1000000: 0 mins, 50 secs, 183 millis, 686684 nanos
As the order of magnitude increases, the performance is affected exponentially. But what had me scratching my head were these lines:
Time for scale = 1: 0 mins, 0 secs, 2 millis, 883903 nanos
Time for scale = 10: 0 mins, 0 secs, 0 millis, 13995 nanos
Time for scale = 50: 0 mins, 0 secs, 1 millis, 138727 nanos
Time for scale = 100: 0 mins, 0 secs, 0 millis, 645636 nanos
Time for scale = 500: 0 mins, 0 secs, 1 millis, 250220 nano
It appears that a scale of 10 is optimal for BigDecimal#divide()? And a scale of 100 is faster than 50? I thought this might just be an anomaly, so I ran it again (this time, omitting the highest two scales because I didn't want to wait 50 seconds :)) and this is the result:
Time for scale = 1: 0 mins, 0 secs, 3 millis, 440903 nanos
Time for scale = 10: 0 mins, 0 secs, 0 millis, 10263 nanos
Time for scale = 50: 0 mins, 0 secs, 0 millis, 833169 nanos
Time for scale = 100: 0 mins, 0 secs, 0 millis, 487492 nanos
Time for scale = 500: 0 mins, 0 secs, 0 millis, 802846 nanos
Time for scale = 1000: 0 mins, 0 secs, 2 millis, 475715 nanos
Time for scale = 5000: 0 mins, 0 secs, 16 millis, 646117 nanos
Again, 10 is considerably faster than 1, and 100 is again faster than 50.
I tried again and again, and 100 was always faster than 50. And a scale of 1 was always slower than everything less than 1000.
Anyone have an explanation?

Java code is optimised dynamically but the first time you run it, it has to be loaded. To avoid confusing results caused by the code being re-compiled as you run it, I suggest
doing your longest run first, not last.
run the test for at least 2 seconds.
ignore the first run of at least 3 to 5 runs (You only have one in this case)
I would keep the scale simple so you can compare all the results. In your case I would do each one at least 1000 times and print the average in micro-seconds.

Related

My loop does not work for finding the System Time Millis in Eclipse Java

Why does my loop not work?
I'm trying to increment by 5 and the output the time it took to increment with the linear method
for(n=5;n<=10;n=n+5) {
long startTime= System.currentTimeMillis();
System.out.println(startTime);
System.out.println("\nOddanaci(" + n +")\n" );
linear(n);
long endTime= System.currentTimeMillis();
long diff= endTime - startTime;
System.out.println("\n\nThe Total time it took to run this program is\n"+diff);
}
my output
Please enter the a non-negative value to find its Oddonacci sequence: 3
Here is the Oddonacci(3) sequence
1 1 1
The method has been called 1 times.
1537756698523
Oddanaci(5)
1 1 1 3 5
The Total time it took to run this program is
1
1537756698524
Oddanaci(10)
1 1 1 3 5 9 17 31 57 105
The Total time it took to run this program is
0
Why does it output as zero? Doesn't n iterate?

Fast-foward a real-time java simulation

I'm doing a simulation of a Bank where I have one queue and three cashiers; the objective is to obtain statistics, for example the average wait time of the customers. Every cashier has a different attention time and a new client comes enters the bank every 1.5 minutes. The bank just runs 5 hours. Now, is there a way I could program it in real time (cause I think its the only way) and then some way fast foward the jvm in order to get the statistics sooner?
You cannot fast-forward the JVM, but you can program this to simulate in non real-time to get statistics soon. Play with variables that represents time in a small enough unit of time (for example in seconds) and do the simulation normally, increasing the time in one unit. For the queue you can use a deque containing the instants when every client entered the bank, and then iterate over time.
private static int TOTAL_TIME = 5*3600; // Hours to seconds
private static int TIME_BETWEEN_CLIENTS = 90; // In seconds
private static int CASHIERS = 3;
public static void main(String[] args) {
// Create cashiers and assign range of attentions time to each cashier, in seconds
// This is an example for cashiers with 3, 3~6 and 4.5~15 minutes
List<Cashier> cashiers = new ArrayList<Cashier>(CASHIERS);
cashiers.add(new Cashier(180, 180));
cashiers.add(new Cashier(180, 360));
cashiers.add(new Cashier(270, 900));
int time = 0; // Counting variable in seconds
int waitingTime = 0; // Save here all waiting time for all clients
int clients = 0; // Save here all clients
// Register here all available cashiers
ArrayList<Cashier> freeCashiers;
Deque<Integer> queue = new ArrayDeque<Integer>(); // Clients queue
// Iterate until bank closes and all clients have been attended
while (time < TOTAL_TIME || !queue.isEmpty()) {
// New client if the bank is not closed
if (time < TOTAL_TIME && time%TIME_BETWEEN_CLIENTS == 0) {
queue.add(time); // Register customer start waiting time
clients++;
}
// Check for free cashiers when someone is on queue
if (!queue.isEmpty()) {
freeCashiers = new ArrayList<Cashier>(CASHIERS);
for (Cashier c : cashiers) {
if (c.isFree(time))
freeCashiers.add(c);
}
if (!freeCashiers.isEmpty()) {
// Register spent time for the removed client
waitingTime += time - queue.removeFirst();
// Select a random cashier from all the available cashiers
Cashier randomAvailableCashier = freeCashiers.get(Cashier.RANDOM.nextInt(freeCashiers.size()));
// Register when the randomly selected cashier will be free again
randomAvailableCashier.attendNewClient(time);
}
}
time++; // Adds one second
}
// Calculate statistics
int avgWaitingTime = waitingTime/clients; // In seconds
System.out.println("Average waiting time on queue: " + formatTime(avgWaitingTime));
}
/**
* Formats a time in minutes and seconds
* #param time the time in seconds
* #return the formatted time
*/
private static String formatTime(int time) {
StringBuilder result = new StringBuilder();
if (time > 60) {
result.append(time/60).append(" minutes");
time %= 60;
if (time > 0)
result.append(" and ");
else
result.append(".");
}
if (time > 0)
result.append(time).append(" seconds.");
return result.toString();
}
And the Cashier class:
public class Cashier {
public static final Random RANDOM = new Random();
private int minAttentionTime, maxAttentionTime, endTime;
/**
* Constructs new Cashier with a range of possible attention time, in seconds
* #param minAttentionTime in seconds
* #param maxAttentionTime in seconds
*/
public Cashier(int minAttentionTime, int maxAttentionTime) {
this.minAttentionTime = minAttentionTime;
this.maxAttentionTime = maxAttentionTime;
endTime = 0;
}
/**
* Register end time with a random attention time in the range.
* #param currentTime the current time in seconds
*/
public void attendNewClient(int currentTime) {
endTime = currentTime + getRandomNumberInRange(minAttentionTime, maxAttentionTime);
}
/**
* Returns if this cashier is available
* #param currentTime the current time in seconds
* #return true if this cashier is free, false otherwise
*/
public boolean isFree(int currentTime) {
return currentTime >= endTime;
}
/**
* Returns a random number in range [min, max]
* #param min the minimum number, inclusive
* #param max the maximum number, inclusive
* #return a random number in range [min, max]
*/
private int getRandomNumberInRange(int min, int max) {
return RANDOM.nextInt(max - min + 1) + min;
}
public String toString() {
return String.valueOf(endTime);
}
}
With this implementation a new client will go to a random free cashier.
If you instead used a queue system, you could go very quickly. The queue would contain events that are coming up, and a timestamp of when they are due. The queue would be organized by event timestamp. Initially, you could seed this queue with a new customer every 1.5 minutes, and an event for the bank closing in 5 hours. Then you pop the first event off the queue, figure out what that triggers. For example, the first event of a customer entering causes you to increase the customer wait line count. A teller becoming free causes the wait line count to reduce and an agent-customer activity that last for some random time length. This causes a new event (another teller being free) to be placed in the queue. When a teller being free occurs, and there are no customers in line, you set an empty teller flag, which would be checked when you next handle a customer entering event. You could quickly run through an entire day within a few seconds with approach, and you can gather statistics on whatever you want at each event.
If you don't mind to use formulas here is my math. If some cashier has time T1 for processing one person then this cashier processes 1/T1 people per time unit. Number of people processed by N cashiers are added. Coming back from processing speed to processing time we get common time for processing one person by all cashiers:
Tcom = 1/(1/T1 + 1/T2 + ... + 1/Tn)
You compare this time with your 1.5 minutes and if Tcom <= 1.5 minutes then your queue will be mostly empty or consist of 1 person (and average waiting time will be around half of average of Ti). In case Tcom > 1.5 minutes your queue will grow constantly by one person each Tcom - 1.5 minutes. Queue length at the end of working day will be Lavg = 5 * 60 / (Tcom - 15) people (and half of it in average). Each person in head of the queue is removed from it each Tcom minutes. It means that average waiting time is going to be Tcom * Lavg / 2.
I know it's all very rough estimates but why not?
(You deleted your other almost identical question. Here's the long answer i wrote before knowing that. Good that you had this copy, so i didn't waste my time)
Here is the functional pseudo. I actually coded it, out of curiosity.
I use a computer language called APL, which is most likely unknown to you, hence pseudo.
You need to create a 2-dimensional table, ie. an array, with 6 columns. If you cannot create 2-dimensional arrays, you can alternatively (hopefully) create 1-dimensional arrays, ie. lists, where one list holds the values of one column in the table. The important thing is that you can index into arrays. If you use listst, you must extend the code a bit.
You will only work with integer values.
The solution is that you pre-create a table, which holds the customer arrival times in column 1. All other columns hold zeroes to start with. Then you loop through the table, updating the other columns, row-by-row, using a help variable for the cashiers.
Once you have looped through all rows in the table, you extract the desired results from it, by additional calculation.
For the cashiers actions, you need another 2-dimensional table, holding [nr of cashiers] rows and 3 columns. Similarly, you can replace it with 3 lists, one per table column. Columns 2 and 3 in this small table accumulate each cashiers tim ethey spent serving a customer, and time they spent waiting for customers.
You do not work with clock times, but with seconds since day start. For example 8am equals 28800 (ie. number of seconds elapsed since day start). To this value, you can easily add customer arrival times with 90 seconds increments (or any, arbitrary amount of seconds intervals). You pre-create customer arrival times for the entire 5-hour (or any hour) day.
// Create a table with 6 columns. Columns are
// 1: Customer arrival timestamp (first value is 28800) [seconds since midnight]
// 2: The timestamp the customer was attended [seconds since midnight]
// 3: The time a customer had to wait [seconds]
// 4: The time the customer spent with the cashier [seconds]
// 5: The timestamp the customer leaves the bank [seconds since midnight]
// 6: The cashier # that provided service, 1,2 or 3 in your case
//
// If the customer arrive at regular 90 s intervals, the table now looks like this
// (10 first rows only, but you must populate it up to bank closing time)
// which would need ca 200 rows. Call this table "Cust":
// 28800 0 0 0 0 0
// 28890 0 0 0 0 0
// 28980 0 0 0 0 0
// 29070 0 0 0 0 0
// 29160 0 0 0 0 0
// 29250 0 0 0 0 0
// 29340 0 0 0 0 0
// 29430 0 0 0 0 0
// 29520 0 0 0 0 0
// 29610 0 0 0 0 0
//
// Create a variable for the cashiers, 1 row per cashier. Columns are:
// 1. An "action timestamp", initially 28800 [seconds since midnight]
// 2. Accumulation of cashiers attention time [seconds]
// 3. Accumulation of cashiers wait/slack time [seconds]
//
// The table now looks like this
// (add more rows if you have more cashiers). Call this table "Cash":
// 28800 0 0
// 28800 0 0
// 28800 0 0
rows = [numers of rows in Cust]
i = 0
:While i < rows
// Note! This pseudocode uses 0-origin, ie. array[0] is the first element
// Find the row number of Cash with the _smallest_ value in it's column 1
row = [0, 1 or 2] // You commonly first find the smallest number, then compare it against each row. The match is the row with smallest number.
// Attention time for this customer (you said "range of"),
// we use 265 s now, for simplicity, but you can give time another value each time
time = 265
:If Cust[i;0]<Cash[row;0] // Customer has waited
Cust[i;1]=Cash[row;0] // Timestamp this Customer got service
Cust[i;3]=time // Time spent with cashier
Cust[i;4]=Cash[row;0]+time // Timestamp customer leaves the bank
Cust[i;5]=row // Which cashier provided the service
Cash[row;1]+=time // Increase accumulated attend time for cashier
Cash[row;0]+=time // Next timestamp this cashier is free
:Else // Cashier had free time (bank was empty until now)
Cash[row;2]+=Cust[i;0]-Cash[row;0] // Accumulate freetime for cashier
Cash[row;1]+=time // Accumulate attend time for cashier
Cust[i;1]=Cust[i;0] // There was no wait time for customer
Cust[i;3]=time // Time spent with cashier
Cust[i;4]=Cust[i;0]+time // Timestamp customer leaves the bank
Cust[i;5]=row // Which cashier provided the service
Cash[row;0]=Cust[i;0]+time // Next timestamp this cashier is free
:End
i+←1
:End
// Resolve customer wait times, equals [time attended] - [time arrived] and
// populate 3rd column of Cust (you must probably loop), row by row
Cust[n;2] = Cust[n;1] - Cust[n;0] // n = each row
// Cust now looks like this:
// 28800 28800 0 265 29065 0
// 28890 28890 0 265 29155 1
// 28980 28980 0 265 29245 2
// 29070 29070 0 265 29335 0
// 29160 29160 0 265 29425 1
// 29250 29250 0 265 29515 2
// 29340 29340 0 265 29605 0
// 29430 29430 0 265 29695 1
// 29520 29520 0 265 29785 2
// 29610 29610 0 265 29875 0
fnAvg = [function that calculates the average of a list of numbers]
// Extract the results
// Note: For example "Cust[;2]" means _all rows in column 2 of Cust_, ie. a list of numbers
// You don't need to create any lists though, just loop through them all and calculate/accumulate
'Nr of customers attended immediately: ',[number of zeroes in Cust[;2]]
'Nr of customers who waited: ',[number of non-zeroes in Cust[;2]]
'Wait times: ',[all numbers in Cust[;2] - this is a list of numbers]
'Average wait times: ',[avg Cust[;2]]
'Average wait times (only those who waited): ',[avg Cust[;2] elements that are non-zero]
'Total cashier attend times: ',Cash[;1]
'Total cashier free times: ',Cash[;2]
// And finally a verification calc (backwards calc, just for curiosity)
'Check: Total customer existance time: ',Cust[i-1;4]-Cust[0;0]
'Check: Cashier total times (should be close to above value): ',[sum of Cash[n;1 2]]
The result with the arguments above is (numbers are seconds unless otherwise said):
Nr of customers attended immediately: 10
Nr of customers who waited: 0
Wait times: 0 0 0 0 0 0 0 0 0 0
Average wait times: 0
Average wait times (only those who waited): 0
Total cashier attend times: 1060 795 795
Total cashier free times: 15 100 190
Check: Total customer existance time: 1075
Check: Cashier total times (should be close to previous row): 1075 895 985
If you have 20 customers arriving, and use random attend times between 100 and 480 seconds, you may get for example:
Nr of customers served immediately: 8
Nr of customers waited: 12
Wait times: 0 0 0 0 0 14 0 0 0 200 120 147 183 122 149 111 185 178 244 218
Average wait times: 93.55
Average wait times (only those who waited): 155.9166667
Total cashier attend times: 1820 1836 1819
Total cashier free times: 217 197 309
Check: Total customer existance time: 2128
Check: Cashier total times (should be close to previous row): 2037 2033 2128
with Cust (edited: was erratically Cash) table looking like this:
28800 28800 0 160 28960 0
28890 28890 0 433 29323 1
28980 28980 0 114 29094 2
29070 29070 0 194 29264 0
29160 29160 0 117 29277 2
29250 29264 14 149 29413 0
29340 29340 0 470 29810 2
29430 29430 0 390 29820 1
29520 29520 0 417 29937 0
29610 29810 200 253 30063 2
29700 29820 120 389 30209 1
29790 29937 147 155 30092 0
29880 30063 183 445 30508 2
29970 30092 122 169 30261 0
30060 30209 149 216 30425 1
30150 30261 111 403 30664 0
30240 30425 185 408 30833 1
30330 30508 178 220 30728 2
30420 30664 244 173 30837 0
30510 30728 218 200 30928 2
Try with populating the 1st column of Cust with different, more random customer arrivbal times, and for each iteration, the time with values that fit your real environment better.
This pseudo supports both "bank temporarily empty" and "long que all time", and also supports the "Once the bank closes after 5 hours, every client gets attended"-criterion (the remaining customers will be cleaned up").
Good luck! :-)

Java delete numbers behind comma

I want to calculate Days, Hours etc.
I want to make it like this:
184 Seconds / 60 = 3,0666666666667
Means 3 Minutes.
0,666666666667 * 60 = 4
So 184 Seconds are 3 Min. und 4 Seconds.
Now i dont know how to bring this into Java. I need a function to seperate the Pre-Comma Value from the After-Comma Value.
It's just a simple example. I want to do this with years,weeks,days and so on
It seems that you are looking for modulo (reminder) operator %. Also there is no "after comma value" in integers words so 184 / 60 = 3 not 3.06666.
int time = 184;
int minutes = time / 60;
int seconds = time % 60;
System.out.println(minutes + " minutes : " + seconds + " seconds");
Output: 3 minutes : 4 seconds
You can also use Period from JodaTime library.
int time = 184;
Period period = new Period(time * 1000);//in milliseconds
System.out.printf("%d minutes, %d seconds%n", period.getMinutes(),
period.getSeconds());
which will print 3 minutes, 4 seconds.
Just use %, /, and a little math:
int totalSeconds = 184;
int minutes = totalSeconds/60; //will be 3 minutes
int seconds = totalSeconds%60; // will be 4 seconds

No JIT Optimization

Have a look at this question :
The code:
class test
{
public static void main(String abc[])
{
for( int k=1; k<=3; k++)
{
for( int N=1; N<=1_000_000_000; N=N*10)
{
long t1 = System.nanoTime();
int j=1;
for(int i=0; i<=N; i++)
j=j*i;
long t2 = System.nanoTime() - t1;
System.out.println("Time taken for "+ N + " : "+ t2);
}
}
}
}
The output of above code:
Time taken for 1 : 2160
Time taken for 10 : 1142
Time taken for 100 : 2651
Time taken for 1000 : 19453
Time taken for 10000 : 407754
Time taken for 100000 : 4648124
Time taken for 1000000 : 12859417
Time taken for 10000000 : 13706643
Time taken for 100000000 : 136928177
Time taken for 1000000000 : 1368847843
Time taken for 1 : 264
Time taken for 10 : 233
Time taken for 100 : 332
Time taken for 1000 : 1562
Time taken for 10000 : 17341
Time taken for 100000 : 136869
Time taken for 1000000 : 1366934
Time taken for 10000000 : 13689017
Time taken for 100000000 : 136887869
Time taken for 1000000000 : 1368178175
Time taken for 1 : 231
Time taken for 10 : 242
Time taken for 100 : 328
Time taken for 1000 : 1551
Time taken for 10000 : 13854
Time taken for 100000 : 136850
Time taken for 1000000 : 1366919
Time taken for 10000000 : 13692465
Time taken for 100000000 : 136833634
Time taken for 1000000000 : 1368862705
In the loop, even though the value of i starts from 0, indicating the product to be zero, there is no JIT Optimization. Why not ?
In the link provided above, I had previously put the for loop in a method call, which the JIT was optimizing. Is putting the statements in a method facilitating in the optimization process ?
In your previous question the JIT optimized away the complete code of the method start without any analysis as to what number happened to be present in the variables upon method return. This is because you chose to make your method void, giving the JIT a dead-easy clue that any values calculated will be discarded.
Contrasting your current example with the one from your previous question, there are no void methods called so naturally the optimization does not occur. Why there is not some other optimization which would help this completely different case is an unanswerable question. There is just no such optimization in the specefic JVM implementation, and the specific JVM invocation, with which you have tested your code.
The loop itself does get jitted (as observed by the slightly lower running times on second and third execution), however eliminating the entire loop is - afaik - only done when the method itself is executed multiple times, because only then the JIT has sufficient runtime information to be sure it can actually eliminate it without consequence.
If I change your code, the loop is eliminated on the third invocation:
public class LoopJit2 {
public static void main(String abc[]) {
for (int x = 0; x < 3; x++) {
loopMethod();
}
}
private static void loopMethod() {
for (int N = 1; N <= 1_000_000_000; N = N * 10) {
long t1 = System.nanoTime();
int j = 1;
for (int i = 0; i <= N; i++)
j = j * i;
long t2 = System.nanoTime() - t1;
System.out.println("Time taken for " + N + " : " + t2);
}
}
}
Time series:
Time taken for 1 : 1466
Time taken for 10 : 1467
Time taken for 100 : 2934
Time taken for 1000 : 20044
Time taken for 10000 : 201422
Time taken for 100000 : 1993200
Time taken for 1000000 : 4038223
Time taken for 10000000 : 11182357
Time taken for 100000000 : 111290192
Time taken for 1000000000 : 1038002176
Time taken for 1 : 1466
Time taken for 10 : 1467
Time taken for 100 : 2934
Time taken for 1000 : 20044
Time taken for 10000 : 10755
Time taken for 100000 : 124667
Time taken for 1000000 : 1010045
Time taken for 10000000 : 10201156
Time taken for 100000000 : 103184413
Time taken for 1000000000 : 1019723107
Time taken for 1 : 978
Time taken for 10 : 1467
Time taken for 100 : 1467
Time taken for 1000 : 1955
Time taken for 10000 : 978
Time taken for 100000 : 489
Time taken for 1000000 : 977
Time taken for 10000000 : 977
Time taken for 100000000 : 978
Time taken for 1000000000 : 978

Java multiplication strange behaviour

Have a look at the code below:
class Test
{
public static void main(String abc[])
{
for( int N=1; N <= 1_000_000_000; N=N*10)
{
long t1 = System.nanoTime();
start(N);
long t2 = System.nanoTime() - t1;
System.out.println("Time taken for " + N + " : " + t2);
}
}
public static void start( int N )
{
int j=1;
for(int i=0; i<=N; i++)
j=j*i;
}
}
The output produced by the above question is:
Time taken for 1 : 7267
Time taken for 10 : 3312
Time taken for 100 : 7908
Time taken for 1000 : 51181
Time taken for 10000 : 432124
Time taken for 100000 : 4313696
Time taken for 1000000 : 9347132
Time taken for 10000000 : 858
Time taken for 100000000 : 658
Time taken for 1000000000 : 750
Questions:
1.) Why is time taken for N=1 unusually greater than the N=10 ? (sometimes it even exceeds N=100)
2.) Why is time taken for N=10M and onwards unusually lower ?
The pattern indicated in the above questions is profound and remains even after many iterations.
Is there any connection to memoization here ?
EDIT:
Thank you for your answers. I thought of replacing the method call with the actual loop. But now, there is no JIT Optimization. Why not ? Is putting the statements in a method facilitating in the optimization process ?
The modified code is below:
class test
{
public static void main(String abc[])
{
for( int k=1; k<=3; k++)
{
for( int N=1; N<=1_000_000_000; N=N*10)
{
long t1 = System.nanoTime();
int j=1;
for(int i=0; i<=N; i++)
j=j*i;
long t2 = System.nanoTime() - t1;
System.out.println("Time taken for "+ N + " : "+ t2);
}
}
}
}
EDIT 2:
The output of above modified code:
Time taken for 1 : 2160
Time taken for 10 : 1142
Time taken for 100 : 2651
Time taken for 1000 : 19453
Time taken for 10000 : 407754
Time taken for 100000 : 4648124
Time taken for 1000000 : 12859417
Time taken for 10000000 : 13706643
Time taken for 100000000 : 136928177
Time taken for 1000000000 : 1368847843
Time taken for 1 : 264
Time taken for 10 : 233
Time taken for 100 : 332
Time taken for 1000 : 1562
Time taken for 10000 : 17341
Time taken for 100000 : 136869
Time taken for 1000000 : 1366934
Time taken for 10000000 : 13689017
Time taken for 100000000 : 136887869
Time taken for 1000000000 : 1368178175
Time taken for 1 : 231
Time taken for 10 : 242
Time taken for 100 : 328
Time taken for 1000 : 1551
Time taken for 10000 : 13854
Time taken for 100000 : 136850
Time taken for 1000000 : 1366919
Time taken for 10000000 : 13692465
Time taken for 100000000 : 136833634
Time taken for 1000000000 : 1368862705
1.) Why is time taken for N=1 unusually greater than the N=10
Because it's the first time the VM has seen that code - it may decide to just interpret it, or it will take a little bit of time JITting it to native code, but probably without optimization. This is one of the "gotchas" of benchmarking Java.
2.) Why is time taken for N=10M and onwards unusually lower ?
At that point, the JIT has worked harder to optimize the code - reducing it to almost nothing.
In particular, if you run this code multiple times (just in a loop), you'll see the effect of the JIT compiler optimizing:
Time taken for 1 : 3732
Time taken for 10 : 1399
Time taken for 100 : 3266
Time taken for 1000 : 26591
Time taken for 10000 : 278508
Time taken for 100000 : 2496773
Time taken for 1000000 : 4745361
Time taken for 10000000 : 933
Time taken for 100000000 : 466
Time taken for 1000000000 : 933
Time taken for 1 : 933
Time taken for 10 : 467
Time taken for 100 : 466
Time taken for 1000 : 466
Time taken for 10000 : 933
Time taken for 100000 : 466
Time taken for 1000000 : 933
Time taken for 10000000 : 467
Time taken for 100000000 : 467
Time taken for 1000000000 : 466
Time taken for 1 : 467
Time taken for 10 : 467
Time taken for 100 : 466
Time taken for 1000 : 466
Time taken for 10000 : 466
Time taken for 100000 : 467
Time taken for 1000000 : 466
Time taken for 10000000 : 466
Time taken for 100000000 : 466
Time taken for 1000000000 : 466
As you can see, after the first the loop takes the same amount of time whatever the input (module noise - basically it's always either ~460ns or ~933ns, unpredictably) which means the JIT has optimized the loop out.
If you actually returned j, and changed the initial value of i to 1 instead of 0, you'll see the kind of results you expect. The change of the initial value of i to 1 is because otherwise the JIT can spot that you'll always end up returning 0.
youre actually benchmarking java's JIT. if i modify yout code a bit:
class Test
{
public static void main(String abc[])
{
for( int N=1; N <= 1_000_000_000; N=N*10)
{
long t1 = System.nanoTime();
start(N);
long t2 = System.nanoTime() - t1;
System.out.println("Time taken for " + N + " : " + t2);
}
for( int N=1; N <= 1_000_000_000; N=N*10)
{
long t1 = System.nanoTime();
start(N);
long t2 = System.nanoTime() - t1;
System.out.println("Time taken for " + N + " : " + t2);
}
}
public static void start( int N )
{
int j=1;
for(int i=0; i<=N; i++)
j=j*i;
}
}
i get this:
Time taken for 1 : 1811
Time taken for 10 : 604
Time taken for 100 : 1510
Time taken for 1000 : 10565
Time taken for 10000 : 104439
Time taken for 100000 : 829173
Time taken for 1000000 : 604
Time taken for 10000000 : 302
Time taken for 100000000 : 0
Time taken for 1000000000 : 0
Time taken for 1 : 0
Time taken for 10 : 302
Time taken for 100 : 0
Time taken for 1000 : 302
Time taken for 10000 : 301
Time taken for 100000 : 302
Time taken for 1000000 : 0
Time taken for 10000000 : 0
Time taken for 100000000 : 0
Time taken for 1000000000 : 302
never benchmark a "cold" system. always repeat every measurement several times and discard the 1st few ones because the optimizations have not yet kicked in
The reason is that 1) you don't return the value, and 2) the result of the calculation is always 0. Eventually the JIT will simply compile the loop away.
You get your expected behaviour if you change your loop to:
public static int start(int N) {
int j = 1;
for (int i = 1; i <= N; i++)
j = j * i;
return j;
}
Note that I have both changed the loop init to int i = 1 and added return j. If I only do one of those, the loop will (eventually) still be compiled away.
This will produce the following series (if executed twice):
Time taken for 1 : 2934
Time taken for 10 : 1466
Time taken for 100 : 3422
Time taken for 1000 : 20534
Time taken for 10000 : 191644
Time taken for 100000 : 1898845
Time taken for 1000000 : 1210489
Time taken for 10000000 : 11884401
Time taken for 100000000 : 115257525
Time taken for 1000000000 : 1061254223
Time taken for 1 : 978
Time taken for 10 : 978
Time taken for 100 : 978
Time taken for 1000 : 2444
Time taken for 10000 : 11244
Time taken for 100000 : 103644
Time taken for 1000000 : 1030089
Time taken for 10000000 : 10448535
Time taken for 100000000 : 107299391
Time taken for 1000000000 : 1072580803

Categories