Rewrite "performance" test to JMH benchmarks

This commit rewrites the remaining "fastEnough" performance tests into
proper JMH benchmarks.

See gh-24830
This commit is contained in:
Brian Clozel
2020-09-25 13:41:10 +02:00
parent e02d3f32b4
commit 61d893257e
22 changed files with 752 additions and 1047 deletions

View File

@@ -34,11 +34,9 @@ import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.beans.support.DerivedFromProtectedBaseBean;
@@ -51,17 +49,13 @@ import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.testfixture.Assume;
import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.lang.Nullable;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.within;
import static org.springframework.core.testfixture.TestGroup.PERFORMANCE;
/**
* Shared tests for property accessors.
@@ -959,60 +953,6 @@ public abstract class AbstractPropertyAccessorTests {
assertThat(target.getArray()[1]).isEqualTo(2);
}
@Test
@EnabledForTestGroups(PERFORMANCE)
public void setPrimitiveArrayPropertyLargeMatching() {
Assume.notLogging(LogFactory.getLog(AbstractPropertyAccessorTests.class));
PrimitiveArrayBean target = new PrimitiveArrayBean();
AbstractPropertyAccessor accessor = createAccessor(target);
int[] input = new int[1024];
StopWatch sw = new StopWatch();
sw.start("array1");
for (int i = 0; i < 1000; i++) {
accessor.setPropertyValue("array", input);
}
sw.stop();
assertThat(target.getArray().length).isEqualTo(1024);
assertThat(target.getArray()[0]).isEqualTo(0);
long time1 = sw.getLastTaskTimeMillis();
assertThat(sw.getLastTaskTimeMillis() < 100).as("Took too long").isTrue();
accessor.registerCustomEditor(String.class, new StringTrimmerEditor(false));
sw.start("array2");
for (int i = 0; i < 1000; i++) {
accessor.setPropertyValue("array", input);
}
sw.stop();
assertThat(sw.getLastTaskTimeMillis() < 125).as("Took too long").isTrue();
accessor.registerCustomEditor(int.class, "array.somePath", new CustomNumberEditor(Integer.class, false));
sw.start("array3");
for (int i = 0; i < 1000; i++) {
accessor.setPropertyValue("array", input);
}
sw.stop();
assertThat(sw.getLastTaskTimeMillis() < 100).as("Took too long").isTrue();
accessor.registerCustomEditor(int.class, "array[0].somePath", new CustomNumberEditor(Integer.class, false));
sw.start("array3");
for (int i = 0; i < 1000; i++) {
accessor.setPropertyValue("array", input);
}
sw.stop();
assertThat(sw.getLastTaskTimeMillis() < 100).as("Took too long").isTrue();
accessor.registerCustomEditor(int.class, new CustomNumberEditor(Integer.class, false));
sw.start("array4");
for (int i = 0; i < 100; i++) {
accessor.setPropertyValue("array", input);
}
sw.stop();
assertThat(target.getArray().length).isEqualTo(1024);
assertThat(target.getArray()[0]).isEqualTo(0);
assertThat(sw.getLastTaskTimeMillis() > time1).as("Took too long").isTrue();
}
@Test
public void setPrimitiveArrayPropertyLargeMatchingWithSpecificEditor() {
PrimitiveArrayBean target = new PrimitiveArrayBean();

View File

@@ -1,168 +0,0 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.core.testfixture.TestGroup;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifiedResource;
/**
* @author Guillaume Poirier
* @author Juergen Hoeller
* @author Chris Beams
* @since 10.03.2004
*/
@EnabledForTestGroups(TestGroup.PERFORMANCE)
public class ConcurrentBeanFactoryTests {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd");
private static final Date DATE_1, DATE_2;
static {
try {
DATE_1 = DATE_FORMAT.parse("2004/08/08");
DATE_2 = DATE_FORMAT.parse("2000/02/02");
}
catch (ParseException e) {
throw new RuntimeException(e);
}
}
private static final Log logger = LogFactory.getLog(ConcurrentBeanFactoryTests.class);
private BeanFactory factory;
private final Set<TestRun> set = Collections.synchronizedSet(new HashSet<>());
private Throwable ex;
@BeforeEach
public void setup() throws Exception {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
qualifiedResource(ConcurrentBeanFactoryTests.class, "context.xml"));
factory.addPropertyEditorRegistrar(
registry -> registry.registerCustomEditor(Date.class,
new CustomDateEditor((DateFormat) DATE_FORMAT.clone(), false)));
this.factory = factory;
}
@Test
public void testSingleThread() {
for (int i = 0; i < 100; i++) {
performTest();
}
}
@Test
public void testConcurrent() {
for (int i = 0; i < 100; i++) {
TestRun run = new TestRun();
run.setDaemon(true);
set.add(run);
}
for (Iterator<TestRun> it = new HashSet<>(set).iterator(); it.hasNext();) {
TestRun run = it.next();
run.start();
}
logger.info("Thread creation over, " + set.size() + " still active.");
synchronized (set) {
while (!set.isEmpty() && ex == null) {
try {
set.wait();
}
catch (InterruptedException e) {
logger.info(e.toString());
}
logger.info(set.size() + " threads still active.");
}
}
if (ex != null) {
throw new AssertionError("Unexpected exception", ex);
}
}
private void performTest() {
ConcurrentBean b1 = (ConcurrentBean) factory.getBean("bean1");
ConcurrentBean b2 = (ConcurrentBean) factory.getBean("bean2");
assertThat(b1.getDate()).isEqualTo(DATE_1);
assertThat(b2.getDate()).isEqualTo(DATE_2);
}
private class TestRun extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10000; i++) {
performTest();
}
}
catch (Throwable e) {
ex = e;
}
finally {
synchronized (set) {
set.remove(this);
set.notifyAll();
}
}
}
}
public static class ConcurrentBean {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
}

View File

@@ -46,7 +46,6 @@ import javax.security.auth.Subject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
@@ -80,7 +79,6 @@ import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.beans.testfixture.beans.DependenciesBean;
import org.springframework.beans.testfixture.beans.DerivedTestBean;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.LifecycleBean;
import org.springframework.beans.testfixture.beans.NestedTestBean;
import org.springframework.beans.testfixture.beans.SideEffectBean;
import org.springframework.beans.testfixture.beans.TestBean;
@@ -93,13 +91,9 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.testfixture.Assume;
import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.core.testfixture.TestGroup;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.core.testfixture.security.TestPrincipal;
import org.springframework.lang.Nullable;
import org.springframework.util.StopWatch;
import org.springframework.util.StringValueResolver;
import static org.assertj.core.api.Assertions.assertThat;
@@ -2352,163 +2346,6 @@ class DefaultListableBeanFactoryTests {
assertThat(tb2.getBeanName()).isEqualTo("myBeanName");
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void prototypeCreationIsFastEnough() {
Assume.notLogging(factoryLog);
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
lbf.registerBeanDefinition("test", rbd);
lbf.freezeConfiguration();
StopWatch sw = new StopWatch();
sw.start("prototype");
for (int i = 0; i < 100000; i++) {
lbf.getBean("test");
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 3000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void prototypeCreationWithDependencyCheckIsFastEnough() {
Assume.notLogging(factoryLog);
RootBeanDefinition rbd = new RootBeanDefinition(LifecycleBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
rbd.setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS);
lbf.registerBeanDefinition("test", rbd);
lbf.addBeanPostProcessor(new LifecycleBean.PostProcessor());
lbf.freezeConfiguration();
StopWatch sw = new StopWatch();
sw.start("prototype");
for (int i = 0; i < 100000; i++) {
lbf.getBean("test");
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 3000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void prototypeCreationWithConstructorArgumentsIsFastEnough() {
Assume.notLogging(factoryLog);
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
rbd.getConstructorArgumentValues().addGenericArgumentValue("juergen");
rbd.getConstructorArgumentValues().addGenericArgumentValue("99");
lbf.registerBeanDefinition("test", rbd);
lbf.freezeConfiguration();
StopWatch sw = new StopWatch();
sw.start("prototype");
for (int i = 0; i < 100000; i++) {
TestBean tb = (TestBean) lbf.getBean("test");
assertThat(tb.getName()).isEqualTo("juergen");
assertThat(tb.getAge()).isEqualTo(99);
}
sw.stop();
assertThat(sw.getTotalTimeMillis() < 3000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void prototypeCreationWithResolvedConstructorArgumentsIsFastEnough() {
Assume.notLogging(factoryLog);
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
rbd.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("spouse"));
lbf.registerBeanDefinition("test", rbd);
lbf.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class));
lbf.freezeConfiguration();
TestBean spouse = (TestBean) lbf.getBean("spouse");
StopWatch sw = new StopWatch();
sw.start("prototype");
for (int i = 0; i < 100000; i++) {
TestBean tb = (TestBean) lbf.getBean("test");
assertThat(tb.getSpouse()).isSameAs(spouse);
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 4000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void prototypeCreationWithPropertiesIsFastEnough() {
Assume.notLogging(factoryLog);
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
rbd.getPropertyValues().add("name", "juergen");
rbd.getPropertyValues().add("age", "99");
lbf.registerBeanDefinition("test", rbd);
lbf.freezeConfiguration();
StopWatch sw = new StopWatch();
sw.start("prototype");
for (int i = 0; i < 100000; i++) {
TestBean tb = (TestBean) lbf.getBean("test");
assertThat(tb.getName()).isEqualTo("juergen");
assertThat(tb.getAge()).isEqualTo(99);
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 4000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void prototypeCreationWithResolvedPropertiesIsFastEnough() {
Assume.notLogging(factoryLog);
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
rbd.getPropertyValues().add("spouse", new RuntimeBeanReference("spouse"));
lbf.registerBeanDefinition("test", rbd);
lbf.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class));
lbf.freezeConfiguration();
TestBean spouse = (TestBean) lbf.getBean("spouse");
StopWatch sw = new StopWatch();
sw.start("prototype");
for (int i = 0; i < 100000; i++) {
TestBean tb = (TestBean) lbf.getBean("test");
assertThat(tb.getSpouse()).isSameAs(spouse);
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 4000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void singletonLookupByNameIsFastEnough() {
Assume.notLogging(factoryLog);
lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class));
lbf.freezeConfiguration();
StopWatch sw = new StopWatch();
sw.start("singleton");
for (int i = 0; i < 1000000; i++) {
lbf.getBean("test");
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 1000).as("Singleton lookup took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void singletonLookupByTypeIsFastEnough() {
Assume.notLogging(factoryLog);
lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class));
lbf.freezeConfiguration();
StopWatch sw = new StopWatch();
sw.start("singleton");
for (int i = 0; i < 1000000; i++) {
lbf.getBean(TestBean.class);
}
sw.stop();
// System.out.println(sw.getTotalTimeMillis());
assertThat(sw.getTotalTimeMillis() < 1000).as("Singleton lookup took too long: " + sw.getTotalTimeMillis()).isTrue();
}
@Test
void beanPostProcessorWithWrappedObjectAndDisposableBean() {
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDisposableBean.class);
@@ -2844,55 +2681,6 @@ class DefaultListableBeanFactoryTests {
assertThat(holder.getNonPublicEnum()).isEqualTo(NonPublicEnum.VALUE_1);
}
/**
* Test that by-type bean lookup caching is working effectively by searching for a
* bean of type B 10K times within a container having 1K additional beans of type A.
* Prior to by-type caching, each bean lookup would traverse the entire container
* (all 1001 beans), performing expensive assignability checks, etc. Now these
* operations are necessary only once, providing a dramatic performance improvement.
* On load-free modern hardware (e.g. an 8-core MPB), this method should complete well
* under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same
* hardware the method will take ~13000 ms. See SPR-6870.
*/
@Test
@Timeout(1)
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void byTypeLookupIsFastEnough() {
for (int i = 0; i < 1000; i++) {
lbf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class));
}
lbf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
lbf.freezeConfiguration();
for (int i = 0; i < 10000; i++) {
lbf.getBean(B.class);
}
}
@Test
@Timeout(1)
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void registrationOfManyBeanDefinitionsIsFastEnough() {
lbf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
// lbf.getBean("b");
for (int i = 0; i < 100000; i++) {
lbf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class));
}
}
@Test
@Timeout(1)
@EnabledForTestGroups(TestGroup.PERFORMANCE)
void registrationOfManySingletonsIsFastEnough() {
lbf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
// lbf.getBean("b");
for (int i = 0; i < 100000; i++) {
lbf.registerSingleton("a" + i, new A());
}
}
@SuppressWarnings("deprecation")
private int registerBeanDefinitions(Properties p) {
@@ -2905,11 +2693,6 @@ class DefaultListableBeanFactoryTests {
}
static class A { }
static class B { }
public static class NoDependencies {
private NoDependencies() {

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="bean1" class="org.springframework.beans.factory.ConcurrentBeanFactoryTests$ConcurrentBean"
scope="prototype">
<property name="date" value="2004/08/08"/>
</bean>
<bean id="bean2" class="org.springframework.beans.factory.ConcurrentBeanFactoryTests$ConcurrentBean"
scope="prototype">
<property name="date" value="2000/02/02"/>
</bean>
</beans>