blob: 4a581772989158596796b2a59e653f43ce9d52d0 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.framework;
import java.util.*;
/**
* This test compares the concurrency of an operation defined run().
* It compares the performance of performing the task with 1, 2, 4, and 16 threads.
* On a single CPU machine all of the results should be similar,
* on a multi-CPU machine the 2,4,16 should be faster if the task can be performed concurrently.
*/
public abstract class ConcurrentPerformanceComparisonTest extends PerformanceComparisonTestCase {
public static double NUMBER_OF_CPUS = 1.1; // 0.1 for hyper-threading.
public static int DEFAULT_THREADS = 32;
protected int minThreads;
protected int maxThreads;
protected Exception caughtException;
protected List workerThreads;
public ConcurrentPerformanceComparisonTest() {
this.minThreads = 2;
this.maxThreads = DEFAULT_THREADS;
}
/**
* Return the maximum number of threads the test should run to.
*/
public int getMaxThreads() {
return maxThreads;
}
/**
* Set the maximum number of threads the test should run to.
*/
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* Reset the iteration count.
* This is maintained by the test to allow concurrent tests to manage the count.
*/
@Override
public synchronized void resetIterations() {
this.iterations = 0;
}
/**
* Start 1 thread test.
*/
@Override
public void startTest() {
startTest(1);
}
/**
* Start each worker thread.
*/
public void startTest(int numberOfThreads) {
caughtException = null;
// Spawn worker threads.
for (int index = 0; index < (numberOfThreads - 1); index++) {
WorkerThread thread = (WorkerThread)getWorkerThreads().get(index);
thread.resumeExecution();
}
}
/**
* Allows any test specific setup before starting the test run.
*/
@Override
public void endTest() {
// Suspend the worker threads,
// suspend all no matter what number of threads was to even out performance.
for (int index = 0; index < getMaxThreads(); index++) {
WorkerThread thread = (WorkerThread)getWorkerThreads().get(index);
thread.suspendExecution();
}
// Let workers finish.
Thread.yield();
// Wait for workers to finish,
// wait all no matter what number of threads was to even out performance.
for (int index = 0; index < getMaxThreads(); index++) {
WorkerThread thread = (WorkerThread)getWorkerThreads().get(index);
thread.joinExecution();
}
}
public List getWorkerThreads() {
return workerThreads;
}
/**
* Start the worker threads.
*/
@Override
public void setup() {
int threads = this.minThreads;
while (threads <= getMaxThreads()) {
this.addThreadTest(threads);
threads = threads * 2;
}
this.workerThreads = new ArrayList(getMaxThreads());
for (int index = 0; index < getMaxThreads(); index++) {
WorkerThread thread = new WorkerThread();
this.workerThreads.add(thread);
thread.start();
}
}
/**
* Count REPEATS runs of the run method with 1 thread.
*/
@Override
public void test() throws Exception {
test(1);
}
/**
* Count REPEATS runs of the run method with n threads.
*/
public void test(int numberOfThreads) throws Exception {
if (caughtException != null) {
throw caughtException;
}
runTask();
}
/**
* Stop the worker threads.
*/
@Override
public void reset() {
// Stop the worker threads.
for (int index = 0; index < getWorkerThreads().size(); index++) {
WorkerThread thread = (WorkerThread)getWorkerThreads().get(index);
thread.stopExecution();
}
// Let workers finish.
Thread.yield();
this.workerThreads = null;
}
/**
* Verify the multi-threaded results are NUMBER_OF_CPU times better
* than the single thread, allowing for the allowableDecrease.
*/
@Override
public void verify() {
PerformanceComparisonTestResult result = (PerformanceComparisonTestResult)getTestResult();
for (int index = 0; index < result.percentageDifferences.size(); index++) {
PerformanceComparisonTest test = (PerformanceComparisonTest)getTests().get(index);
double allowable =
(Math.max(Math.min(NUMBER_OF_CPUS, index + 1), NUMBER_OF_CPUS) * 100) - 100 + (getAllowableDecrease() *
(index + 1));
test.setAllowableDecrease(allowable);
}
super.verify();
}
/**
* Perform the task.
*/
public abstract void runTask() throws Exception;
/**
* Spawn numberOfThreads to run the test's run method and count total number
* of operations.
*/
public void addThreadTest(final int numberOfThreads) {
PerformanceComparisonTestCase test = new PerformanceComparisonTestCase() {
@Override
public void startTest() {
ConcurrentPerformanceComparisonTest.this.startTest(numberOfThreads);
}
@Override
public void test() throws Exception {
ConcurrentPerformanceComparisonTest.this.test(numberOfThreads);
}
@Override
public void endTest() {
ConcurrentPerformanceComparisonTest.this.endTest();
}
};
test.setName("ThreadTest:" + numberOfThreads);
addTest(test);
}
/**
* Defines the work thread.
* A worker thread calls the run method in a loop,
* until it is suspended.
*/
protected class WorkerThread extends Thread {
protected volatile boolean isSuspended = true;
protected volatile boolean isDead = false;
/**
* After the next completion of the run method, suspend execution.
* The thread is still alive, but waiting to be signaled to resumed.
*/
public void stopExecution() {
isDead = true;
}
/**
* After the next completion of the run method, suspend execution.
* The thread is still alive, but waiting to be signaled to resumed.
*/
public void suspendExecution() {
isSuspended = true;
}
/**
* Wait for the thread to suspend.
* Synchronize will wait for wait to release synchornization.
*/
public synchronized void joinExecution() {
if (!isSuspended) {
throw new RuntimeException("Must suspend first");
}
}
/**
* Continue running the run method.
*/
public synchronized void resumeExecution() {
isSuspended = false;
try {
notify();
} catch (Exception exception) {
throw new RuntimeException(exception.getMessage());
}
}
/**
* Run the test run method in a loop until killed.
*/
@Override
public synchronized void run() {
try {
while (!isDead) {
// Allows the thread to suspend itself when the current test is done.
if (isSuspended) {
wait();
}
ConcurrentPerformanceComparisonTest.this.runTask();
ConcurrentPerformanceComparisonTest.this.incrementIterations();
}
} catch (Exception exception) {
ConcurrentPerformanceComparisonTest.this.caughtException = exception;
}
}
}
}