Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FMA Auto Detection #8

Open
wants to merge 3 commits into
base: fma
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.dfsek.paralithic.node.binary.CommutativeBinaryNode;
import com.dfsek.paralithic.node.Constant;
import com.dfsek.paralithic.node.special.function.NativeFunctionNode;
import com.dfsek.paralithic.util.Constants;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.MethodVisitor;

Expand All @@ -14,7 +15,6 @@
import static org.objectweb.asm.Opcodes.*;

public class AdditionNode extends CommutativeBinaryNode {
private static final boolean FMA = "true".equals(System.getProperty("paralithic.optimisation.fma"));

public AdditionNode(Node left, Node right) {
super(left, right);
Expand Down Expand Up @@ -42,10 +42,10 @@ public Constant constantSimplify() {

@Override
public @NotNull Node finalSimplify() {
if(FMA && left instanceof MultiplicationNode m) {
if(Constants.HAS_FAST_SCALAR_FMA && left instanceof MultiplicationNode m) {
return new NativeFunctionNode(NativeMath.FMA, List.of(m.getLeft(), m.getRight(), right));
}
if(FMA && right instanceof MultiplicationNode m) {
if(Constants.HAS_FAST_SCALAR_FMA && right instanceof MultiplicationNode m) {
return new NativeFunctionNode(NativeMath.FMA, List.of(m.getLeft(), m.getRight(), left));
}
if(left instanceof Constant c && c.getValue() == 0) {
Expand Down
189 changes: 189 additions & 0 deletions src/main/java/com/dfsek/paralithic/util/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Sourced from Apache Lucene.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dfsek.paralithic.util;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.logging.Logger;

/** Some useful constants. */
public final class Constants {
private Constants() {} // can't construct

private static final String UNKNOWN = "Unknown";

/** JVM vendor info. */
public static final String JVM_VENDOR = getSysProp("java.vm.vendor", UNKNOWN);

/** JVM vendor name. */
public static final String JVM_NAME = getSysProp("java.vm.name", UNKNOWN);

/** The value of <code>System.getProperty("os.name")</code>. * */
public static final String OS_NAME = getSysProp("os.name", UNKNOWN);

/** True iff running on Linux. */
public static final boolean LINUX = OS_NAME.startsWith("Linux");

/** True iff running on Windows. */
public static final boolean WINDOWS = OS_NAME.startsWith("Windows");

/** True iff running on SunOS. */
public static final boolean SUN_OS = OS_NAME.startsWith("SunOS");

/** True iff running on Mac OS X */
public static final boolean MAC_OS_X = OS_NAME.startsWith("Mac OS X");

/** True iff running on FreeBSD */
public static final boolean FREE_BSD = OS_NAME.startsWith("FreeBSD");

/** The value of <code>System.getProperty("os.arch")</code>. */
public static final String OS_ARCH = getSysProp("os.arch", UNKNOWN);

/** The value of <code>System.getProperty("os.version")</code>. */
public static final String OS_VERSION = getSysProp("os.version", UNKNOWN);

/** The value of <code>System.getProperty("java.vendor")</code>. */
public static final String JAVA_VENDOR = getSysProp("java.vendor", UNKNOWN);

/** True iff the Java runtime is a client runtime and C2 compiler is not enabled. */
public static final boolean IS_CLIENT_VM =
getSysProp("java.vm.info", "").contains("emulated-client");

/** True iff the Java VM is based on Hotspot and has the Hotspot MX bean readable by Paralithic. */
public static final boolean IS_HOTSPOT_VM = HotspotVMOptions.IS_HOTSPOT_VM;

/** True if jvmci is enabled (e.g. graalvm) */
public static final boolean IS_JVMCI_VM =
HotspotVMOptions.get("UseJVMCICompiler").map(Boolean::valueOf).orElse(false);

/** True iff running on a 64bit JVM */
public static final boolean JRE_IS_64BIT = is64Bit();

private static boolean is64Bit() {
final String datamodel = getSysProp("sun.arch.data.model");
if (datamodel != null) {
return datamodel.contains("64");
} else {
return (OS_ARCH != null && OS_ARCH.contains("64"));
}
}

/** true if FMA likely means a cpu instruction and not BigDecimal logic. */
private static final boolean HAS_FMA =
(IS_CLIENT_VM == false) && HotspotVMOptions.get("UseFMA").map(Boolean::valueOf).orElse(false);

/** maximum supported vectorsize. */
private static final int MAX_VECTOR_SIZE =
HotspotVMOptions.get("MaxVectorSize").map(Integer::valueOf).orElse(0);

/** true for an AMD cpu with SSE4a instructions. */
private static final boolean HAS_SSE4A =
HotspotVMOptions.get("UseXmmI2F").map(Boolean::valueOf).orElse(false);

/** true iff we know VFMA has faster throughput than separate vmul/vadd. */
public static final boolean HAS_FAST_VECTOR_FMA = hasFastVectorFMA();

/** true iff we know FMA has faster throughput than separate mul/add. */
public static final boolean HAS_FAST_SCALAR_FMA = hasFastScalarFMA();

private static boolean hasFastVectorFMA() {
if (HAS_FMA) {
String value = getSysProp("paralithic.useVectorFMA", "auto");
if ("auto".equals(value)) {
// newer Neoverse cores have their act together
// the problem is just apple silicon (this is a practical heuristic)
if (OS_ARCH.equals("aarch64") && MAC_OS_X == false) {
return true;
}
// zen cores or newer, its a wash, turn it on as it doesn't hurt
// starts to yield gains for vectors only at zen4+
if (HAS_SSE4A && MAX_VECTOR_SIZE >= 32) {
return true;
}
// intel has their act together
if (OS_ARCH.equals("amd64") && HAS_SSE4A == false) {
return true;
}
} else {
return Boolean.parseBoolean(value);
}
}
// everyone else is slow, until proven otherwise by benchmarks
return false;
}

private static boolean hasFastScalarFMA() {
if (HAS_FMA) {
String value = getSysProp("paralithic.useScalarFMA", "auto");
if ("auto".equals(value)) {
// newer Neoverse cores have their act together
// the problem is just apple silicon (this is a practical heuristic)
if (OS_ARCH.equals("aarch64") && MAC_OS_X == false) {
return true;
}
// latency becomes 4 for the Zen3 (0x19h), but still a wash
// until the Zen4 anyway, and big drop on previous zens:
if (HAS_SSE4A && MAX_VECTOR_SIZE >= 64) {
return true;
}
// intel has their act together
if (OS_ARCH.equals("amd64") && HAS_SSE4A == false) {
return true;
}
} else {
return Boolean.parseBoolean(value);
}
}
// everyone else is slow, until proven otherwise by benchmarks
return false;
}

private static String getSysProp(String property) {
try {
return doPrivileged(() -> System.getProperty(property));
} catch (
@SuppressWarnings("unused")
SecurityException se) {
logSecurityWarning(property);
return null;
}
}

private static String getSysProp(String property, String def) {
try {
return doPrivileged(() -> System.getProperty(property, def));
} catch (
@SuppressWarnings("unused")
SecurityException se) {
logSecurityWarning(property);
return def;
}
}

private static void logSecurityWarning(String property) {
var log = Logger.getLogger(Constants.class.getName());
log.warning("SecurityManager prevented access to system property: " + property);
}

// Extracted to a method to be able to apply the SuppressForbidden annotation
@SuppressWarnings("removal")
@SuppressForbidden(reason = "security manager")
private static <T> T doPrivileged(PrivilegedAction<T> action) {
return AccessController.doPrivileged(action);
}
}
91 changes: 91 additions & 0 deletions src/main/java/com/dfsek/paralithic/util/HotspotVMOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Sourced from Apache Lucene.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dfsek.paralithic.util;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.logging.Logger;

/** Accessor to get Hotspot VM Options (if available). */
final class HotspotVMOptions {
private HotspotVMOptions() {} // can't construct

/** True iff the Java VM is based on Hotspot and has the Hotspot MX bean readable by Paralithic */
public static final boolean IS_HOTSPOT_VM;

/**
* Returns an optional with the value of a Hotspot VM option. If the VM option does not exist or
* is not readable, returns an empty optional.
*/
public static Optional<String> get(String name) {
return ACCESSOR.apply(Objects.requireNonNull(name, "name"));
}

private static final String MANAGEMENT_FACTORY_CLASS = "java.lang.management.ManagementFactory";
private static final String HOTSPOT_BEAN_CLASS = "com.sun.management.HotSpotDiagnosticMXBean";
private static final Function<String, Optional<String>> ACCESSOR;

static {
boolean isHotspot = false;
Function<String, Optional<String>> accessor = name -> Optional.empty();
try {
final Class<?> beanClazz = Class.forName(HOTSPOT_BEAN_CLASS);
// we use reflection for this, because the management factory is not part
// of java.base module:
final Object hotSpotBean =
Class.forName(MANAGEMENT_FACTORY_CLASS)
.getMethod("getPlatformMXBean", Class.class)
.invoke(null, beanClazz);
if (hotSpotBean != null) {
final Method getVMOptionMethod = beanClazz.getMethod("getVMOption", String.class);
final Method getValueMethod = getVMOptionMethod.getReturnType().getMethod("getValue");
isHotspot = true;
accessor =
name -> {
try {
final Object vmOption = getVMOptionMethod.invoke(hotSpotBean, name);
return Optional.of(getValueMethod.invoke(vmOption).toString());
} catch (@SuppressWarnings("unused")
ReflectiveOperationException
| RuntimeException e) {
return Optional.empty();
}
};
}
} catch (@SuppressWarnings("unused") ReflectiveOperationException | RuntimeException e) {
isHotspot = false;
final Logger log = Logger.getLogger(HotspotVMOptions.class.getName());
final Module module = HotspotVMOptions.class.getModule();
final ModuleLayer layer = module.getLayer();
// classpath / unnamed module has no layer, so we need to check:
if (layer != null
&& layer.findModule("jdk.management").map(module::canRead).orElse(false) == false) {
log.warning(
"Paralithic cannot access JVM internals to optimize performance, unless the 'jdk.management' Java module "
+ "is readable [please add 'jdk.management' to modular application either by command line or its module descriptor].");
} else {
log.warning(
"Paralithic cannot optimize performance for JVMs that are not based on Hotspot or a compatible implementation.");
}
}
IS_HOTSPOT_VM = isHotspot;
ACCESSOR = accessor;
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/dfsek/paralithic/util/SuppressForbidden.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Sourced from Apache Lucene.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dfsek.paralithic.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to suppress forbidden-apis errors inside a whole class, a method, or a field.
*
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface SuppressForbidden {
/** A reason for suppressing should always be given. */
String reason();
}