Skip to content
Permalink
Browse files

Probable fix for slowness.

This commit caches the native class along with the FQCN, which can be used to
seriously speed up the lookup elsewhere, particularly when using instanceof.
The only exception to this is DynamicEnums, but there are so few of those that
it doesn't cause enough of a performance hit. This speeds up RandomTests by an
order of magnitude, but there were other performance increases that should have
helped some as well (though not an order of magnitude.) The end result is that
this should actually be a bit faster than the original code anyways.
  • Loading branch information...
LadyCailin committed Apr 8, 2019
1 parent 92e71ac commit 3e28991b04fb16e4b68f77d59cd763c5367ea440
@@ -728,7 +728,7 @@ public boolean doesClassExtend(ClassMirror<?> subClass, Class<?> superClass) {
// but there's no way for us to encode T into the generic type of the definition, so we just do this,
// lie to the compiler, and go about our merry way. We do the same below.
// I'm totally open to a better approach though.
return (Set<ClassMirror<? extends T>>)(Object) classesWithAnnotationThatExtendCache.get(id);
return (Set<ClassMirror<? extends T>>) (Object) classesWithAnnotationThatExtendCache.get(id);
}
Set<ClassMirror<? extends T>> mirrors = new HashSet<>();
for(ClassMirror<?> c : getClassesWithAnnotation(annotation)) {
@@ -741,7 +741,7 @@ public boolean doesClassExtend(ClassMirror<?> subClass, Class<?> superClass) {
// ourselves here.
mirrors.add(new ClassMirror<>(superClass));
}
classesWithAnnotationThatExtendCache.put(id, (Set<ClassMirror<?>>)(Object)mirrors);
classesWithAnnotationThatExtendCache.put(id, (Set<ClassMirror<?>>) (Object) mirrors);
return mirrors;
}

@@ -6,11 +6,14 @@
package com.laytonsmith.core;

import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.annotations.MEnum;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.compiler.CompilerEnvironment;
import com.laytonsmith.core.constructs.NativeTypeList;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.natives.interfaces.Mixed;
import com.laytonsmith.core.objects.ObjectDefinitionNotFoundException;
import com.laytonsmith.core.objects.ObjectDefinitionTable;
import java.util.ArrayList;
@@ -26,6 +29,7 @@
public static final String PATH_SEPARATOR = ".";

private final String fullyQualifiedName;
private Class<? extends Mixed> nativeClass;

private FullyQualifiedClassName(String name) {
Objects.requireNonNull(name, "The name passed in may not be null");
@@ -70,13 +74,56 @@ public static FullyQualifiedClassName forName(String unqualified, Target t, Envi
}

/**
* If the class is known for sure to be within the default import list, this method can be used.
* If the type represents an enum tagged with {@link MEnum}, then this method should be used, but is otherwise
* identical to {@link #forNativeClass(java.lang.Class)}.
* @param clazz
* @return
*/
public static FullyQualifiedClassName forNativeEnum(Class<? extends Enum> clazz) {
MEnum m = clazz.getAnnotation(MEnum.class);
if(m == null) {
throw new Error("Native enum " + clazz + " does not provide an MEnum annotation");
}
String fqcn = m.value();
FullyQualifiedClassName f = new FullyQualifiedClassName(fqcn);
try {
f.nativeClass = NativeTypeList.getNativeEnumType(f).typeof().getNativeType();
} catch (ClassNotFoundException ex) {
// This can't happen, it would have already been the above error.
throw new Error(ex);
}
return f;
}

/**
* If the type represents a native class, this method can be used. Not only does it never throw an exception
* (except an Error, if the class does not define a typeof annotation), getNativeClass will return a reference
* to the Class object, which is useful for shortcutting various operations.
* @param clazz
* @return
*/
public static FullyQualifiedClassName forNativeClass(Class<? extends Mixed> clazz) {
typeof t = clazz.getAnnotation(typeof.class);
if(t == null) {
throw new Error("Native class " + clazz + " does not provide a typeof annotation");
}
String fqcn = t.value();
FullyQualifiedClassName f = new FullyQualifiedClassName(fqcn);
f.nativeClass = clazz;
return f;
}

/**
* If the class is known for sure to be within the default import list, this method can be used. If the native
* class is available, this MUST not be used, as it causes too much of a performance hit. Instead, use
* {@link #forNativeClass(java.lang.Class)} or {@link #forNativeEnum(java.lang.Class)}. DynamicEnums are not
* specially supported, but they can safely use this method, though their use should be very limited.
* @param unqualified The (potentially) unqualified type.
* @param t The code target.
* @return The FullyQualifiedClassName.
* @throws CRECastException If the class type can't be found
*/
public static FullyQualifiedClassName forDefaultClasses(String unqualified, Target t) throws CRECastException {
private static FullyQualifiedClassName forDefaultClasses(String unqualified, Target t) throws CRECastException {
String fqcn = NativeTypeList.resolveNativeType(unqualified);
if(fqcn == null) {
throw new CRECastException("Cannot find \"" + unqualified + "\" type", t);
@@ -88,6 +135,8 @@ public static FullyQualifiedClassName forDefaultClasses(String unqualified, Targ
* If you know for a fact that the name is already fully qualified, this step skips qualification. If you aren't
* sure whether or not the name is fully qualified, don't use the method, the other methods will accept a fully
* qualified class name, but not change it, but if it isn't fully qualified, then it will do so.
*
* If this represents a native class, use {@link #forNativeClass(java.lang.Class)} instead.
* @param qualified
* @return
*/
@@ -130,6 +179,14 @@ public boolean isTypeUnion() {
return this.fullyQualifiedName.contains("|");
}

/**
* Returns the underlying native class, iff this is a native class.
* @return
*/
public Class<? extends Mixed> getNativeClass() {
return nativeClass;
}

public String getSimpleName() {
List<String> parts = new ArrayList<>();
for(String t : fullyQualifiedName.split("\\|")) {
@@ -581,7 +581,7 @@ public static Construct resolveConstruct(String val, Target t, boolean returnBar
if(fqType != null) {
try {
return CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(fqType));
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
// Can't happen, because we just resolved the type, and it wasn't null.
throw new Error(ex);
}
@@ -60,7 +60,7 @@
public static final CClassType[] EMPTY_CLASS_ARRAY = new CClassType[0];

static {
CACHE.put(FullyQualifiedClassName.forFullyQualifiedClass("ms.lang.ClassType"), TYPE);
CACHE.put(FullyQualifiedClassName.forNativeClass(CClassType.class), TYPE);
}

private final boolean isTypeUnion;
@@ -104,12 +104,7 @@
*/
public static CClassType get(Class<? extends Mixed> type) {
try {
typeof typeof = type.getAnnotation(typeof.class);
if(typeof == null) {
throw new IllegalArgumentException("Missing typeof annotation for " + type);
}
String fqcn = typeof.value();
CClassType t = get(FullyQualifiedClassName.forFullyQualifiedClass(fqcn));
CClassType t = get(FullyQualifiedClassName.forNativeClass(type));
t.nativeClass = type;
return t;
} catch (ClassNotFoundException ex) {
@@ -241,7 +236,11 @@ private CClassType(FullyQualifiedClassName type, Target t, boolean newDefinition
found = true;
}
} else {
found = null != NativeTypeList.resolveNativeType(fqcn.getFQCN());
if(fqcn.getNativeClass() != null) {
found = true;
} else {
found = null != NativeTypeList.resolveNativeType(fqcn.getFQCN());
}
}
}
// TODO: When user types are added, we will need to do some more digging here, and probably need
@@ -593,6 +592,14 @@ public Mixed slice(int begin, int end, Target t) {
throw new CREUnsupportedOperationException("Unsupported operation", t);
}


/**
* If this was constructed against a native class, we can do some optimizations in the course
* of operation. This may be null, and all code that uses this method must support the mechanisms if this
* is null anyways, but if it isn't null, then this can perhaps be used to help optimize.
* @return
*/
public Class<? extends Mixed> getNativeType() {
return nativeClass;
}

}
@@ -100,12 +100,12 @@ public static boolean isInstanceof(Mixed value, CClassType instanceofThis, Envir
return isInstanceof(value, instanceofThis.getFQCN(), env);
}

private static FullyQualifiedClassName typeof(Class<?> c) {
private static FullyQualifiedClassName typeof(Class<? extends Mixed> c) {
typeof type = c.getAnnotation(typeof.class);
if(type == null) {
return null;
} else {
return FullyQualifiedClassName.forFullyQualifiedClass(type.value());
return FullyQualifiedClassName.forNativeClass(c);
}
}

@@ -100,22 +100,21 @@ public static String resolveNativeType(String simpleName) {
for(ClassMirror<? extends Mixed> c : ClassDiscovery.getDefaultInstance()
.getClassesWithAnnotationThatExtend(typeof.class, Mixed.class)) {
nativeTypes.add((String) c.getAnnotation(typeof.class).getValue("value"));
fqNativeTypes.add(FullyQualifiedClassName.forNativeClass(c.loadClass()));
}

for(ClassMirror<? extends Enum> c : ClassDiscovery.getDefaultInstance()
.getClassesWithAnnotationThatExtend(MEnum.class, Enum.class)) {
String name = (String) c.getAnnotation(MEnum.class).getValue("value");
nativeTypes.add(name);
fqNativeTypes.add(FullyQualifiedClassName.forNativeEnum(c.loadClass()));
}

for(ClassMirror<? extends DynamicEnum> c : ClassDiscovery.getDefaultInstance()
.getClassesWithAnnotationThatExtend(MDynamicEnum.class, DynamicEnum.class)) {
String name = (String) c.getAnnotation(MDynamicEnum.class).getValue("value");
nativeTypes.add(name);
}

for(String s : nativeTypes) {
fqNativeTypes.add(FullyQualifiedClassName.forFullyQualifiedClass(s));
fqNativeTypes.add(FullyQualifiedClassName.forFullyQualifiedClass(name));
}
}
}
@@ -139,6 +138,9 @@ public static String resolveNativeType(String simpleName) {
* be thrown.
*/
public static Class<? extends Mixed> getNativeClass(FullyQualifiedClassName fqcn) throws ClassNotFoundException {
if(fqcn.getNativeClass() != null) {
return fqcn.getNativeClass();
}
// This is super super expensive, so we cannot afford to run this more than once per class. Even more
// ideally, this information should be stored WITH the class, so there's no runtime penalty whatsoever,
// but having a local cache is at least an improvement.
@@ -326,8 +328,7 @@ public static Mixed getNativeInvalidInstanceForUse(Class<? extends Mixed> clazz)
throw new RuntimeException(clazz + " is missing typeof annotation!");
}
try {
return getInvalidInstanceForUse(FullyQualifiedClassName
.forFullyQualifiedClass(clazz.getAnnotation(typeof.class).value()));
return getInvalidInstanceForUse(FullyQualifiedClassName.forNativeClass(clazz));
} catch (ClassNotFoundException e) {
throw new Error(e);
}
@@ -3,15 +3,12 @@
import com.laytonsmith.PureUtilities.Common.Annotations.ForceImplementation;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.FullyQualifiedClassName;
import com.laytonsmith.core.MSVersion;
import com.laytonsmith.core.constructs.CClassType;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.natives.interfaces.ArrayAccess;
import com.laytonsmith.core.natives.interfaces.Mixed;
import com.laytonsmith.core.objects.ObjectType;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*
@@ -54,16 +51,19 @@ public Version since() {
if(this.getClass() == CREThrowable.class) {
return new CClassType[]{Mixed.TYPE};
} else {
try {
// try {
return new CClassType[]{
CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(
this.getClass().getSuperclass().getAnnotation(typeof.class).value()))
CClassType.get(
// The superclass of a subclass to this class will always be of type Mixed,
// so this cast will never fail, but we need it to convince the compiler this is ok.
(Class<? extends Mixed>) this.getClass().getSuperclass()
)
};
} catch(ClassNotFoundException ex) {
throw new Error("Subclasses can reliably return super.getSuperclasses() for this, ONLY if it follows"
+ " the rule that it only has one superclass, and that superclass is the underlying java"
+ " object as well. This appears to be wrong in " + this.getClass());
}
// } catch(ClassNotFoundException ex) {
// throw new Error("Subclasses can reliably return super.getSuperclasses() for this, ONLY if it follows"
// + " the rule that it only has one superclass, and that superclass is the underlying java"
// + " object as well. This appears to be wrong in " + this.getClass());
// }
}
}

@@ -57,8 +57,6 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@@ -216,13 +214,13 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntime
//No name provided
for(ClassMirror<? extends Enum> e : enums) {
String name = (String) e.getAnnotation(MEnum.class).getValue("value");
a.push(CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(name)), t);
a.push(CClassType.get(FullyQualifiedClassName.forNativeEnum(e.loadClass())), t);
}
for(ClassMirror<? extends DynamicEnum> d : dEnums) {
String name = (String) d.getAnnotation(MDynamicEnum.class).getValue("value");
a.push(CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(name)), t);
}
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new Error(ex);
}
} else if(args.length == 2) {
@@ -59,7 +59,7 @@ public void testEnumTypeIsFound() throws ClassNotFoundException {
@Test
public void testEnumTypeValueIsCorrect() throws ClassNotFoundException {
MEnumType t = NativeTypeList.getNativeEnumType(FullyQualifiedClassName
.forFullyQualifiedClass("ms.lang.ArraySortType"));
.forNativeEnum(CArray.ArraySortType.class));
MEnumTypeValue v = t.values().get(0);
assertEquals(0, v.ordinal());
assertEquals("REGULAR", v.name());

0 comments on commit 3e28991

Please sign in to comment.
You can’t perform that action at this time.