-
Notifications
You must be signed in to change notification settings - Fork 3
/
RandomArrayTotal.java
208 lines (171 loc) · 5.89 KB
/
RandomArrayTotal.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Random;
/**
* Demonstrates basic multithreading, and illustrates how to break up a problem
* into subproblems. Also used to motivate the inefficiency of constantly
* creating new threads instead of reusing them.
*
* @author CS 272 Software Development (University of San Francisco)
* @version Fall 2021
*/
public class RandomArrayTotal {
/**
* Calculates a subtotal in an array. Does no validation of parameters, so
* results will vary when invalid indices are provided.
*
* @param numbers the array of numbers to subtotal
* @param start the index of array to start subtotal
* @param chunk the number of values to subtotal
* @return subtotal of numbers from index {@code start} (inclusive) to
* {@code start + chunk} (exclusive)
*/
public static long subtotal(int[] numbers, int start, int chunk) {
long total = 0;
for (int i = start; i < start + chunk; i++) {
total = total + numbers[i];
}
return total;
}
/**
* Calculates total of values in an array.
*
* @param numbers array of numbers to total
* @return total of numbers in array
*/
public static long total(int[] numbers) {
return subtotal(numbers, 0, numbers.length);
}
/**
* Calculates the total of an array using multithreading. Used to demonstrate
* the cost of creating/destroying thread objects versus using a work queue.
*
* @param numbers array of numbers to total
* @param threads number of worker threads to create
* @return total of numbers in array
* @throws InterruptedException if interrupted, see {@link Thread#join()}
*/
public static long total(int[] numbers, int threads) throws InterruptedException {
// make sure we have valid number of threads
if (threads < 1 || threads > numbers.length) {
throw new ArrayIndexOutOfBoundsException(threads);
}
// create an array of workers
ArrayWorker[] workers = new ArrayWorker[threads];
// calculate how to split up the problem
int chunk = numbers.length / workers.length;
int remainder = numbers.length % workers.length;
int last = workers.length - 1;
long total = 0;
assert chunk > 0;
assert remainder >= 0;
// create and start the worker threads
for (int i = 0; i < last; i++) {
workers[i] = new ArrayWorker(numbers, i * chunk, chunk);
workers[i].start();
}
// account for any remainder
workers[last] = new ArrayWorker(numbers, last * chunk, chunk + remainder);
workers[last].start();
// wait for workers to finish and add up subtotal
for (ArrayWorker worker : workers) {
worker.join();
total = total + worker.subtotal;
}
return total;
}
/**
* Uses the {@link RandomArrayTotal#subtotal(int[], int, int)} method to
* generate a subtotal of an array.
*/
private static class ArrayWorker extends Thread {
/** The array of numbers to subtotal */
private final int[] numbers;
/** The starting index in the numbers array */
private final int start;
/** The ending index in the numbers array */
private final int end;
/** The calculated subtotal from start to end in the numbers array */
private long subtotal;
/**
* Initializes this worker.
*
* @param numbers the array of numbers
* @param start the starting index
* @param end the ending index
*/
public ArrayWorker(int[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
this.subtotal = 0;
}
@Override
public void run() {
subtotal = subtotal(numbers, start, end);
}
}
/**
* Compares our single threaded and multi threaded approach. Notice that we do
* not see a speedup when we increase the number of threads for small sizes. Do
* you understand why?
*
* @param args unused
* @throws InterruptedException if interrupted
*/
public static void main(String[] args) throws InterruptedException {
// create an array of random integers
int[] numbers = new Random().ints(10, 0, 100).toArray();
System.out.println(Arrays.toString(numbers));
System.out.println();
System.out.println(total(numbers));
System.out.println(total(numbers, 5));
System.out.println();
// int size = 100; // too small of a problem!
int size = 10000000;
double fastest = Double.MAX_VALUE;
fastest = Math.min(fastest, benchmark(size, 1));
fastest = Math.min(fastest, benchmark(size, 2));
fastest = Math.min(fastest, benchmark(size, 3));
fastest = Math.min(fastest, benchmark(size, 5));
fastest = Math.min(fastest, benchmark(size, 8));
System.out.println();
fastest = Math.min(fastest, benchmark(size, 8));
fastest = Math.min(fastest, benchmark(size, 5));
fastest = Math.min(fastest, benchmark(size, 3));
fastest = Math.min(fastest, benchmark(size, 2));
fastest = Math.min(fastest, benchmark(size, 1)); // note how varies from first run
System.out.printf("%nFastest average: %.06f", fastest);
}
/**
* Rough benchmarking estimate. Use a benchmark package for better results.
*
* @param size the size of the random array
* @param threads the number of worker threads
* @return placeholder value
* @throws InterruptedException if interrupted
*/
private static double benchmark(int size, int threads) throws InterruptedException {
int warmup = 10;
int runs = 30;
int[] numbers = new Random().ints(size, 0, 100).toArray();
long placeholder = 0;
double average = 0;
Instant start;
Duration elapsed;
for (int i = 0; i < warmup; i++) {
placeholder = Math.max(placeholder, total(numbers, threads));
}
for (int i = 0; i < runs; i++) {
start = Instant.now();
placeholder = Math.max(placeholder, total(numbers, threads));
elapsed = Duration.between(start, Instant.now());
average += elapsed.toNanos();
}
average /= runs;
average /= Duration.ofSeconds(1).toNanos();
System.out.printf("%.06f seconds average for %d numbers and %d threads (placeholder %d)%n", average, size, threads, placeholder);
return average;
}
}