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

8302191: Performance degradation for float/double modulo on Linux #12508

Closed
wants to merge 14 commits into from
Closed
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
32 changes: 32 additions & 0 deletions src/hotspot/cpu/x86/sharedRuntime_x86.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "precompiled.hpp"
#include "asm/macroAssembler.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/sharedRuntime.hpp"
#include "vmreg_x86.inline.hpp"
#ifdef COMPILER1
Expand Down Expand Up @@ -83,3 +84,34 @@ void SharedRuntime::inline_check_hashcode_from_object_header(MacroAssembler* mas
}
#endif //COMPILER1

#if defined(TARGET_COMPILER_gcc) && !defined(_WIN64)
JRT_LEAF(jfloat, SharedRuntime::frem(jfloat x, jfloat y))
jfloat retval;
asm ("\
1: \n\
fprem \n\
fnstsw %%ax \n\
test $0x4,%%ah \n\
jne 1b \n\
"
:"=t"(retval)
jankratochvil marked this conversation as resolved.
Show resolved Hide resolved
:"0"(x), "u"(y)
:"cc", "ax");
return retval;
JRT_END

JRT_LEAF(jdouble, SharedRuntime::drem(jdouble x, jdouble y))
jdouble retval;
asm ("\
1: \n\
fprem \n\
fnstsw %%ax \n\
test $0x4,%%ah \n\
jne 1b \n\
"
:"=t"(retval)
:"0"(x), "u"(y)
:"cc", "ax");
return retval;
JRT_END
#endif // TARGET_COMPILER_gcc && !_WIN64
7 changes: 5 additions & 2 deletions src/hotspot/share/runtime/sharedRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,15 @@ JRT_LEAF(jlong, SharedRuntime::lrem(jlong y, jlong x))
JRT_END


#ifdef _WIN64
const juint float_sign_mask = 0x7FFFFFFF;
const juint float_infinity = 0x7F800000;
const julong double_sign_mask = CONST64(0x7FFFFFFFFFFFFFFF);
const julong double_infinity = CONST64(0x7FF0000000000000);
#endif

JRT_LEAF(jfloat, SharedRuntime::frem(jfloat x, jfloat y))
#if !defined(X86) || !defined(TARGET_COMPILER_gcc) || defined(_WIN64)
JRT_LEAF(jfloat, SharedRuntime::frem(jfloat x, jfloat y))
#ifdef _WIN64
// 64-bit Windows on amd64 returns the wrong values for
// infinity operands.
Expand All @@ -251,7 +254,6 @@ JRT_LEAF(jfloat, SharedRuntime::frem(jfloat x, jfloat y))
#endif
JRT_END


JRT_LEAF(jdouble, SharedRuntime::drem(jdouble x, jdouble y))
#ifdef _WIN64
union { jdouble d; julong l; } xbits, ybits;
Expand All @@ -267,6 +269,7 @@ JRT_LEAF(jdouble, SharedRuntime::drem(jdouble x, jdouble y))
return ((jdouble)fmod((double)x,(double)y));
#endif
JRT_END
#endif // !X86 || !TARGET_COMPILER_gcc || _WIN64

JRT_LEAF(jfloat, SharedRuntime::i2f(jint x))
return (jfloat)x;
Expand Down
229 changes: 229 additions & 0 deletions test/micro/org/openjdk/bench/vm/floatingpoint/DremFrem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Copyright (c) 2023, Azul Systems, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.vm.floatingpoint;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
* Tests for float and double modulo.
* Testcase is based on: https://github.com/cirosantilli/java-cheat/blob/c5ffd8ea19c5620ce752b6c98b2d3579be2bef98/Nan.java
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class DremFrem {

private static final int DEFAULT_X_RANGE = 1 << 11;
private static final int DEFAULT_Y_RANGE = 1 << 11;
private static boolean regressionValue = false;

@Benchmark
@OperationsPerInvocation(DEFAULT_X_RANGE * DEFAULT_Y_RANGE)
public void calcFloatJava(Blackhole bh) {
for (int i = 0; i < DEFAULT_X_RANGE; i++) {
for (int j = DEFAULT_Y_RANGE; j > 0; j--) {
float x = i;
float y = j;
boolean result = (13.0F * x * x * x) % y == 1.0F;
regressionValue = regressionValue & result;
}
}
}

@Benchmark
@OperationsPerInvocation(DEFAULT_X_RANGE * DEFAULT_Y_RANGE)
public void calcDoubleJava(Blackhole bh) {
for (int i = 0; i < DEFAULT_X_RANGE; i++) {
for (int j = DEFAULT_Y_RANGE; j > 0; j--) {
double x = i;
double y = j;
boolean result = (13.0D * x * x * x) % y == 1.0D;
regressionValue = regressionValue & result;
}
}
}

@SuppressWarnings("divzero")
public void cornercaseFloatJava_divzero(Blackhole bh) {
assert Float.isNaN(10 / 0);
assert Float.isNaN(10 / 0);
}

@Benchmark
@OperationsPerInvocation(DEFAULT_X_RANGE * DEFAULT_Y_RANGE)
public void cornercaseFloatJava(Blackhole bh) {
for (int i = 0; i < DEFAULT_X_RANGE * DEFAULT_Y_RANGE; i++) {
// Generate some NaNs.
float nan = Float.NaN;
float zero_div_zero = 0.0f / 0.0f;
float sqrt_negative = (float)Math.sqrt(-1.0);
float log_negative = (float)Math.log(-1.0);
float inf_minus_inf = Float.POSITIVE_INFINITY - Float.POSITIVE_INFINITY;
float inf_times_zero = Float.POSITIVE_INFINITY * 0.0f;
float quiet_nan1 = Float.intBitsToFloat(0x7fc00001);
float quiet_nan2 = Float.intBitsToFloat(0x7fc00002);
float signaling_nan1 = Float.intBitsToFloat(0x7fa00001);
float signaling_nan2 = Float.intBitsToFloat(0x7fa00002);
float nan_minus = -nan;

// Generate some infinities.
float positive_inf = Float.POSITIVE_INFINITY;
float negative_inf = Float.NEGATIVE_INFINITY;
float one_div_zero = 1.0f / 0.0f;
float log_zero = (float)Math.log(0.0);

// Double check that they are actually NaNs.
assert Float.isNaN(nan);
assert Float.isNaN(zero_div_zero);
assert Float.isNaN(sqrt_negative);
assert Float.isNaN(inf_minus_inf);
assert Float.isNaN(inf_times_zero);
assert Float.isNaN(quiet_nan1);
assert Float.isNaN(quiet_nan2);
assert Float.isNaN(signaling_nan1);
assert Float.isNaN(signaling_nan2);
assert Float.isNaN(nan_minus);
assert Float.isNaN(log_negative);

// Double check that they are infinities.
assert Float.isInfinite(positive_inf);
assert Float.isInfinite(negative_inf);
assert !Float.isNaN(positive_inf);
assert !Float.isNaN(negative_inf);
assert one_div_zero == positive_inf;
assert log_zero == negative_inf;
// Double check infinities.

assert Float.isNaN(positive_inf / 10);
assert Float.isNaN(negative_inf / 10);
cornercaseFloatJava_divzero(bh);
assert (+10 / positive_inf) == +10;
assert (+10 / negative_inf) == +10;
assert (-10 / positive_inf) == -10;
assert (-10 / negative_inf) == -10;

// NaN comparisons always fail.
// Therefore, all tests that we will do afterwards will be just isNaN.
assert !(1.0f < nan);
assert !(1.0f == nan);
assert !(1.0f > nan);
assert !(nan == nan);

// NaN propagate through most operations.
assert Float.isNaN(nan + 1.0f);
assert Float.isNaN(1.0f + nan);
assert Float.isNaN(nan + nan);
assert Float.isNaN(nan / 1.0f);
assert Float.isNaN(1.0f / nan);
assert Float.isNaN((float)Math.sqrt((double)nan));
}
}

@SuppressWarnings("divzero")
public void cornercaseDoubleJava_divzero(Blackhole bh) {
assert Double.isNaN(10 / 0);
assert Double.isNaN(10 / 0);
}

@Benchmark
@OperationsPerInvocation(DEFAULT_X_RANGE * DEFAULT_Y_RANGE)
public void cornercaseDoubleJava(Blackhole bh) {
for (int i = 0; i < DEFAULT_X_RANGE * DEFAULT_Y_RANGE; i++) {
// Generate some NaNs.
double nan = Double.NaN;
double zero_div_zero = 0.0f / 0.0f;
double sqrt_negative = (double)Math.sqrt(-1.0);
double log_negative = (double)Math.log(-1.0);
double inf_minus_inf = Double.POSITIVE_INFINITY - Double.POSITIVE_INFINITY;
double inf_times_zero = Double.POSITIVE_INFINITY * 0.0f;
double quiet_nan1 = Double.longBitsToDouble(0x7ffc000000000001L);
double quiet_nan2 = Double.longBitsToDouble(0x7ffc000000000002L);
double signaling_nan1 = Double.longBitsToDouble(0x7ffa000000000001L);
double signaling_nan2 = Double.longBitsToDouble(0x7ffa000000000002L);
double nan_minus = -nan;

// Generate some infinities.
double positive_inf = Double.POSITIVE_INFINITY;
double negative_inf = Double.NEGATIVE_INFINITY;
double one_div_zero = 1.0d / 0.0f;
double log_zero = (double)Math.log(0.0);

// Double check that they are actually NaNs.
assert Double.isNaN(nan);
assert Double.isNaN(zero_div_zero);
assert Double.isNaN(sqrt_negative);
assert Double.isNaN(inf_minus_inf);
assert Double.isNaN(inf_times_zero);
assert Double.isNaN(quiet_nan1);
assert Double.isNaN(quiet_nan2);
assert Double.isNaN(signaling_nan1);
assert Double.isNaN(signaling_nan2);
assert Double.isNaN(nan_minus);
assert Double.isNaN(log_negative);

// Double check that they are infinities.
assert Double.isInfinite(positive_inf);
assert Double.isInfinite(negative_inf);
assert !Double.isNaN(positive_inf);
assert !Double.isNaN(negative_inf);
assert one_div_zero == positive_inf;
assert log_zero == negative_inf;
// Double check infinities.

assert Double.isNaN(positive_inf / 10);
assert Double.isNaN(negative_inf / 10);
cornercaseDoubleJava_divzero(bh);
assert (+10 / positive_inf) == +10;
assert (+10 / negative_inf) == +10;
assert (-10 / positive_inf) == -10;
assert (-10 / negative_inf) == -10;

// NaN comparisons always fail.
// Therefore, all tests that we will do afterwards will be just isNaN.
assert !(1.0d < nan);
assert !(1.0d == nan);
assert !(1.0d > nan);
assert !(nan == nan);

// NaN propagate through most operations.
assert Double.isNaN(nan + 1.0d);
assert Double.isNaN(1.0d + nan);
assert Double.isNaN(nan + nan);
assert Double.isNaN(nan / 1.0d);
assert Double.isNaN(1.0d / nan);
assert Double.isNaN((double)Math.sqrt((double)nan));
}
}

}