Add theoretical performance optimization for Scanner

Useless for now until I find a way of running transformers
before mods load
This commit is contained in:
embeddedt 2023-01-22 18:56:43 -05:00
parent 8cdc425d7d
commit ad5fcf44e5
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
5 changed files with 199 additions and 6 deletions

View File

@ -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<String, ILaunchPluginService> 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;

View File

@ -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);

View File

@ -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<ModFileScanData> 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<ModFileScanData> cir) {
CachedScanner.saveCachedDataForFile(this.fileToScan, cir.getReturnValue());
}
}

View File

@ -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<String> interfacesTypeDesc;
}
static class SerializedAnnotationData implements Serializable {
public String annotationTypeDesc;
public ElementType targetTypeDesc;
public String classTypeDesc;
public String memberName;
public Map<String, Object> 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<SerializedClassData> classDataList = (ArrayList<SerializedClassData>)stream.readObject();
Set<ModFileScanData.ClassData> 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<SerializedAnnotationData> annotationDataList = (ArrayList<SerializedAnnotationData>)stream.readObject();
Set<ModFileScanData.AnnotationData> 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<SerializedClassData> 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<Type>)classDataInterfacesField.get(data)).stream().map(Type::getDescriptor).collect(Collectors.toCollection(ArrayList::new));
serializedClassDataList.add(sData);
}
stream.writeObject(serializedClassDataList);
ArrayList<SerializedAnnotationData> 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();
}
}
}

View File

@ -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;
}
}