Skip to content

Commit 98a4ff8

Browse files
committed
Load classpath lazily and cache zip files
This brings total time needed to decompile Minecraft 1.18.2 from ~38 seconds, to ~25 seconds on my machine. Decomplication can be quite easily performed with only 2GB of memory allocated to the JVM, when previously 4+GB was required.
1 parent fa78eff commit 98a4ff8

3 files changed

Lines changed: 2439 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: zml <zml@stellardrift.ca>
3+
Date: Wed, 23 Mar 2022 21:30:37 -0700
4+
Subject: [PATCH] Quick & dirty run time logging
5+
6+
7+
diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java
8+
index bd3535557b78014919d34c3ee5d17c34ea523e2d..3e939e4e5a39fce84d694a02e2e53ec486e50ba5 100644
9+
--- a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java
10+
+++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java
11+
@@ -25,6 +25,7 @@ import java.util.zip.ZipOutputStream;
12+
public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver {
13+
@SuppressWarnings("UseOfSystemOutOrSystemErr")
14+
public static void main(String[] args) {
15+
+ final long startTime = System.currentTimeMillis();
16+
List<String> params = new ArrayList<String>();
17+
for (int x = 0; x < args.length; x++) {
18+
if (args[x].startsWith("-cfg")) {
19+
@@ -124,6 +125,8 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver {
20+
}
21+
22+
decompiler.decompileContext();
23+
+ final long endTime = System.currentTimeMillis();
24+
+ logger.writeMessage("Decompliation complete in " + (endTime - startTime) / 1000 + "s", IFernflowerLogger.Severity.WARN);
25+
}
26+
27+
@SuppressWarnings("UseOfSystemOutOrSystemErr")
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: zml <zml@stellardrift.ca>
3+
Date: Wed, 23 Mar 2022 21:41:32 -0700
4+
Subject: [PATCH] Cache zip file instances and source class data
5+
6+
7+
diff --git a/src/org/jetbrains/java/decompiler/main/Fernflower.java b/src/org/jetbrains/java/decompiler/main/Fernflower.java
8+
index 22cb517d3ab2901bb7d88e38802eb2825584b78d..c62bf0d68c62e0a982104172ce4f78ae4a6f233b 100644
9+
--- a/src/org/jetbrains/java/decompiler/main/Fernflower.java
10+
+++ b/src/org/jetbrains/java/decompiler/main/Fernflower.java
11+
@@ -125,6 +125,7 @@ public class Fernflower implements IDecompiledData {
12+
13+
public void clearContext() {
14+
DecompilerContext.setCurrentContext(null);
15+
+ structContext.clear();
16+
}
17+
18+
@Override
19+
diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java
20+
index 3e939e4e5a39fce84d694a02e2e53ec486e50ba5..c8acfeb637ea18fb06840f696c3b444202f02d64 100644
21+
--- a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java
22+
+++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java
23+
@@ -8,6 +8,7 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
24+
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
25+
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
26+
import org.jetbrains.java.decompiler.util.InterpreterUtil;
27+
+import org.jetbrains.java.decompiler.util.ZipFileCache;
28+
29+
import java.io.*;
30+
import java.nio.charset.StandardCharsets;
31+
@@ -148,6 +149,7 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver {
32+
private final Fernflower engine;
33+
private final Map<String, ZipOutputStream> mapArchiveStreams = new HashMap<>();
34+
private final Map<String, Set<String>> mapArchiveEntries = new HashMap<>();
35+
+ private final ZipFileCache openZips = new ZipFileCache();
36+
37+
protected ConsoleDecompiler(File destination, Map<String, Object> options, IFernflowerLogger logger) {
38+
root = destination;
39+
@@ -199,16 +201,15 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver {
40+
41+
@Override
42+
public byte[] getBytecode(String externalPath, String internalPath) throws IOException {
43+
- File file = new File(externalPath);
44+
if (internalPath == null) {
45+
+ File file = new File(externalPath);
46+
return InterpreterUtil.getBytes(file);
47+
}
48+
else {
49+
- try (ZipFile archive = new ZipFile(file)) {
50+
- ZipEntry entry = archive.getEntry(internalPath);
51+
- if (entry == null) throw new IOException("Entry not found: " + internalPath);
52+
- return InterpreterUtil.getBytes(archive, entry);
53+
- }
54+
+ final ZipFile archive = this.openZips.get(externalPath);
55+
+ ZipEntry entry = archive.getEntry(internalPath);
56+
+ if (entry == null) throw new IOException("Entry not found: " + internalPath);
57+
+ return InterpreterUtil.getBytes(archive, entry);
58+
}
59+
}
60+
61+
@@ -279,7 +280,8 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver {
62+
return;
63+
}
64+
65+
- try (ZipFile srcArchive = new ZipFile(new File(source))) {
66+
+ try {
67+
+ ZipFile srcArchive = this.openZips.get(source);
68+
ZipEntry entry = srcArchive.getEntry(entryName);
69+
if (entry != null) {
70+
try (InputStream in = srcArchive.getInputStream(entry)) {
71+
@@ -346,4 +348,9 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver {
72+
DecompilerContext.getLogger().writeMessage("Cannot close " + file, IFernflowerLogger.Severity.WARN);
73+
}
74+
}
75+
+
76+
+ @Override
77+
+ public void close() throws IOException {
78+
+ this.openZips.close();
79+
+ }
80+
}
81+
diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/SingleFileSaver.java b/src/org/jetbrains/java/decompiler/main/decompiler/SingleFileSaver.java
82+
index 8e37643c54c6e961c317d9bc2c1ed5556e0b4179..d12e1a684a58f43194776b61d1238c9c863262b4 100644
83+
--- a/src/org/jetbrains/java/decompiler/main/decompiler/SingleFileSaver.java
84+
+++ b/src/org/jetbrains/java/decompiler/main/decompiler/SingleFileSaver.java
85+
@@ -17,11 +17,13 @@ import org.jetbrains.java.decompiler.main.DecompilerContext;
86+
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
87+
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
88+
import org.jetbrains.java.decompiler.util.InterpreterUtil;
89+
+import org.jetbrains.java.decompiler.util.ZipFileCache;
90+
91+
public class SingleFileSaver implements IResultSaver {
92+
private final File target;
93+
private ZipOutputStream output;
94+
private Set<String> entries = new HashSet<>();
95+
+ private final ZipFileCache openZips = new ZipFileCache();
96+
97+
public SingleFileSaver(File target) {
98+
this.target = target;
99+
@@ -65,7 +67,8 @@ public class SingleFileSaver implements IResultSaver {
100+
if (!checkEntry(entryName))
101+
return;
102+
103+
- try (ZipFile srcArchive = new ZipFile(new File(source))) {
104+
+ try {
105+
+ final ZipFile srcArchive = this.openZips.get(source);
106+
ZipEntry entry = srcArchive.getEntry(entryName);
107+
if (entry != null) {
108+
try (InputStream in = srcArchive.getInputStream(entry)) {
109+
@@ -115,6 +118,11 @@ public class SingleFileSaver implements IResultSaver {
110+
}
111+
}
112+
113+
+ @Override
114+
+ public void close() throws IOException {
115+
+ this.openZips.close();
116+
+ }
117+
+
118+
private boolean checkEntry(String entryName) {
119+
boolean added = entries.add(entryName);
120+
if (!added) {
121+
diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ThreadSafeResultSaver.java b/src/org/jetbrains/java/decompiler/main/decompiler/ThreadSafeResultSaver.java
122+
index 585494c81bda9c2bde5ea284fb8851df3c1b072b..565949b9782abfbb9303e86b59cca0986a9149df 100644
123+
--- a/src/org/jetbrains/java/decompiler/main/decompiler/ThreadSafeResultSaver.java
124+
+++ b/src/org/jetbrains/java/decompiler/main/decompiler/ThreadSafeResultSaver.java
125+
@@ -5,13 +5,14 @@ import org.jetbrains.java.decompiler.main.DecompilerContext;
126+
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
127+
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
128+
import org.jetbrains.java.decompiler.util.InterpreterUtil;
129+
+import org.jetbrains.java.decompiler.util.ZipFileCache;
130+
131+
import java.io.*;
132+
import java.nio.charset.StandardCharsets;
133+
-import java.util.HashMap;
134+
import java.util.HashSet;
135+
import java.util.Map;
136+
import java.util.Set;
137+
+import java.util.concurrent.ConcurrentHashMap;
138+
import java.util.concurrent.ExecutionException;
139+
import java.util.concurrent.ExecutorService;
140+
import java.util.concurrent.Executors;
141+
@@ -27,11 +28,11 @@ import java.util.zip.ZipOutputStream;
142+
*/
143+
//TODO, Split off default impl inside ConsoleDecompiler and make this extend that.
144+
public class ThreadSafeResultSaver implements IResultSaver {
145+
-
146+
- private final Map<String, ArchiveContext> archiveContexts = new HashMap<>();
147+
+ private final Map<String, ArchiveContext> archiveContexts = new ConcurrentHashMap<>();
148+
private final File target;
149+
private final boolean archiveMode;//Latch for Archive mode.
150+
private ArchiveContext singeArchiveCtx;
151+
+ private final ZipFileCache sources = new ZipFileCache();
152+
153+
public ThreadSafeResultSaver(File target) {
154+
this.target = target;
155+
@@ -88,7 +89,8 @@ public class ThreadSafeResultSaver implements IResultSaver {
156+
if (!ctx.addEntry(entryName)) {
157+
return;
158+
}
159+
- try (ZipFile srcArchive = new ZipFile(new File(source))) {
160+
+ try {
161+
+ final ZipFile srcArchive = this.sources.get(source);
162+
ZipEntry entry = srcArchive.getEntry(entryName);
163+
if (entry != null) {
164+
try (InputStream in = srcArchive.getInputStream(entry)) {
165+
@@ -204,6 +206,20 @@ public class ThreadSafeResultSaver implements IResultSaver {
166+
}
167+
}
168+
169+
+ @Override
170+
+ public void close() throws IOException {
171+
+ if (!this.archiveContexts.isEmpty()) {
172+
+ for (final Map.Entry<String, ArchiveContext> entry : this.archiveContexts.entrySet()) {
173+
+ DecompilerContext.getLogger().writeMessage("Unclosed archive detected at end of run in " + entry.getKey(), IFernflowerLogger.Severity.ERROR);
174+
+ entry.getValue().executor.shutdown();
175+
+ entry.getValue().stream.close();
176+
+ }
177+
+ this.archiveContexts.clear();
178+
+ }
179+
+
180+
+ this.sources.close();
181+
+ }
182+
+
183+
private String getAbsolutePath(String path) {
184+
return new File(target, path).getAbsolutePath();
185+
}
186+
diff --git a/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java b/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java
187+
index e16c60301264feaeaabfaf281495807d888456db..eb2d5a7a09acca7a0fc101c6d31c364867ea2eb8 100644
188+
--- a/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java
189+
+++ b/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java
190+
@@ -1,11 +1,12 @@
191+
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
192+
package org.jetbrains.java.decompiler.main.extern;
193+
194+
+import java.io.IOException;
195+
import java.nio.ByteBuffer;
196+
import java.nio.ByteOrder;
197+
import java.util.jar.Manifest;
198+
199+
-public interface IResultSaver {
200+
+public interface IResultSaver extends AutoCloseable {
201+
void saveFolder(String path);
202+
203+
void copyFile(String source, String path, String entryName);
204+
@@ -28,6 +29,9 @@ public interface IResultSaver {
205+
}
206+
207+
void closeArchive(String path, String archiveName);
208+
+
209+
+ @Override
210+
+ default void close() throws IOException {}
211+
212+
default byte[] getCodeLineData(int[] mappings) {
213+
if (mappings == null || mappings.length == 0) {
214+
diff --git a/src/org/jetbrains/java/decompiler/struct/ContextUnit.java b/src/org/jetbrains/java/decompiler/struct/ContextUnit.java
215+
index 5e119e5030efab2bccc1bd96601075c081056339..920932069cc12c2d55842559ba0060be3f227262 100644
216+
--- a/src/org/jetbrains/java/decompiler/struct/ContextUnit.java
217+
+++ b/src/org/jetbrains/java/decompiler/struct/ContextUnit.java
218+
@@ -189,12 +189,9 @@ public class ContextUnit {
219+
}
220+
221+
//Ask the executor to shutdown
222+
- executor.shutdown();
223+
waitForAll(futures);
224+
futures.clear();
225+
226+
- executor = Executors.newFixedThreadPool(threads);
227+
-
228+
// classes
229+
for (ClassContext clCtx : toProcess) {
230+
if (clCtx.shouldContinue) {
231+
diff --git a/src/org/jetbrains/java/decompiler/struct/StructContext.java b/src/org/jetbrains/java/decompiler/struct/StructContext.java
232+
index 5aa59c4d71dc73bbc8875879ac4c90ab030d6920..a3cec5d4e0097c95d596c924e49fe21b6423ef20 100644
233+
--- a/src/org/jetbrains/java/decompiler/struct/StructContext.java
234+
+++ b/src/org/jetbrains/java/decompiler/struct/StructContext.java
235+
@@ -3,6 +3,7 @@ package org.jetbrains.java.decompiler.struct;
236+
237+
import org.jetbrains.java.decompiler.main.DecompilerContext;
238+
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
239+
+import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
240+
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger.Severity;
241+
import org.jetbrains.java.decompiler.struct.gen.generics.GenericMain;
242+
import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor;
243+
@@ -153,7 +154,7 @@ public class StructContext {
244+
StructClass cl = StructClass.create(new DataInputFullStream(bytes), isOwn, loader);
245+
classes.put(cl.qualifiedName, cl);
246+
unit.addClass(cl, name);
247+
- loader.addClassLink(cl.qualifiedName, new LazyLoader.Link(file.getAbsolutePath(), name));
248+
+ loader.addClassLink(cl.qualifiedName, new LazyLoader.Link(file.getAbsolutePath(), name, bytes));
249+
}
250+
else {
251+
unit.addOtherEntry(file.getAbsolutePath(), name);
252+
@@ -246,4 +247,13 @@ public class StructContext {
253+
List<String> params = this.abstractNames.get(className + ' ' + methodName + ' ' + descriptor);
254+
return params != null && index < params.size() ? params.get(index) : _default;
255+
}
256+
+
257+
+ public void clear() {
258+
+ try {
259+
+ this.saver.close();
260+
+ } catch (final IOException ex) {
261+
+ DecompilerContext.getLogger().writeMessage("Failed to close out result saver", IFernflowerLogger.Severity.ERROR, ex);
262+
+ }
263+
+ }
264+
+
265+
}
266+
diff --git a/src/org/jetbrains/java/decompiler/util/ZipFileCache.java b/src/org/jetbrains/java/decompiler/util/ZipFileCache.java
267+
new file mode 100644
268+
index 0000000000000000000000000000000000000000..92ebb33e213d4a9da802284f77df25b0cebbfa4f
269+
--- /dev/null
270+
+++ b/src/org/jetbrains/java/decompiler/util/ZipFileCache.java
271+
@@ -0,0 +1,43 @@
272+
+package org.jetbrains.java.decompiler.util;
273+
+
274+
+import java.io.File;
275+
+import java.io.IOException;
276+
+import java.io.UncheckedIOException;
277+
+import java.util.Map;
278+
+import java.util.concurrent.ConcurrentHashMap;
279+
+import java.util.zip.ZipFile;
280+
+
281+
+public final class ZipFileCache implements AutoCloseable {
282+
+ private final Map<String, ZipFile> files = new ConcurrentHashMap<>();
283+
+
284+
+ public ZipFile get(final String path) throws IOException {
285+
+ try {
286+
+ return this.files.computeIfAbsent(path, pth -> {
287+
+ try {
288+
+ return new ZipFile(new File(pth));
289+
+ } catch (final IOException ex) {
290+
+ throw new UncheckedIOException(ex);
291+
+ }
292+
+ });
293+
+ } catch (final UncheckedIOException ex) {
294+
+ throw ex.getCause();
295+
+ }
296+
+ }
297+
+
298+
+ @Override
299+
+ public void close() throws IOException {
300+
+ IOException failure = null;
301+
+ for (final Map.Entry<String, ZipFile> entry : this.files.entrySet()) {
302+
+ try {
303+
+ entry.getValue().close();
304+
+ } catch (final IOException ex) {
305+
+ if (failure == null) {
306+
+ failure = ex;
307+
+ } else {
308+
+ failure.addSuppressed(ex);
309+
+ }
310+
+ }
311+
+ }
312+
+ this.files.clear();
313+
+ }
314+
+}
315+
diff --git a/test/org/jetbrains/java/decompiler/DecompilerTestFixture.java b/test/org/jetbrains/java/decompiler/DecompilerTestFixture.java
316+
index 7d29c198014cbfee8092fd31461208ce58e7f775..9648d9dcb68376e43611ef659572ec361e235c9c 100644
317+
--- a/test/org/jetbrains/java/decompiler/DecompilerTestFixture.java
318+
+++ b/test/org/jetbrains/java/decompiler/DecompilerTestFixture.java
319+
@@ -60,7 +60,7 @@ public class DecompilerTestFixture {
320+
if (tempDir != null && cleanup) {
321+
delete(tempDir);
322+
}
323+
- decompiler.close();
324+
+ decompiler.clear();
325+
}
326+
327+
public File getTestDataDir() {
328+
@@ -78,11 +78,11 @@ public class DecompilerTestFixture {
329+
public ConsoleDecompiler getDecompiler() {
330+
return decompiler;
331+
}
332+
-
333+
+
334+
public void setCleanup(boolean value) {
335+
this.cleanup = value;
336+
}
337+
-
338+
+
339+
public boolean getCleanup() {
340+
return cleanup;
341+
}
342+
@@ -149,7 +149,7 @@ public class DecompilerTestFixture {
343+
}
344+
}
345+
346+
- void close() {
347+
+ void clear() {
348+
for (ZipFile file : zipFiles.values()) {
349+
try {
350+
file.close();

0 commit comments

Comments
 (0)