From ad5fcf44e50ed1c5bba7e28ed6886b6acbd2bf04 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 22 Jan 2023 18:56:43 -0500 Subject: [PATCH] Add theoretical performance optimization for Scanner Useless for now until I find a way of running transformers before mods load --- .../ModernFixCachingClassTransformer.java | 8 +- .../core/config/ModernFixEarlyConfig.java | 1 + .../mixin/perf/scan_cache/ScannerMixin.java | 29 ++++ .../modernfix/scanning/CachedScanner.java | 157 ++++++++++++++++++ .../embeddedt/modernfix/util/FileUtil.java | 10 ++ 5 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/scan_cache/ScannerMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/scanning/CachedScanner.java create mode 100644 src/main/java/org/embeddedt/modernfix/util/FileUtil.java diff --git a/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java b/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java index dbcd7f9f..35855cab 100644 --- a/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java +++ b/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java @@ -27,6 +27,7 @@ import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.classloading.api.IHashableTransformer; import org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher; import org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher; +import org.embeddedt.modernfix.util.FileUtil; import org.objectweb.asm.Type; import org.spongepowered.asm.launch.MixinLaunchPluginLegacy; @@ -35,7 +36,7 @@ import javax.lang.model.SourceVersion; public class ModernFixCachingClassTransformer extends ClassTransformer { private static final Logger LOGGER = LogManager.getLogger("ModernFixCachingTransformer"); - private final File CLASS_CACHE_FOLDER = childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classCacheV1").toFile()); + private final File CLASS_CACHE_FOLDER = FileUtil.childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classCacheV1").toFile()); private final LaunchPluginHandler pluginHandler; private final Map plugins; private final TransformStore transformStore; @@ -56,11 +57,6 @@ public class ModernFixCachingClassTransformer extends ClassTransformer { } }); - private static File childFile(File file) { - file.getParentFile().mkdirs(); - return file; - } - public ModernFixCachingClassTransformer(TransformStore transformStore, LaunchPluginHandler pluginHandler, TransformingClassLoader transformingClassLoader, TransformerAuditTrail trail) { super(transformStore, pluginHandler, transformingClassLoader, trail); this.transformStore = transformStore; diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index f8ceb873..dc794b9d 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -31,6 +31,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.thread_priorities", true); this.addMixinRule("perf.preload_block_classes", false); this.addMixinRule("perf.sync_executor_sleep", true); + this.addMixinRule("perf.scan_cache", true); this.addMixinRule("perf.parallel_blockstate_cache_rebuild", true); this.addMixinRule("perf.deduplicate_location", true); this.addMixinRule("safety", true); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/scan_cache/ScannerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/scan_cache/ScannerMixin.java new file mode 100644 index 00000000..8029a567 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/scan_cache/ScannerMixin.java @@ -0,0 +1,29 @@ +package org.embeddedt.modernfix.mixin.perf.scan_cache; + +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.moddiscovery.Scanner; +import net.minecraftforge.forgespi.language.ModFileScanData; +import org.embeddedt.modernfix.scanning.CachedScanner; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Scanner.class) +public class ScannerMixin { + @Shadow @Final private ModFile fileToScan; + + @Inject(method = "scan", at = @At(value = "HEAD"), cancellable = true, remap = false) + private void useCachedScanResults(CallbackInfoReturnable cir) { + ModFileScanData cached = CachedScanner.getCachedDataForFile(this.fileToScan); + if(cached != null) + cir.setReturnValue(cached); + } + + @Inject(method = "scan", at = @At(value = "TAIL"), remap = false) + private void saveCachedScanResults(CallbackInfoReturnable cir) { + CachedScanner.saveCachedDataForFile(this.fileToScan, cir.getReturnValue()); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/scanning/CachedScanner.java b/src/main/java/org/embeddedt/modernfix/scanning/CachedScanner.java new file mode 100644 index 00000000..1b1c8300 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/scanning/CachedScanner.java @@ -0,0 +1,157 @@ +package org.embeddedt.modernfix.scanning; + +import cpw.mods.modlauncher.api.LamdbaExceptionUtils; +import net.minecraftforge.fml.loading.FMLPaths; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.forgespi.language.IModLanguageProvider; +import net.minecraftforge.forgespi.language.ModFileScanData; +import org.objectweb.asm.Type; + +import java.io.*; +import java.lang.annotation.ElementType; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class CachedScanner { + private static final Path SCAN_CACHE_FOLDER = FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("scanCacheV1"); + + private static File getCacheFileLocation(ModFile file) { + Path modPath = FMLPaths.MODSDIR.get().relativize(file.getFilePath()); + return SCAN_CACHE_FOLDER.resolve(modPath).toFile(); + } + + private static MessageDigest modFileDigest = LamdbaExceptionUtils.uncheck(() -> MessageDigest.getInstance("SHA-256")); + + private static byte[] computeModFileHash(ModFile file) { + modFileDigest.reset(); + byte[] buffer = new byte[8192]; + int bytesRead; + try(BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(file.getFilePath()))) { + while((bytesRead = stream.read(buffer, 0, buffer.length)) > 0) { + modFileDigest.update(buffer, 0, bytesRead); + } + } catch(IOException e) { + throw new RuntimeException("Failed to read mod file", e); + } + return modFileDigest.digest(); + } + + private static String getCurrentLangVersion(ModFile file) { + IModLanguageProvider loader = file.getLoader(); + String currentLangVersion = loader.getClass().getPackage().getImplementationVersion(); + if(currentLangVersion == null) + currentLangVersion = "[none]"; + return currentLangVersion; + } + + static class SerializedClassData implements Serializable { + public String classTypeDesc; + public String parentTypeDesc; + public ArrayList interfacesTypeDesc; + } + + static class SerializedAnnotationData implements Serializable { + public String annotationTypeDesc; + public ElementType targetTypeDesc; + public String classTypeDesc; + public String memberName; + public Map annotationData; + } + + private static ModFileScanData deserializeScanData(ModFile file, ObjectInputStream stream) throws IOException, ClassNotFoundException { + ModFileScanData result = new ModFileScanData(); + result.addModFileInfo(file.getModFileInfo()); + /* Read all the classes */ + ArrayList classDataList = (ArrayList)stream.readObject(); + Set classDataSet = result.getClasses(); + for(SerializedClassData data : classDataList) { + classDataSet.add(new ModFileScanData.ClassData( + Type.getType(data.classTypeDesc), + Type.getType(data.parentTypeDesc), + data.interfacesTypeDesc.stream().map(Type::getType).collect(Collectors.toSet()))); + } + /* Read all the annotations */ + ArrayList annotationDataList = (ArrayList)stream.readObject(); + Set annotationDataSet = result.getAnnotations(); + for(SerializedAnnotationData data : annotationDataList) { + annotationDataSet.add(new ModFileScanData.AnnotationData( + Type.getType(data.annotationTypeDesc), + data.targetTypeDesc, + Type.getType(data.classTypeDesc), + data.memberName, + data.annotationData + )); + } + return result; + } + + public static ModFileScanData getCachedDataForFile(ModFile file) { + byte[] currentHash = computeModFileHash(file); + String currentLangVersion = getCurrentLangVersion(file); + try(ObjectInputStream stream = new ObjectInputStream(new FileInputStream(getCacheFileLocation(file)))) { + byte[] modFileHash = (byte[])stream.readObject(); + if(!Arrays.equals(modFileHash, currentHash)) { + return null; + } + String langVersion = stream.readUTF(); + if(!langVersion.equals(currentLangVersion)) + return null; + return deserializeScanData(file, stream); + } catch(IOException | ClassNotFoundException e) { + if(!(e instanceof FileNotFoundException)) + e.printStackTrace(); + return null; + } + } + + private static Field classDataTypeField, classDataParentField, classDataInterfacesField; + + public static void saveCachedDataForFile(ModFile file, ModFileScanData scanData) { + try(ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(getCacheFileLocation(file)))) { + stream.writeObject(computeModFileHash(file)); + stream.writeObject(getCurrentLangVersion(file)); + /* serialize scan data */ + ArrayList serializedClassDataList = new ArrayList<>(); + for(ModFileScanData.ClassData data : scanData.getClasses()) { + SerializedClassData sData = new SerializedClassData(); + if(classDataTypeField == null) { + classDataTypeField = ModFileScanData.ClassData.class.getDeclaredField("clazz"); + classDataTypeField.setAccessible(true); + } + sData.classTypeDesc = ((Type)classDataTypeField.get(data)).getDescriptor(); + if(classDataTypeField == null) { + classDataTypeField = ModFileScanData.ClassData.class.getDeclaredField("clazz"); + classDataTypeField.setAccessible(true); + } + sData.classTypeDesc = ((Type)classDataTypeField.get(data)).getDescriptor(); + if(classDataInterfacesField == null) { + classDataInterfacesField = ModFileScanData.ClassData.class.getDeclaredField("interfaces"); + classDataInterfacesField.setAccessible(true); + } + sData.interfacesTypeDesc = ((Set)classDataInterfacesField.get(data)).stream().map(Type::getDescriptor).collect(Collectors.toCollection(ArrayList::new)); + serializedClassDataList.add(sData); + } + stream.writeObject(serializedClassDataList); + ArrayList serializedAnnotationDataList = new ArrayList<>(); + for(ModFileScanData.AnnotationData data : scanData.getAnnotations()) { + SerializedAnnotationData sData = new SerializedAnnotationData(); + sData.annotationTypeDesc = data.getAnnotationType().getDescriptor(); + sData.targetTypeDesc = data.getTargetType(); + sData.classTypeDesc = data.getClassType().getDescriptor(); + sData.memberName = data.getMemberName(); + sData.annotationData = data.getAnnotationData(); + serializedAnnotationDataList.add(sData); +; } + stream.writeObject(serializedAnnotationDataList); + } catch(IOException | ReflectiveOperationException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/FileUtil.java b/src/main/java/org/embeddedt/modernfix/util/FileUtil.java new file mode 100644 index 00000000..7e8dda59 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/FileUtil.java @@ -0,0 +1,10 @@ +package org.embeddedt.modernfix.util; + +import java.io.File; + +public class FileUtil { + public static File childFile(File file) { + file.getParentFile().mkdirs(); + return file; + } +}