Skip to content

Commit 0325932

Browse files
authored
Avoid extra byte copy inside FileSystemImpl.readFileInternal (#5730)
* Avoid extra byte copy inside FileSystemImpl.readFileInternal Closes #5677 The improvement consists in creating the buffer upfront and using Netty API to read the file channel. Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Added ReadFileBenchmark On my machine, the advantage grows with file size. At 1MB, using the Netty FileChannel method is nearly 2x faster and allocates half as much as the Files.readAllBytes approach. Signed-off-by: Thomas Segismont <tsegismont@gmail.com> --------- Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent e1fdd4c commit 0325932

2 files changed

Lines changed: 90 additions & 33 deletions

File tree

vertx-core/src/main/java/io/vertx/core/file/impl/FileSystemImpl.java

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License 2.0 which is available at
@@ -14,45 +14,21 @@
1414
import io.vertx.codegen.annotations.Nullable;
1515
import io.vertx.core.Future;
1616
import io.vertx.core.buffer.Buffer;
17-
import io.vertx.core.file.AsyncFile;
18-
import io.vertx.core.file.CopyOptions;
19-
import io.vertx.core.file.FileProps;
17+
import io.vertx.core.file.*;
2018
import io.vertx.core.file.FileSystem;
2119
import io.vertx.core.file.FileSystemException;
22-
import io.vertx.core.file.FileSystemProps;
23-
import io.vertx.core.file.OpenOptions;
2420
import io.vertx.core.internal.ContextInternal;
2521
import io.vertx.core.internal.VertxInternal;
22+
import io.vertx.core.internal.buffer.BufferInternal;
2623

2724
import java.io.File;
2825
import java.io.FilenameFilter;
2926
import java.io.IOException;
3027
import java.io.RandomAccessFile;
31-
import java.nio.file.CopyOption;
32-
import java.nio.file.FileAlreadyExistsException;
33-
import java.nio.file.FileStore;
34-
import java.nio.file.FileVisitOption;
35-
import java.nio.file.FileVisitResult;
36-
import java.nio.file.Files;
37-
import java.nio.file.LinkOption;
38-
import java.nio.file.Path;
39-
import java.nio.file.Paths;
40-
import java.nio.file.SimpleFileVisitor;
41-
import java.nio.file.StandardCopyOption;
42-
import java.nio.file.attribute.BasicFileAttributes;
43-
import java.nio.file.attribute.FileAttribute;
44-
import java.nio.file.attribute.GroupPrincipal;
45-
import java.nio.file.attribute.PosixFileAttributeView;
46-
import java.nio.file.attribute.PosixFilePermission;
47-
import java.nio.file.attribute.PosixFilePermissions;
48-
import java.nio.file.attribute.UserPrincipal;
49-
import java.nio.file.attribute.UserPrincipalLookupService;
50-
import java.util.ArrayList;
51-
import java.util.EnumSet;
52-
import java.util.HashSet;
53-
import java.util.List;
54-
import java.util.Objects;
55-
import java.util.Set;
28+
import java.nio.channels.FileChannel;
29+
import java.nio.file.*;
30+
import java.nio.file.attribute.*;
31+
import java.util.*;
5632
import java.util.concurrent.Callable;
5733
import java.util.regex.Pattern;
5834

@@ -876,8 +852,17 @@ private BlockingAction<Buffer> readFileInternal(String path) {
876852
public Buffer perform() {
877853
try {
878854
Path target = resolveFile(path).toPath();
879-
byte[] bytes = Files.readAllBytes(target);
880-
return Buffer.buffer(bytes);
855+
try (FileChannel fc = FileChannel.open(target, StandardOpenOption.READ)) {
856+
long size = fc.size();
857+
if (size > (long) Integer.MAX_VALUE) {
858+
// Throwing OOM as Files#readAllLines would in this case
859+
throw new OutOfMemoryError("File is too big");
860+
}
861+
int len = (int) size;
862+
BufferInternal res = BufferInternal.buffer(len);
863+
res.unwrap().writeBytes(fc, 0, len);
864+
return res;
865+
}
881866
} catch (IOException e) {
882867
throw new FileSystemException(getFileAccessErrorMessage("read", path), e);
883868
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
12+
package io.vertx.benchmarks;
13+
14+
import io.netty.util.ResourceLeakDetector;
15+
import io.vertx.core.buffer.Buffer;
16+
import io.vertx.core.internal.buffer.BufferInternal;
17+
import org.openjdk.jmh.annotations.*;
18+
19+
import java.io.IOException;
20+
import java.nio.channels.FileChannel;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.StandardOpenOption;
24+
import java.util.concurrent.ThreadLocalRandom;
25+
import java.util.concurrent.TimeUnit;
26+
27+
@State(Scope.Thread)
28+
@BenchmarkMode(Mode.AverageTime)
29+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
30+
@Warmup(iterations = 20, time = 1, timeUnit = TimeUnit.SECONDS)
31+
@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
32+
@Fork(5)
33+
public class ReadFileBenchmark {
34+
35+
static {
36+
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
37+
}
38+
39+
@Param({"128", "4096", "65536", "1048576"})
40+
private int fileSize;
41+
42+
private Path tempFile;
43+
44+
@Setup
45+
public void setup() throws IOException {
46+
tempFile = Files.createTempFile("vertx-bench-", ".dat");
47+
byte[] data = new byte[fileSize];
48+
ThreadLocalRandom.current().nextBytes(data);
49+
Files.write(tempFile, data);
50+
}
51+
52+
@TearDown
53+
public void tearDown() throws IOException {
54+
Files.deleteIfExists(tempFile);
55+
}
56+
57+
@Benchmark
58+
public Buffer readAllBytes() throws IOException {
59+
byte[] bytes = Files.readAllBytes(tempFile);
60+
return Buffer.buffer(bytes);
61+
}
62+
63+
@Benchmark
64+
public Buffer fileChannel() throws IOException {
65+
try (FileChannel fc = FileChannel.open(tempFile, StandardOpenOption.READ)) {
66+
int len = (int) fc.size();
67+
BufferInternal res = BufferInternal.buffer(len);
68+
res.unwrap().writeBytes(fc, 0, len);
69+
return res;
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)