Skip to content

Commit 4a79cda

Browse files
author
Gary Gregory
committed
Add benchmarks for IOUtils.contentEquals(Reader, Reader).
This is a follow up regarding the work in PR 118 by XenoAmess.
1 parent 303f89d commit 4a79cda

2 files changed

Lines changed: 273 additions & 35 deletions

File tree

src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsInputStreamsBenchmark.java

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@
4242

4343
/**
4444
* Test different implementations of {@link IOUtils#contentEquals(InputStream, InputStream)}.
45-
*
45+
*
4646
* <pre>
47-
* Benchmark Mode Cnt Score Error Units
4847
* IOUtilsContentEqualsInputStreamsBenchmark.testFileCurrent avgt 5 1518342.821 ▒ 201890.705 ns/op
4948
* IOUtilsContentEqualsInputStreamsBenchmark.testFilePr118 avgt 5 1578606.938 ▒ 66980.718 ns/op
5049
* IOUtilsContentEqualsInputStreamsBenchmark.testFileRelease_2_8_0 avgt 5 2439163.068 ▒ 265765.294 ns/op
@@ -148,53 +147,53 @@ public static boolean contentEqualsPr118(final InputStream input1, final InputSt
148147
@Benchmark
149148
public boolean[] testFileCurrent() throws IOException {
150149
final boolean[] res = new boolean[3];
151-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_A);
152-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_B)) {
153-
res[0] = IOUtils.contentEquals(inputStream1, inputStream1);
150+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
151+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_B)) {
152+
res[0] = IOUtils.contentEquals(input1, input1);
154153
}
155-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_A);
156-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_A);) {
157-
res[1] = IOUtils.contentEquals(inputStream1, inputStream2);
154+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
155+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_A);) {
156+
res[1] = IOUtils.contentEquals(input1, input2);
158157
}
159-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_16K_A);
160-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_16K_A_COPY);) {
161-
res[2] = IOUtils.contentEquals(inputStream1, inputStream2);
158+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_16K_A);
159+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_16K_A_COPY);) {
160+
res[2] = IOUtils.contentEquals(input1, input2);
162161
}
163162
return res;
164163
}
165164

166165
@Benchmark
167166
public boolean[] testFilePr118() throws IOException {
168167
final boolean[] res = new boolean[3];
169-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_A);
170-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_B)) {
171-
res[0] = contentEqualsPr118(inputStream1, inputStream1);
168+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
169+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_B)) {
170+
res[0] = contentEqualsPr118(input1, input1);
172171
}
173-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_A);
174-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_A);) {
175-
res[1] = contentEqualsPr118(inputStream1, inputStream2);
172+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
173+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_A)) {
174+
res[1] = contentEqualsPr118(input1, input2);
176175
}
177-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_16K_A);
178-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_16K_A_COPY);) {
179-
res[2] = contentEqualsPr118(inputStream1, inputStream2);
176+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_16K_A);
177+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_16K_A_COPY)) {
178+
res[2] = contentEqualsPr118(input1, input2);
180179
}
181180
return res;
182181
}
183182

184183
@Benchmark
185184
public boolean[] testFileRelease_2_8_0() throws IOException {
186185
final boolean[] res = new boolean[3];
187-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_A);
188-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_B)) {
189-
res[0] = contentEquals_release_2_8_0(inputStream1, inputStream1);
186+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
187+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_B)) {
188+
res[0] = contentEquals_release_2_8_0(input1, input1);
190189
}
191-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_A);
192-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_A);) {
193-
res[1] = contentEquals_release_2_8_0(inputStream1, inputStream2);
190+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
191+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_A);) {
192+
res[1] = contentEquals_release_2_8_0(input1, input2);
194193
}
195-
try (InputStream inputStream1 = this.getClass().getResourceAsStream(TEST_PATH_16K_A);
196-
InputStream inputStream2 = this.getClass().getResourceAsStream(TEST_PATH_16K_A_COPY);) {
197-
res[2] = contentEquals_release_2_8_0(inputStream1, inputStream2);
194+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_16K_A);
195+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_16K_A_COPY)) {
196+
res[2] = contentEquals_release_2_8_0(input1, input2);
198197
}
199198
return res;
200199
}
@@ -215,9 +214,9 @@ public void testStringCurrent(final Blackhole blackhole) throws IOException {
215214
public void testStringPr118(final Blackhole blackhole) throws IOException {
216215
for (int i = 0; i < 5; i++) {
217216
for (int j = 0; j < 5; j++) {
218-
try (InputStream inputStream1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET);
219-
InputStream inputStream2 = IOUtils.toInputStream(STRINGS[j], DEFAULT_CHARSET)) {
220-
blackhole.consume(contentEqualsPr118(inputStream1, inputStream2));
217+
try (InputStream input1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET);
218+
InputStream input2 = IOUtils.toInputStream(STRINGS[j], DEFAULT_CHARSET)) {
219+
blackhole.consume(contentEqualsPr118(input1, input2));
221220
}
222221
}
223222
}
@@ -227,9 +226,9 @@ public void testStringPr118(final Blackhole blackhole) throws IOException {
227226
public void testStringRelease_2_8_0(final Blackhole blackhole) throws IOException {
228227
for (int i = 0; i < 5; i++) {
229228
for (int j = 0; j < 5; j++) {
230-
try (InputStream inputStream1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET);
231-
InputStream inputStream2 = IOUtils.toInputStream(STRINGS[j], DEFAULT_CHARSET)) {
232-
blackhole.consume(contentEquals_release_2_8_0(inputStream1, inputStream2));
229+
try (InputStream input1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET);
230+
InputStream input2 = IOUtils.toInputStream(STRINGS[j], DEFAULT_CHARSET)) {
231+
blackhole.consume(contentEquals_release_2_8_0(input1, input2));
233232
}
234233
}
235234
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.io.jmh;
19+
20+
import static org.apache.commons.io.IOUtils.DEFAULT_BUFFER_SIZE;
21+
import static org.apache.commons.io.IOUtils.EOF;
22+
23+
import java.io.BufferedReader;
24+
import java.io.IOException;
25+
import java.io.InputStream;
26+
import java.io.InputStreamReader;
27+
import java.io.Reader;
28+
import java.io.StringReader;
29+
import java.nio.charset.Charset;
30+
import java.util.concurrent.TimeUnit;
31+
32+
import org.apache.commons.io.IOUtils;
33+
import org.apache.commons.lang3.StringUtils;
34+
import org.openjdk.jmh.annotations.Benchmark;
35+
import org.openjdk.jmh.annotations.BenchmarkMode;
36+
import org.openjdk.jmh.annotations.Fork;
37+
import org.openjdk.jmh.annotations.Measurement;
38+
import org.openjdk.jmh.annotations.Mode;
39+
import org.openjdk.jmh.annotations.OutputTimeUnit;
40+
import org.openjdk.jmh.annotations.Scope;
41+
import org.openjdk.jmh.annotations.State;
42+
import org.openjdk.jmh.annotations.Warmup;
43+
import org.openjdk.jmh.infra.Blackhole;
44+
45+
/**
46+
* Test different implementations of {@link IOUtils#contentEquals(InputStream, InputStream)}.
47+
*
48+
* <pre>
49+
* IOUtilsContentEqualsReadersBenchmark.testFileCurrent avgt 5 1788514.748 ▒ 725103.413 ns/op
50+
* IOUtilsContentEqualsReadersBenchmark.testFilePr118 avgt 5 1786345.992 ▒ 636923.719 ns/op
51+
* IOUtilsContentEqualsReadersBenchmark.testFileRelease_2_8_0 avgt 5 2113268.805 ▒ 670263.275 ns/op
52+
* IOUtilsContentEqualsReadersBenchmark.testStringCurrent avgt 5 4773229733.333 ▒ 198489493.236 ns/op
53+
* IOUtilsContentEqualsReadersBenchmark.testStringPr118 avgt 5 1158805764.444 ▒ 49390259.195 ns/op
54+
* IOUtilsContentEqualsReadersBenchmark.testStringRelease_2_8_0 avgt 5 4692235120.000 ▒ 315543521.826 ns/op
55+
* </pre>
56+
*/
57+
@BenchmarkMode(Mode.AverageTime)
58+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
59+
@State(Scope.Thread)
60+
@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS)
61+
@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS)
62+
@Fork(value = 1, jvmArgs = {"-server"})
63+
public class IOUtilsContentEqualsReadersBenchmark {
64+
65+
private static final String TEST_PATH_A = "/org/apache/commons/io/testfileBOM.xml";
66+
private static final String TEST_PATH_16K_A = "/org/apache/commons/io/abitmorethan16k.txt";
67+
private static final String TEST_PATH_16K_A_COPY = "/org/apache/commons/io/abitmorethan16kcopy.txt";
68+
private static final String TEST_PATH_B = "/org/apache/commons/io/testfileNoBOM.xml";
69+
private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
70+
static String[] STRINGS = new String[5];
71+
72+
static {
73+
STRINGS[0] = StringUtils.repeat("ab", 1 << 24);
74+
STRINGS[1] = STRINGS[0] + 'c';
75+
STRINGS[2] = STRINGS[0] + 'd';
76+
STRINGS[3] = StringUtils.repeat("ab\rab\n", 1 << 24);
77+
STRINGS[4] = StringUtils.repeat("ab\r\nab\r", 1 << 24);
78+
}
79+
80+
static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", 1 << 24) + '\n', 2);
81+
static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", 1 << 24) + '\n', 2);
82+
83+
@SuppressWarnings("resource")
84+
public static boolean contentEquals_release_2_8_0(final Reader input1, final Reader input2) throws IOException {
85+
if (input1 == input2) {
86+
return true;
87+
}
88+
if (input1 == null ^ input2 == null) {
89+
return false;
90+
}
91+
final BufferedReader bufferedInput1 = IOUtils.toBufferedReader(input1);
92+
final BufferedReader bufferedInput2 = IOUtils.toBufferedReader(input2);
93+
94+
int ch = bufferedInput1.read();
95+
while (EOF != ch) {
96+
final int ch2 = bufferedInput2.read();
97+
if (ch != ch2) {
98+
return false;
99+
}
100+
ch = bufferedInput1.read();
101+
}
102+
103+
return bufferedInput2.read() == EOF;
104+
}
105+
106+
public static boolean contentEqualsPr118(final Reader input1, final Reader input2) throws IOException {
107+
if (input1 == input2) {
108+
return true;
109+
}
110+
if (input1 == null || input2 == null) {
111+
return false;
112+
}
113+
114+
final char[] array1 = new char[DEFAULT_BUFFER_SIZE];
115+
final char[] array2 = new char[DEFAULT_BUFFER_SIZE];
116+
int pos1;
117+
int pos2;
118+
int count1;
119+
int count2;
120+
while (true) {
121+
pos1 = 0;
122+
pos2 = 0;
123+
for (int index = 0; index < DEFAULT_BUFFER_SIZE; index++) {
124+
if (pos1 == index) {
125+
do {
126+
count1 = input1.read(array1, pos1, DEFAULT_BUFFER_SIZE - pos1);
127+
} while (count1 == 0);
128+
if (count1 == EOF) {
129+
return pos2 == index && input2.read() == EOF;
130+
}
131+
pos1 += count1;
132+
}
133+
if (pos2 == index) {
134+
do {
135+
count2 = input2.read(array2, pos2, DEFAULT_BUFFER_SIZE - pos2);
136+
} while (count2 == 0);
137+
if (count2 == EOF) {
138+
return pos1 == index && input1.read() == EOF;
139+
}
140+
pos2 += count2;
141+
}
142+
if (array1[index] != array2[index]) {
143+
return false;
144+
}
145+
}
146+
}
147+
}
148+
149+
@Benchmark
150+
public boolean[] testFileCurrent() throws IOException {
151+
final boolean[] res = new boolean[3];
152+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
153+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_B)) {
154+
res[0] = IOUtils.contentEquals(input1, input1);
155+
}
156+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_A);
157+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_A)) {
158+
res[1] = IOUtils.contentEquals(input1, input2);
159+
}
160+
try (InputStream input1 = getClass().getResourceAsStream(TEST_PATH_16K_A);
161+
InputStream input2 = getClass().getResourceAsStream(TEST_PATH_16K_A_COPY)) {
162+
res[2] = IOUtils.contentEquals(input1, input2);
163+
}
164+
return res;
165+
}
166+
167+
@Benchmark
168+
public boolean[] testFilePr118() throws IOException {
169+
final boolean[] res = new boolean[3];
170+
try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A));
171+
Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_B))) {
172+
res[0] = contentEqualsPr118(input1, input1);
173+
}
174+
try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A));
175+
Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A))) {
176+
res[1] = contentEqualsPr118(input1, input2);
177+
}
178+
try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A));
179+
Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A_COPY))) {
180+
res[2] = contentEqualsPr118(input1, input2);
181+
}
182+
return res;
183+
}
184+
185+
@Benchmark
186+
public boolean[] testFileRelease_2_8_0() throws IOException {
187+
final boolean[] res = new boolean[3];
188+
try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A));
189+
Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_B))) {
190+
res[0] = contentEquals_release_2_8_0(input1, input1);
191+
}
192+
try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A));
193+
Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A))) {
194+
res[1] = contentEquals_release_2_8_0(input1, input2);
195+
}
196+
try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A));
197+
Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A_COPY))) {
198+
res[2] = contentEquals_release_2_8_0(input1, input2);
199+
}
200+
return res;
201+
}
202+
203+
@Benchmark
204+
public void testStringCurrent(final Blackhole blackhole) throws IOException {
205+
for (int i = 0; i < 5; i++) {
206+
for (int j = 0; j < 5; j++) {
207+
try (StringReader input1 = new StringReader(STRINGS[i]);
208+
StringReader input2 = new StringReader(STRINGS[j])) {
209+
blackhole.consume(IOUtils.contentEquals(input1, input2));
210+
}
211+
}
212+
}
213+
}
214+
215+
@Benchmark
216+
public void testStringPr118(final Blackhole blackhole) throws IOException {
217+
for (int i = 0; i < 5; i++) {
218+
for (int j = 0; j < 5; j++) {
219+
try (StringReader input1 = new StringReader(STRINGS[i]);
220+
StringReader input2 = new StringReader(STRINGS[j])) {
221+
blackhole.consume(contentEqualsPr118(input1, input2));
222+
}
223+
}
224+
}
225+
}
226+
227+
@Benchmark
228+
public void testStringRelease_2_8_0(final Blackhole blackhole) throws IOException {
229+
for (int i = 0; i < 5; i++) {
230+
for (int j = 0; j < 5; j++) {
231+
try (StringReader input1 = new StringReader(STRINGS[i]);
232+
StringReader input2 = new StringReader(STRINGS[j])) {
233+
blackhole.consume(contentEquals_release_2_8_0(input1, input2));
234+
}
235+
}
236+
}
237+
}
238+
239+
}

0 commit comments

Comments
 (0)