Skip to content

Commit 8c8f100

Browse files
committed
test: add block read/write benchmark
1 parent fdf4886 commit 8c8f100

1 file changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/*-
2+
* #%L
3+
* Not HDF5
4+
* %%
5+
* Copyright (C) 2017 - 2025 Stephan Saalfeld
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package org.janelia.saalfeldlab.n5.universe.benchmarks;
30+
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.nio.file.FileSystems;
34+
import java.nio.file.Files;
35+
import java.nio.file.Paths;
36+
import java.util.ArrayList;
37+
import java.util.Arrays;
38+
import java.util.Random;
39+
import java.util.concurrent.TimeUnit;
40+
41+
import org.janelia.saalfeldlab.n5.Compression;
42+
import org.janelia.saalfeldlab.n5.DataBlock;
43+
import org.janelia.saalfeldlab.n5.DataType;
44+
import org.janelia.saalfeldlab.n5.DatasetAttributes;
45+
import org.janelia.saalfeldlab.n5.FileSystemKeyValueAccess;
46+
import org.janelia.saalfeldlab.n5.GzipCompression;
47+
import org.janelia.saalfeldlab.n5.Lz4Compression;
48+
import org.janelia.saalfeldlab.n5.N5KeyValueWriter;
49+
import org.janelia.saalfeldlab.n5.N5Writer;
50+
import org.janelia.saalfeldlab.n5.RawCompression;
51+
import org.janelia.saalfeldlab.n5.XzCompression;
52+
import org.janelia.saalfeldlab.n5.blosc.BloscCompression;
53+
import org.janelia.scicomp.n5.zstandard.ZstandardCompression;
54+
import org.openjdk.jmh.annotations.Benchmark;
55+
import org.openjdk.jmh.annotations.BenchmarkMode;
56+
import org.openjdk.jmh.annotations.Fork;
57+
import org.openjdk.jmh.annotations.Level;
58+
import org.openjdk.jmh.annotations.Measurement;
59+
import org.openjdk.jmh.annotations.Mode;
60+
import org.openjdk.jmh.annotations.OutputTimeUnit;
61+
import org.openjdk.jmh.annotations.Param;
62+
import org.openjdk.jmh.annotations.Scope;
63+
import org.openjdk.jmh.annotations.Setup;
64+
import org.openjdk.jmh.annotations.State;
65+
import org.openjdk.jmh.annotations.TearDown;
66+
import org.openjdk.jmh.annotations.Warmup;
67+
import org.openjdk.jmh.infra.Blackhole;
68+
import org.openjdk.jmh.runner.Runner;
69+
import org.openjdk.jmh.runner.RunnerException;
70+
import org.openjdk.jmh.runner.options.Options;
71+
import org.openjdk.jmh.runner.options.OptionsBuilder;
72+
73+
import com.google.gson.GsonBuilder;
74+
75+
@State(Scope.Benchmark)
76+
@Warmup(iterations = 50, time = 100, timeUnit = TimeUnit.MICROSECONDS)
77+
@Measurement(iterations = 500, time = 100, timeUnit = TimeUnit.MICROSECONDS)
78+
@BenchmarkMode(Mode.AverageTime)
79+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
80+
@Fork(1)
81+
public class BlockReadWriteBenchmarks {
82+
83+
static final String GZIP_COMPRESSION = "gzip";
84+
static final String RAW_COMPRESSION = "raw";
85+
static final String LZ4_COMPRESSION = "lz4";
86+
static final String XZ_COMPRESSION = "xz";
87+
static final String BLOSC_COMPRESSION = "blosc";
88+
static final String ZSTD_COMPRESSION = "zstd";
89+
90+
Random random = new Random(7777);
91+
92+
final String writeGroup = "writeGroup";
93+
final String readGroup = "readGroup";
94+
95+
N5Writer n5;
96+
DatasetAttributes dsetAttrs;
97+
ArrayList<DataBlock<?>> blocks;
98+
99+
//@Param( value = { RAW_COMPRESSION, GZIP_COMPRESSION, LZ4_COMPRESSION, XZ_COMPRESSION, BLOSC_COMPRESSION, ZSTD_COMPRESSION } )
100+
@Param( value = { RAW_COMPRESSION } )
101+
protected String compressionType;
102+
103+
// @Param( value = { "int8", "int16", "int32", "int64", "float32", "float64" } )
104+
105+
@Param( value = { "int8"} )
106+
protected String dataType;
107+
108+
@Param( value = { "3" } )
109+
protected int numDimensions;
110+
111+
@Param( value = { "64" } )
112+
protected int blockDim;
113+
114+
@Param( value = { "5" } )
115+
protected int numBlocks;
116+
117+
public static void main( String[] args ) throws RunnerException {
118+
119+
final Options options = new OptionsBuilder().include( BlockReadWriteBenchmarks.class.getSimpleName() + "\\." ).build();
120+
new Runner(options).run();
121+
}
122+
123+
@TearDown(Level.Trial)
124+
public void teardown() {
125+
File d = new File(n5.getURI());
126+
n5.remove();
127+
d.delete();
128+
}
129+
130+
@Setup(Level.Trial)
131+
public void setup() {
132+
133+
File tmpDir;
134+
try {
135+
tmpDir = Files.createTempDirectory("n5-blockWriteBenchmark-").toFile();
136+
FileSystemKeyValueAccess kva = new FileSystemKeyValueAccess(FileSystems.getDefault());
137+
n5 = new N5KeyValueWriter(kva, tmpDir.getAbsolutePath(), new GsonBuilder(), true);
138+
139+
int[] blockSize = new int[numDimensions];
140+
Arrays.fill(blockSize, blockDim);
141+
142+
long[] dims = new long[numDimensions];
143+
Arrays.fill(dims, blockDim);
144+
dims[0] = blockDim * numBlocks;
145+
146+
DataType dtype = DataType.fromString(dataType);
147+
148+
// write blocks into the readGroup so that they can be read during the benchmark
149+
final Compression compression = getCompression(compressionType);
150+
dsetAttrs = new DatasetAttributes(dims, blockSize, dtype, compression);
151+
n5.createDataset(readGroup, dsetAttrs);
152+
blocks = new ArrayList<>();
153+
final long[] p = new long[numDimensions];
154+
for (int i = 0; i < numBlocks; i++) {
155+
p[0] = i;
156+
157+
DataBlock<?> blk = dtype.createDataBlock(blockSize, p);
158+
fillBlock(dtype, blk);
159+
blocks.add(blk);
160+
161+
// write data into the read group
162+
n5.writeBlock(readGroup, dsetAttrs, blk);
163+
}
164+
165+
} catch (final IOException e) {
166+
e.printStackTrace();
167+
}
168+
169+
}
170+
171+
// @Benchmark
172+
// public void writeBenchmark() throws IOException {
173+
//
174+
// blocks.forEach(blk -> {
175+
// n5.writeBlock(writeGroup, dsetAttrs, blk);
176+
// });
177+
// }
178+
//
179+
// @Benchmark
180+
// public void readBenchmark(Blackhole hole) throws IOException {
181+
//
182+
// final long[] p = new long[numDimensions];
183+
// for (int i = 0; i < numBlocks; i++) {
184+
// p[0] = i;
185+
// hole.consume(n5.readBlock(readGroup, dsetAttrs, p));
186+
// }
187+
// }
188+
189+
@Benchmark
190+
public void fileRead(Blackhole hole) throws IOException {
191+
192+
final byte[] bytes = Files.readAllBytes(Paths.get("/home/john/tmp/mri-stack.tif"));
193+
hole.consume(bytes);
194+
}
195+
196+
private void fillBlock(DataType dtype, DataBlock<?> blk) {
197+
198+
switch (dtype) {
199+
case INT32:
200+
fill((int[])blk.getData());
201+
break;
202+
case FLOAT32:
203+
fill((float[])blk.getData());
204+
break;
205+
case FLOAT64:
206+
fill((double[])blk.getData());
207+
break;
208+
case INT16:
209+
fill((short[])blk.getData());
210+
break;
211+
case INT64:
212+
fill((long[])blk.getData());
213+
break;
214+
case INT8:
215+
fill((byte[])blk.getData());
216+
break;
217+
case OBJECT:
218+
break;
219+
case STRING:
220+
break;
221+
case UINT16:
222+
fill((short[])blk.getData());
223+
break;
224+
case UINT32:
225+
fill((int[])blk.getData());
226+
break;
227+
case UINT64:
228+
fill((long[])blk.getData());
229+
break;
230+
case UINT8:
231+
fill((byte[])blk.getData());
232+
break;
233+
default:
234+
break;
235+
}
236+
}
237+
238+
private void fill(short[] arr) {
239+
for (int i = 0; i < arr.length; i++)
240+
arr[i] = (short)random.nextInt();
241+
}
242+
243+
private void fill(int[] arr) {
244+
for (int i = 0; i < arr.length; i++)
245+
arr[i] = random.nextInt();
246+
}
247+
248+
private void fill(long[] arr) {
249+
for (int i = 0; i < arr.length; i++)
250+
arr[i] = random.nextLong();
251+
}
252+
253+
private void fill(float[] arr) {
254+
for (int i = 0; i < arr.length; i++)
255+
arr[i] = random.nextFloat();
256+
}
257+
258+
private void fill(double[] arr) {
259+
for (int i = 0; i < arr.length; i++)
260+
arr[i] = random.nextDouble();
261+
}
262+
263+
private void fill(byte[] arr) {
264+
random.nextBytes(arr);
265+
}
266+
267+
public static Compression getCompression(final String compressionArg) {
268+
269+
switch (compressionArg) {
270+
case GZIP_COMPRESSION:
271+
return new GzipCompression();
272+
case LZ4_COMPRESSION:
273+
return new Lz4Compression();
274+
case XZ_COMPRESSION:
275+
return new XzCompression();
276+
case RAW_COMPRESSION:
277+
return new RawCompression();
278+
case BLOSC_COMPRESSION:
279+
return new BloscCompression();
280+
case ZSTD_COMPRESSION:
281+
return new ZstandardCompression();
282+
default:
283+
return new RawCompression();
284+
}
285+
}
286+
287+
}

0 commit comments

Comments
 (0)